Composition of ControlValueAccessor to implement nested form is introduced in an Angular Connect 2017 presentation.
In this presentation, the speaker showed a way to implement custom form control which have multiple value (not only single string value but has two string field, like street and city). I want to implement it but I'm stuck. Sample app is here, does anybody know what should I correct?
https://stackblitz.com/edit/angular-h2ehwx
parent component
@Component({
selector: 'my-app',
template: `
<h1>Form</h1>
<form [formGroup]="form" (ngSubmit)="onSubmit(form.value)" novalidate>
<label>name</label>
<input formControlName="name">
<app-address-form formControlName="address"></app-address-form>
<button>submit</button>
</form>
`,
})
export class AppComponent {
@Input() name: string;
submitData = '';
form: FormGroup;
constructor(private fb: FormBuilder) {
this.form = fb.group({
name: 'foo bar',
address: fb.group({
city: 'baz',
town: 'qux',
})
});
}
onSubmit(v: any) {
console.log(v);
}
}
nested form component
@Component({
selector: 'app-address-form',
template: `
<div [formGroup]="form">
<label>city</label>
<input formControlName="city" (blur)="onTouched()">
<label>town</label>
<input formControlName="town" (blur)="onTouched()">
</div>
`,
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: forwardRef(() => AddressFormComponent)
}
]
})
export class AddressFormComponent implements ControlValueAccessor {
form: FormGroup;
onTouched: () => void = () => {};
writeValue(v: any) {
this.form.setValue(v, { emitEvent: false });
}
registerOnChange(fn: (v: any) => void) {
this.form.valueChanges.subscribe(fn);
}
setDisabledState(disabled: boolean) {
disabled ? this.form.disable() : this.form.enable();
}
registerOnTouched(fn: () => void) {
this.onTouched = fn;
}
}
and error message I got
ERROR TypeError: Cannot read property 'setValue' of undefined
at AddressFormComponent.writeValue (address-form.component.ts:32)
at setUpControl (shared.js:47)
at FormGroupDirective.addControl (form_group_directive.js:125)
at FormControlName._setUpControl (form_control_name.js:201)
at FormControlName.ngOnChanges (form_control_name.js:114)
at checkAndUpdateDirectiveInline (provider.js:249)
at checkAndUpdateNodeInline (view.js:472)
at checkAndUpdateNode (view.js:415)
at debugCheckAndUpdateNode (services.js:504)
at debugCheckDirectivesFn (services.js:445)
I think FormGroup instance should be injected to nested form component somehow...
Couple issues, on your
AppComponent
change yourFormBuilder
to:On your
AddressFormComponent
you need to initialize your FormGroup like so:Here's the fork of your sample: https://stackblitz.com/edit/angular-np38bi
We (at work) encountered that issue and tried different things for months: How to properly deal with nested forms.
Indeed,
ControlValueAccessor
seems to be the way to go but we found it very verbose and it was quite long to build nested forms. As we're using that pattern a lot within our app, we've ended up spending some time to investigate and try to come up with a better solution. We called itngx-sub-form
and it's a repo available on NPM (+ source code on Github).Basically, to create a sub form all you have to do is extends a class we provide and also pass your FormControls. That's it.
We've updated our codebase to use it and we're definitely happy about it so you may want to give a try and see how it goes for you :)
Everything is explained in the README on github.
PS: We also have a full demo running here https://cloudnc.github.io/ngx-sub-form