Angular2 nested template driven form

user4328424 picture user4328424 · Aug 31, 2016 · Viewed 20.3k times · Source

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; 
  }
}

Answer

yurzui picture yurzui · Oct 14, 2017

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.

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