Search code examples
javascriptangulartypescriptangular-reactive-formsangular-forms

Angular reactive nested forms


I am trying to nest multiple reactive forms in my angular project and there forms are in different components.

For example I have a component that contains a form with two inputs one input for name and one input for description and a submit button. I call this component NameDescComponent

I am planning to use this component across multiple pages and forms. Here is the html for the component.

<form [formGroup]="nameDescForm" (ngSubmit)="customEmit()">
    <div fxLayout="row" fxLayoutGap="10px" fxFlex>
      <mat-form-field>
        <input matInput placeholder="Name" formControlName="name">
      </mat-form-field>
      <mat-form-field fxFlex>
        <input matInput placeholder="Description" formControlName="description">
      </mat-form-field>
    </div>
    <div fxLayout="column" fxLayoutGap="10px">
      <button type="submit" mat-raised-button color="primary">
        {{buttonText}}
      </button>
      <div>
      </div>
    </div>
  </form>

And here is the abbreviated ts file

public nameDescForm: FormGroup;



@Input() public buttonText: string;
  @Output() public save: EventEmitter<any> = new EventEmitter<any>();
  @Output() public nameDescFormEmit: EventEmitter<FormGroup> = new EventEmitter<FormGroup>();

  constructor(fb: FormBuilder) {
    this.nameDescForm = fb.group({
      'name': ['', Validators.required],
      'description': ['']
    });
  }

  public ngOnInit() {
    console.log(this.nameDescForm);
    this.nameDescFormEmit.emit(this.nameDescForm);
  }

  public customEmit() {
    this.save.emit();
  }

Then in a page where I am using this component I also have another form with the NameDescComponent inside the form, like this

<form [formGroup]="parentForm" (ngSubmit)="customEmit()">

  <app-name-description (nameDescFormEmit)="getNameDescForm($event)" buttonText="Save" (save)="save()"></app-name-description>

  <input type="test" formControlName="test">

</form>

Currently what I am doing is passing the nameDescFrom from its component to the ParentComponent with the Output and EventEmitter. This solution seems to work and when I update child I am able to access the values. But the downside is when I go to submit the form I have to check that the parentForm and then nameDescFrom are both valid and manage both forms separately.

I am wondering if there is a better way to approach this? When I can access the nameDescFrom from within the parent form?

Thanks


Solution

  • To merge your form with nested forms and have a single validation process for all of them, you can use the formbuilder to create the whole model object structure in the root form component. Then in its html template you will add the custom element of the sub-forms (ex: <nested-form>), which will render the sub-forms.

    See example : https://stackblitz.com/edit/angular-m5fexe)

    Useful angular doc links :

    Code :

    export class Form1Component  {
      @Input() name: string;
    
      public dummyForm: FormGroup;
    
      constructor(
          private _fb: FormBuilder,
      ) {
          this.createForm();
      }
    
      createForm() {
        this.dummyForm = this._fb.group({
          username: ['username', Validators.required],
          nestedForm: this._fb.group({        
            complement1: ['complement1', Validators.required],
            complement2: ['complement2', Validators.required],
          })
        });
      }
    
      submit() {
        if (this.dummyForm.valid) {
          console.log('form AND subforms are valid', this.dummyForm.value);
        } else {
          console.warn('form AND/OR subforms are invalid', this.dummyForm.value);
        }
      }
    }
    

    Template for the Form1Component :

    <form [formGroup]="dummyForm" (ngSubmit)="submit()">    
        <div>
          <label for="username">Root Input</label>
          <input type="text" id="username" formControlName="username"/>
        </div>
        <nested-form [parentForm]="dummyForm"></nested-form>
        <button>Send</button>    
      </form>
    

    Nested form code:

    export class NestedFormComponent {
      @Input()
      public parentForm: FormGroup;
    }
    

    Nested form template :

    <form [formGroup]="parentForm">
        <div formGroupName="nestedForm">
          <div>
            <label for="complement1">Nested input 1</label>
            <input type="text" formControlName="complement1"/>
          </div>
          <div>
            <label for="complement1">Nested input 1</label>
            <input type="text" formControlName="complement2"/>
          </div>
        </div>
      </form>