ghjghgkj
ghjghgkj

Reputation: 393

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

Upvotes: 5

Views: 22125

Answers (2)

zos1000
zos1000

Reputation: 107

You can also use a custom form control, it is a built-in concept in angular.

The idea is to have a custom component that will serve as a "bridge" between the parent form and the nested form. the custom component will listen to "change" event of the nested form and will update its value with it. the parent form treats the custom control as a single field although its value will be an object containing the nested form controls values.

The custom conponent should implenet an interface called ControlValueAccessor, by which angular provides all neccesary to update the custom control states such as value, valid, touched, pristine and so on. in this way you actually manage the custom control state so that the parent form can treat it like any other form control.

You can read about it in the following links:

https://blog.sreyaj.dev/custom-form-controls-controlvalueaccessor-in-angular

https://medium.com/@majdasab/implementing-control-value-accessor-in-angular-1b89f2f84ebf

https://indepth.dev/posts/1055/never-again-be-confused-when-implementing-controlvalueaccessor-in-angular-forms

Upvotes: 1

ylerjen
ylerjen

Reputation: 4259

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>

Upvotes: 13

Related Questions