Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nested Forms in Angular Material Stepper trigger a ExpressionChangedAfterItHasBeenCheckedError

I'm trying to divide a Material Angular mat-step (from a mat-horizontal-stepper) into separate nested sub-forms components, and get a ExpressionChangedAfterItHasBeenCheckedError regarding the validity of the form.

This stackblitz demonstrates the problem: https://stackblitz.com/edit/mat-stepper-components

The nested components are in step one.

Interestingly enough, the problem doesn't happen if there is just one level of nesting (if steps do not have nested sub-forms), as demonstrated in step two.

Here are the main parts of the code:

create-profile.component.html

<mat-horizontal-stepper [linear]=true #stepper>
    <mat-step [stepControl]="frmStepOne">
        <ng-template matStepLabel>Step One Details</ng-template>
        <form [formGroup]="frmStepOne"><ng-template matStepLabel>Step One</ng-template>
            <step-one-component></step-one-component>
        </form>
  </mat-step>
    <mat-step [stepControl]="frmStepTwo">
        <ng-template matStepLabel>Step Two Details</ng-template>
        <form [formGroup]="frmStepTwo">
            <step-two-component></step-two-component>
        </form>
    </mat-step>
</mat-horizontal-stepper>

create-profile.component.ts

  //...
  constructor(private fb: FormBuilder) {
    this.frmStepOne = new FormGroup({});
    this.frmStepTwo = new FormGroup({});
  }

step-one.component.html

<step-one-child-one></step-one-child-one>

step-one.component.ts

// nothing interesting, just component boilerplate

step-one-child-one.component.html

<mat-form-field>
  <input matInput formControlName="name" placeholder="Name" required>
</mat-form-field>

step-one-child-one.component.ts

@Component({
  selector: 'step-one-child-one',
  templateUrl: './step-one-child-one.component.html',
  viewProviders: [
    {
      provide: ControlContainer,
      useExisting: FormGroupDirective
    }
  ]
})
export class StepOneChildOneComponent {
  constructor(private parent: FormGroupDirective) {
  }

  ngOnInit() {
    this.parent.form.addControl('name', new FormControl('', [Validators.required]));
  }
}
like image 373
yannick1976 Avatar asked Sep 15 '25 11:09

yannick1976


1 Answers

You need to implement change detection.

Please update create-profile.component.ts with following code

import { Component, ViewChild, Input, ChangeDetectorRef, AfterViewChecked } from '@angular/core';
import { FormGroup, FormControl, Validators, FormBuilder } from '@angular/forms';
import { MatStepper } from '@angular/material';
import { StepOneComponent } from './step-one.component';
import { StepTwoComponent } from './step-two.component';
import { StepThreeComponent } from './step-three.component';

@Component({
  selector: 'create-profile-component',
  templateUrl: './create-profile.component.html'
})

export class CreateProfileComponent implements AfterViewChecked {

  frmStepOne: FormGroup;
  frmStepTwo: FormGroup;
  @ViewChild('StepTwoComponent') stepThreeComponent: StepThreeComponent;

  get frmStepThree() {
    return this.stepThreeComponent ? this.stepThreeComponent.frmStepThree : null;
  }

  constructor(private fb: FormBuilder,
  private changeDetect: ChangeDetectorRef) {
    this.frmStepOne = new FormGroup({});
    this.frmStepTwo = new FormGroup({});
  }

  ngAfterViewChecked(): void {
    this.changeDetect.detectChanges();
  }


}
like image 126
Armin Avatar answered Sep 17 '25 05:09

Armin