15

Every EventEmiiter in my child module gives this error and I can't find a fix for this.

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

This is what triggers my EventEmitter:

ngOnChanges(changes: any) {
    if (changes.groupingTabValid) {
        if (changes.groupingTabValid.currentValue !== changes.groupingTabValid.previousValue) {
            this.groupingTabValidChange.emit(this.groupingTabValid);
        }
    }
}

Here is my "main" componenent's HTML

<year-overview-grouping [definitionDetails]="definitionDetails"
                        [fixedData]="fixedData"
                        [showValidation]="showValidation"
                        [groupingTabValid]="groupingTabValid"
                        (groupingTabValidChange)="setValidators('groupingTab', $event)">

</year-overview-grouping>

Which calls this function

public setValidators(validator: string, e: boolean) {
    switch (validator) {
        case "groupingTab":             
            this.groupingTabValid = e;
            break;

        case "selectionTab":
            this.selectionTabValid = e;
            break;
    }

    if (this.groupingTabValid && this.selectionTabValid) {
        this.valid = true;
    } else {
        this.valid = false;
    }
}

1) In a a simple explanation, what's causing this error?

2) What steps can I take to solve this?

Frederik Struck-Schøning
  • 12,981
  • 8
  • 59
  • 68
Nicolas
  • 4,526
  • 17
  • 50
  • 87
  • 3
    check this out http://stackoverflow.com/questions/41364386/whats-the-difference-between-markforcheck-and-detectchanges/41364469#41364469 – Milad May 19 '17 at 13:00
  • that's an interesting case, can you put up a plunker maybe? you're updating parents properties after they were checked and this is what causing the trouble - [read more here](https://hackernoon.com/angulars-digest-is-reborn-in-the-newer-version-of-angular-718a961ebd3e#d14a), but it shouldn't be the problem if you're doing it in the `onChanges` hook. – Max Koretskyi May 19 '17 at 13:38
  • @Maximus here's a plunker with that shows the problem: http://plnkr.co/edit/cdh9ryNFqOnmnHaN8rl1 With the console open, try changing age to 18. In this particular case the `*ngIf` expression is causing the problem in the parent template – Christian Jun 06 '17 at 07:07
  • Please read the [help centre advice on tagging](https://stackoverflow.com/help/tagging). Pretty much all your questions have tags inserted in their titles, which they shouldn't *For example, rather than writing, "JavaScript, jQuery: When should I use one or the other?" – which forces tags into the title – you can convey the same information in a conversational tone "Can I use jQuery to foo the bar on the baz, or am I stuck using plain JavaScript?"* – Liam Jun 16 '17 at 13:01
  • [Everything you need to know about the `ExpressionChangedAfterItHasBeenCheckedError` error](https://medium.com/@maximus.koretskyi/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error-e3fd9ce7dbb4) explains this behavior in great details – Max Koretskyi Jul 02 '17 at 05:24

4 Answers4

11

Abide by the unidirectional data flow rule

Try to postpone the call to emit for one tick with a setTimeout

if (changes.groupingTabValid.currentValue !== changes.groupingTabValid.previousValue) {
    setTimeout(() => this.groupingTabValidChange.emit(this.groupingTabValid), 0)
}
Draeken
  • 380
  • 3
  • 7
4

Just import ChangeDetectionStrategy and then add this to your component

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  selector: 'app-bla-bla-bla',
  templateUrl: './xxxxx'
})
Cengkuru Michael
  • 4,590
  • 1
  • 33
  • 33
2

You can avoid using setTimeout and tell Angular that changes are about to happen after initial check.

You can inject ChangeDetectorRef into your component and use its markForCheck method.

/* ... */

constructor(private cdr: ChangeDetectorRef) {}

/* ... */

if (changes.groupingTabValid.currentValue !== changes.groupingTabValid.previousValue {
  this.cdr.markForCheck();
  this.groupingTabValidChange.emit(this.groupingTabValid);
}
Michał Pietraszko
  • 5,666
  • 3
  • 21
  • 27
1

I'm just starting to look at this exception type myself, so no expert, but presume that it's there to stop feedback loops.

I can't see any obvious reason why you want to send the value down to the child and back up to the parent, but presume the child applies some additional logic. To me, seems better to apply any additional logic in a service rather than a child component.

I realize the plunker code doesn't have a loop, but the Angular mechanism seems rather simple - just re-checks values at the end of the cycle. Note, groupingTabValid in your question code does feedback directly.

Looking at the plunker code as given, structurally the change to age can be handled on the parent. The click event would be (click)=changeAge(age.value) and the method (on parent) to handle it

changeAge(inputAge) {
  this.newAge = inputAge;
  this.person.age = inputAge;
}

Here's a fork of the plunker. Modified plunker
The child component still updates person.age, but no longer causes an error as the value is the same as set on the parent.

Richard Matsen
  • 20,671
  • 3
  • 43
  • 77