问题
I'm trying to build a data-driven form, with inputs coming from another component, like this:
<form [formGroup]="signupForm" (ngSubmit)="onSubmit()">
<app-form-group [name]="name"></app-form-group>
<app-form-group [name]="email"></app-form-group>
<app-form-group [name]="other"></app-form-group>
</form>
The app-form-group
component will look something like this:
<div class="form-group">
<label class="col-md-2 control-label">{{Name}}</label>
<div class="col-md-9">
<input class="form-control" [name]="name" [formControlName]="formCtrlName">
</div>
The problem is that formControlName
needs a formGroup
directive, therefore I get this error:
Error : Error in ./FormGroupComponent class FormGroupComponent - inline template:3:58 caused by: formControlName must be used with a parent formGroup directive.You'll want to add a formGroup
directive and pass it an existing FormGroup instance (you can create one in your class).
Is there any way to get around this issue?
回答1:
You should use your FormGroup [formGroup]="signupForm"
in app-form-group
Component.You can use this Code :
<div class="form-group" [formGroup]="signupForm">
<label class="col-md-2 control-label">{{Name}}</label>
<div class="col-md-9">
<input class="form-control" [name]="name" [formControlName]="formCtrlName">
</div>
回答2:
You can use a @Input property to get the reference into the sub-component:
app-form-group.ts:
@Input('group')
public signupForm: FormGroup;
app.html:
<form [formGroup]="signupForm" (ngSubmit)="onSubmit()">
<app-form-group [name]="name" [group]="signupForm"></app-form-group>
<app-form-group [name]="name" [group]="signupForm"></app-form-group>
<app-form-group [name]="name" [group]="signupForm"></app-form-group>
</form>
app-form-group.html:
<div class="form-group" [formGroup]="signupForm">
<label class="col-md-2 control-label">{{Name}}</label>
<div class="col-md-9">
<input class="form-control" [name]="name" [formControlName]="formCtrlName">
</div>
For detailed information have a look at this tutorial: https://scotch.io/tutorials/how-to-build-nested-model-driven-forms-in-angular-2
回答3:
When you want to implement a custom component for data-binding to an individual value the correct Angular way to do that is to go with the approach where the parent view specifies either [formControl]
or [formControlName]
.
<app-form-group formControlName="name"></app-form-group>
<app-form-group formControlName="email"></app-form-group>
<app-form-group formControlName="other"></app-form-group>
In your child control you need to do the following:
First add the following provider to your @Component
declaration, so Angular knows your component can work with [formControlName]
/ [formControl]
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CountrySelectorComponent),
multi: true
}
]
Your child component's class then needs to implement the ControlValueAccessor
interface. This should be a fully working example, if not then let me know and I will change the code, I typed it into the browser directly.
@Component({
// Ensure [formControl] or [formControlName] is also specified
selector: '[formControl] app-form-group, [formControlName] app-form-group',
templateUrl: './country-selector.component.html',
styleUrls: ['./country-selector.component.scss'],
providers: [
{
// Provide the value accessor
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CountrySelectorComponent),
multi: true
}
]
})
export class CountrySelectorComponent implements ControlValueAccessor {
private value: any;
private hasHadFocus = false;
private hasNotifiedTouched = false;
private propagateChange: any = () => {};
private propogateTouched: any = () => {};
public changed(event: any) {
this.value = event.srcElement.value;
this.propagateChange(this.value);
}
/**
* Called when (focus) is triggered
*/
public focused() {
this.hasHadFocus = true;
}
/**
* Called when (blur) is triggered
*/
public blurred() {
if (this.hasHadFocus && !this.hasNotifiedTouched) {
// Only notify once, and only if lost focus after a focus
this.hasNotifiedTouched = true;
this.propogateTouched();
}
}
/**
* Called when a new value is set via code
* @param obj
*/
writeValue(obj: any): void {
this.value = obj;
}
/**
* Register a call back to call when our value changes
* @param fn
*/
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
/**
* Register a call back to call when our value is first touched
* @param fn
*/
registerOnTouched(fn: any): void {
this.propogateTouched = fn;
}
}
Your app-form-group
template would look something like this
<div class="form-group">
<label class="col-md-2 control-label">{{Name}}</label>
<div class="col-md-9">
<input class="form-control" (blur)="blurred()" focus="focussed()" change="changed($event)">
</div>
- Add
[formGroup]="signupForm"
to each of yourapp-form-group
instances in your main view - Implement OnInit in your
app-form-group
control. - Add
private controlContainer: ControlContainer
to the constructor of yourapp-form-group
component so Angular will inject it - Add
public form: FormGroup;
property to yourapp-form-group
component. In ngOnInit add the following code
this.form = this.containerControl.control;
In your app-form-group
template you would add [formGroup] like so
<div class="form-group" [formGroup]="form">
<label class="col-md-2 control-label">{{Name}}</label>
<div class="col-md-9">
<input class="form-control" [name]="name" [formControlName]="formCtrlName">
</div>
This is the method that requires the least amount of code, and is the one I would recommend when you want to embed composite controls that edit multiple pieces of data (like an Address).
From this blog -> https://mrpmorris.blogspot.co.uk/2017/08/angular-composite-controls-formgroup-formgroupname-reactiveforms.html
来源:https://stackoverflow.com/questions/39895131/angular-2-form-group-component