Angular 4 ExpressionChangedAfterItHasBeenCheckedError

в ParentComponent =>

ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: ''. Current value: '[object Object]'.
            at viewDebugError (vendor.bundle.js:8962)
            at expressionChangedAfterItHasBeenCheckedError (vendor.bundle.js:8940)

Родительский компонент Html

<div>
  <app-child-widget [allItems]="allItems" (notify)="eventCalled($event)"></app-child-widget>
<div>

Родительский компонент

export class ParentComponent implements OnInit {

  returnedItems: Array<any> = [];
  allItems: Array<any> = [];

  constructor(
  ) { }

  ngOnInit() {
     this.allItems = // load from server...
  }

  eventCalled(items: Array<any>): void {
    this.returnedItems = items;
  }
}

Дочерний компонент

@Component({
  selector: 'app-child-widget',
  templateUrl: 'child.component.html',
  styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit {
  @Output() notify: EventEmitter<any> = new EventEmitter();
  @Input() private allItems: Array<any>;

  constructor() { }

  ngOnInit() {
    doSomething();
  }

  doSomething() {
    this.notify.emit(allItems);
  }
}

person Saurabh Kumar    schedule 22.06.2017    source источник


Ответы (3)


Статья Все, что вам нужно знать об ошибке ExpressionChangedAfterItHasBeenCheckedError подробно объясняет это поведение

Причина

Ваша проблема очень похожа на эту, но вместо обновления родительского свойства через службу вы обновляете его через синхронную трансляцию событий. . Вот цитата из связанного ответа:

Во время цикла дайджеста Angular выполняет определенные операции с дочерними директивами. Одна из таких операций - обновление входных данных и вызов перехватчика жизненного цикла ngOnInit для дочерних директив / компонентов. Важно то, что эти операции выполняются в строгом порядке:

  • Обновить входы
  • Вызов ngOnInit

Итак, в вашем случае Angular обновил привязку ввода allItems для дочернего компонента, а затем вызвал onInit для дочернего компонента, что вызвало обновление до allItems родительского компонента. Теперь у вас есть несогласованность данных. Родительский компонент имеет одно значение, а дочерний - другое. Если Angular продолжит синхронизацию изменений, вы получите бесконечный цикл. Вот почему во время следующего цикла обнаружения изменений Angular обнаружил, что allItems был изменен, и выдал ошибку.

Решение

Похоже, что это недостаток дизайна приложения, поскольку вы обновляете details как из родительского, так и из дочернего компонентов. Если это не так, то вы можете решить проблему, послав событие асинхронно следующим образом:

export class ChildComponent implements OnInit {
  @Output() notify: EventEmitter<any> = new EventEmitter(true);
                                                        ^^^^^^-------------

Но нужно быть очень осторожным. Если вы используете любой другой хук, например ngAfterViewChecked, который вызывается в каждом цикле дайджеста, вы попадете в циклическую зависимость!

person Max Koretskyi    schedule 22.06.2017
comment
будет ли проблема решена асинхронной трансляцией событий? Есть ли способ асинхронной трансляции событий? - person Saurabh Kumar; 22.06.2017
comment
просто используйте setTimeout, я обновил вопрос. Но на самом деле проблема в том, что вы обновляете все элементы внутри родительского и дочернего элементов, это недостаток дизайна вашего приложения, подумайте, как вы можете его переделать. - person Max Koretskyi; 22.06.2017
comment
@yurzui, только что проверил, он использует setTimeout под капюшон. Он запланирует макрозадачу. Мне интересно, есть ли способ запланировать микрозадачу. Я пробовал использовать promise.resolve, поскольку он планирует микрозадачу, но ошибка не исчезла, требуется исследование - person Max Koretskyi; 22.06.2017

В моем случае я менял состояние своих данных - этот ответ требует, чтобы вы прочитали объяснение цикла дайджеста AngularInDepth.com - внутри уровня html все, что мне нужно было сделать, это изменить способ обработки таких данных :

<div>{{event.subjects.pop()}}</div>

в

<div>{{event.subjects[0]}}</div>

Резюме: вместо всплывающего окна, которое возвращает и удаляет последний элемент массива, изменяя, таким образом, состояние моих данных, я использовал обычный способ получения своих данных без изменения их состояния, таким образом предотвращая исключение. .

person Miko Chu    schedule 05.02.2018
comment
Эти два понятия не эквивалентны. Один возвращает последнее, а другой - первое. Но дело было принято. - person Kevin Beal; 06.11.2018

При получении этой ошибки вы можете использовать rxjs timer, чтобы отложить вызов на короткое время.

В этом примере используется @angular/material stepper для загрузки шага по умолчанию из параметров запроса в URL-адресе.

import { timer } from 'rxjs'

@Component({
  template: `
    <mat-horizontal-stepper #stepper>
      <mat-step></mat-step>
      <mat-step></mat-step>
      <mat-step></mat-step>
    </mat-horizontal-stepper>
  `
})
export class MyComponent {
  @ViewChild('stepper', { static: true })
  private stepper: MatHorizontalStepper

  public ngOnInit() {
    timer(10).subscribe(() => {
      this.activatedRoute.queryParams.subscribe(params => {
        this.stepper.selectedIndex = parseInt(params.step || 0)
      })
    })
  }
}
person Get Off My Lawn    schedule 03.12.2020