Angular2 nested template driven form
This is just madness , looks like there is no way to have a form which one of it's inputs is in a child component .
I have read all the blogs and tutorials and everything , no way to work this out .
The problem is when a child component is going to have any kind of form directives ( ngModel , ngModelGroup or whatever ..) , it wont work.
This is only a problem in template driven forms
This is the plunker :
import { Component } from '@angular/core';
@Component({
selector: 'child-form-component',
template: `
<fieldset ngModelGroup="address">
<div>
<label>Street:</label>
<input type="text" name="street" ngModel>
</div>
<div>
<label>Zip:</label>
<input type="text" name="zip" ngModel>
</div>
<div>
<label>City:</label>
<input type="text" name="city" ngModel>
</div>
</fieldset>`
})
export class childFormComponent{
}
@Component({
selector: 'form-component',
directives:[childFormComponent],
template: `
<form #form="ngForm" (ngSubmit)="submit(form.value)">
<fieldset ngModelGroup="name">
<div>
<label>Firstname:</label>
<input type="text" name="firstname" ngModel>
</div>
<div>
<label>Lastname:</label>
<input type="text" name="lastname" ngModel>
</div>
</fieldset>
<child-form-component></child-form-component>
<button type="submit">Submit</button>
</form>
<pre>
{{form.value | json}}
</pre>
<h4>Submitted</h4>
<pre>
{{value | json }}
</pre>
`
})
export class FormComponent {
value: any;
submit(form) {
this.value = form;
}
}
Solution 1:
One simple solution is to provide ControlContainer
in viewProviders
array of your child component like:
import { ControlContainer, NgForm } from '@angular/forms';
@Component({
...,
viewProviders: [ { provide: ControlContainer, useExisting: NgForm } ]
})
export class ChildComponent {}
Stackblitz Example
Read also this article that explains why it's working.
- Angular: Nested template driven form
Update
If you're looking for nested model driven form then here is the similar approach:
@Component({
selector: 'my-form-child',
template: `<input formControlName="age">`,
viewProviders: [
{
provide: ControlContainer,
useExisting: FormGroupDirective
}
]
})
export class ChildComponent {
constructor(private parent: FormGroupDirective) {}
ngOnInit() {
this.parent.form.addControl('age', new FormControl('', Validators.required))
}
}
Ng-run Example
Update 2
If you don't know exactly which type of ControlContainer
wraps your custom component(for example your controls is inside FormArray directive) then just use common version:
import { SkipSelf } from '@angular/core';
import { ControlContainer} from '@angular/forms';
@Component({
...,
viewProviders: [{
provide: ControlContainer,
useFactory: (container: ControlContainer) => container,
deps: [[new SkipSelf(), ControlContainer]],
}]
})
export class ChildComponent {}
Ng-run Example
Solution 2:
Reading through a bunch of related github issues [1] [2], I haven't found a straightforward way to make angular add a child Component
's controls to a parent ngForm
(some people also call them nested forms, nested inputs or complex controls).
So what I'm going to show here is a workaround that works for me, using separate ngForm
directives for parents and childs. It's not perfect, but it gets me close enough that I stopped there.
I declare my childFormComponent
with an ngForm
directive (i.e. it's not a html form tag, only the directive):
<fieldset ngForm="addressFieldsForm" #addressFieldsForm="ngForm">
<div class="form-group">
<label for="email">Email</label>
<input type="email" class="form-control" [(ngModel)]="model.email" name="email" #email="ngModel" required placeholder="Email">
</div>
...
The Component then exposes the addressFieldsForm
as a property, and also exports itself as a template reference variable:
@Component({
selector: 'mst-address-fields',
templateUrl: './address-fields.component.html',
styleUrls: ['./address-fields.component.scss'],
exportAs: 'mstAddressFields'
})
export class AddressFieldsComponent implements OnInit {
@ViewChild('addressFieldsForm') public form: NgForm;
....
The parent form can then use the child form component like this:
<form (ngSubmit)="saveAddress()" #ngFormAddress="ngForm" action="#">
<fieldset>
<mst-address-fields [model]="model" #addressFields="mstAddressFields"></mst-address-fields>
<div class="form-group form-buttons">
<button class="btn btn-primary" type="submit" [disabled]="!ngFormAddress.valid || !addressFields.form.valid">Save</button>
</div>
</fieldset>
</form>
Note that the submit button explicitly checks valid state on both the ngFormAddress
and the addressFields
form. That way I can at least sensibly compose complex forms, even though it has some boilerplate.