16

I am using reactive forms in an angular 2+ application and there is a need to pass the main FormGroup to multiple components so that different parts of the form e.g. header, footer etc can be managed in separate components and populated by those different components. This is how I am doing it, at the moment:

<div class="container-fluid">
  <form [formGroup]="orderForm">
    <order-header [orderForm]="orderForm"></order-header>
    <order-items [orderForm]="orderForm"></order-items>
    <order-footer [orderForm]="orderForm"></order-footer>
  </form>
</div>

I am wondering, if this is a correct approach coz I do see a warning/error with this code:

ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'true'. Current value: 'false'.

On this line:

<form [formGroup]="orderForm">

Any suggestions? Thanks.

adeelmahmood
  • 2,371
  • 8
  • 36
  • 59
  • Maybe you should have a form-element inside each of the child components instead of wrapping footer, items, and header inside a form-element? Remove form, and put one in each custom order. Just a wild guess. I do not know if it helps or not. – John May 01 '17 at 22:06

2 Answers2

20

As mentioned by Julia, this is normal behavior.

It's actually the @Input in your components that are causing this, not the line <form [formGroup]="orderForm">

This happens during dev mode. How you can get around this problem, is to manually trigger change detection in the components that receive the orderForm. Since you are using @Input, you can use the ngOnChanges life cycle hook in your other components:

import { ChangeDetectorRef } from '@angular/core';

@Input() orderForm: FormGroup

constructor(private ref: ChangeDetectorRef) { }

ngOnChanges() {
  this.ref.detectChanges()
}

Check more here about your error: Expression ___ has changed after it was checked

Also, as a sidenote, do you really need to pass the complete form to your other components. Usually we pass just the nested group that the component will handle, so something like this:

<form [formGroup]="orderForm">
  <order-header [orderForm]="orderForm.controls.myNestedGroupName"></order-header>
</form>

Just as a sidenote! :)

Community
  • 1
  • 1
AT82
  • 71,416
  • 24
  • 140
  • 167
  • Thank you! I wonder where do I find an official source for the "Angular in dev mode perform two change detection cycles" claim posted by Julia? I'm asking you because she is offline since a month, so. – LppEdd Mar 20 '19 at 09:07
  • Found! `ApplicationRef#tick` documentation ;) Anyway, you wrote "manually trigger change detection in the components that receive the orderForm". But what does this mean in technical terms? – LppEdd Mar 20 '19 at 09:10
  • 1
    This is a good article on how change detection works: https://blog.angular-university.io/how-does-angular-2-change-detection-really-work/ @LppEdd Also this: https://blog.angularindepth.com/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error-e3fd9ce7dbb4 – AT82 Mar 20 '19 at 09:32
1

You are doing it correctly. Angular in dev mode perform two change detection cycles. One is normal and the second is to check that all called function first time and second time returns the same result. check that all your @Input or function called from template to render values do not mutate values.

For example this component will throw this exception because long() function always return different values.

@Component({
  selector: 'my-app',
  template: `
      {{long()}}
  `,
})
export class App {
  name:string;
  i=0;

  constructor() {
    this.name = `Angular! v${VERSION.full}`
  }

  long(){
    return this.i++;
  }
}
Julia Passynkova
  • 17,256
  • 6
  • 33
  • 32