Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MatExpansionPanel expression has changed after it was checked error 'mat-expanded: true'

I have an Angular Material Expansion Panel Query list:

  @ViewChildren(MatExpansionPanel)
  matExpansionPanelQueryList: QueryList<MatExpansionPanel>;

and a simple array:

questions: [];

The Expansion Panels are generated with an *ngFor: simplified for example:

   <ng-container *ngFor="let question of questions>
            <mat-expansion-panel
               ...

When I extend the questions array, I want to open up the last expansion panel.

     extendQuestion() {
        this.matExpansionPanelQueryList.changes.subscribe(
          change => {
            change.last.open();
          }
        );

        this.questions.push('some-new-item');
      }

This works just fine - I insert an item to the array,
the ExpansionPanels get's re-rendered, a new Panel gets created and it actually opens up - my problem is that it generates the following error in the console:

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

Is there any way to avoid this?
I have tried using changeDetectorRef and markForCheck() in the subscription, but the error message did not go away (and honestly, I am 100% sure what's the exact problem here).

Update: Stackblitz example of the issue (click the "+" button)

like image 510
ForestG Avatar asked Feb 22 '19 08:02

ForestG


1 Answers

From angularindepth:

This is a cautionary mechanism put in place to prevent inconsistencies between model data and UI so that erroneous or old data are not shown to a user on the page.

A running Angular application is a tree of components. During change detection Angular performs checks for each component which consists of the following operations performed in the specified order:

  • update bound properties for all child components/directives
  • call ngOnInit, OnChanges and ngDoCheck lifecycle hooks on all child components/directives
  • update DOM for the current component
  • run change detection for a child component
  • call ngAfterViewInit lifecycle hook for all child components/directives

After each operation Angular remembers what values it used to perform an operation. They are stored in the oldValues property of the component view.

You might want to trigger the lifecycle hook of your component before the DOM update operation..

That should work for you:

You can add a constructor for changedetection (cd) and call it after change.last.open();

import {ChangeDetectorRef, Component, OnInit, QueryList, ViewChildren} from '@angular/core';

import {MatDialog, MatExpansionPanel} from '@angular/material';

@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})

export class AppComponent {
questions = ['first', 'second'];

constructor(private cd: ChangeDetectorRef) {
    }

@ViewChildren(MatExpansionPanel) matExpansionPanelQueryList: QueryList<MatExpansionPanel>;


extendQuestion() {
        this.matExpansionPanelQueryList.changes.subscribe(
        change => {
            change.last.open();
            this.cd.detectChanges();
        }
        );
        this.questions.push('some-new-item');
    }
}
like image 164
iLuvLogix Avatar answered Nov 15 '22 06:11

iLuvLogix