Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular2 nested template driven form

People also ask

How do I validate a template-driven form?

To add validation to a template-driven form, you add the same validation attributes as you would with native HTML form validation. Angular uses directives to match these attributes with validator functions in the framework.

Which directive can be used in submit button of template-driven forms?

The ngForm directive will convert it to the Template-driven form and create the top-level FormGroup control. Next, we use the ngModel directive to create the FormControl instance for each of the HTML form elements. Later, we will learn how to submit the form data to the component class.

What is NgModelGroup?

The NgModelGroup is used to create a top-level form group Instance, and it binds the form to the given form value. Syntax: <div ngModelGroup="name"></div> NgModule: Module used by NgModelGroup is: FormsModule.


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


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.


Another possible workaround:

@Directive({
    selector: '[provide-parent-form]',
    providers: [
        {
            provide: ControlContainer,
            useFactory: function (form: NgForm) {
                return form;
            },
            deps: [NgForm]
        }
    ]
})
export class ProvideParentForm {}

Just place this directive in a child component somewhere at the top of nodes hierarchy (before any ngModel).

How it works: NgModel qualifies parent form's dependency lookup with @Host(). So a form from a parent component is not visible to NgModel in a child component. But we can inject and provide it inside a child component using the code demonstrated above.


From official docs: This directive can only be used as a child of NgForm.

So I think you can try to wrap your child component in different ngForm, and expect in parent component result @Output of child component. Let me know if you need more clarification.

UPDATE: Here is Plunker with some changes, I converted child form to model driven, because there is no way to listen on form driven form for updated before it will be submited.