ExpressionChangedAfterItHasBeenCheckedError в двусторонней привязке Angular

Вот пример:

@Component({
  selector: 'my-app',
  template: `
    <div>
      <h1>{{ foo }}</h1>    
      <bpp [(foo)]="foo"></bpp>
    </div>
  `,
})
export class App {
  foo;
}

@Component({
  selector: 'bpp',
  template: `
    <div>
      <h2>{{ foo }}</h2>
    </div>
  `,
})
export class Bpp {
  @Input('foo') foo;

  @Output('fooChange') fooChange = new EventEmitter();

  ngAfterViewInit() {
    const potentiallyButNotNecessarilyAsyncObservable = Observable.of(null);

    potentiallyButNotNecessarilyAsyncObservable.subscribe(() => {
      this.fooChange.emit('foo');
    })
  }
}

где иногда появляется ошибка:

ExpressionChangedAfterItHasBeenCheckedError: выражение изменилось после проверки. Предыдущее значение: «не определено». Текущее значение: 'foo'

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

Что можно сделать, чтобы избежать этой ошибки здесь?

Имеет ли ошибка ExpressionChangedAfterItHasBeenCheckedError вредные последствия или ее можно игнорировать? Если может, может ли датчик смены на нем молчать и не загрязнять консоль?


person Estus Flask    schedule 06.09.2017    source источник
comment
Я никогда не получаю эту ошибку, но вы пытались использовать асинхронный канал? может быть, это помогает.   -  person fastAsTortoise    schedule 06.09.2017
comment
@fastAsTortoise Труба здесь не применима. Ошибка появится независимо от шаблона.   -  person Estus Flask    schedule 06.09.2017


Ответы (1)


Давайте сначала развернем двустороннюю привязку данных, чтобы упростить объяснение:

<div>
  <h1>{{ foo }}</h1>    
  <bpp [foo]="foo" (fooChange)="foo=$event"></bpp>
</div>

Он по-прежнему имеет тот же эффект и иногда выдает ошибку. Ошибка будет выдаваться только в том случае, если potentiallyButNotNecessarilyAsyncObservable является синхронным. Таким образом, мы также можем заменить это:

ngAfterViewInit() {
    const potentiallyButNotNecessarilyAsyncObservable = Observable.of(null);

    potentiallyButNotNecessarilyAsyncObservable.subscribe(() => {
      this.fooChange.emit('foo');
    })

с этим:

ngAfterViewInit() {
    this.fooChange.emit('foo');

Этот случай относится к категории ошибок Synchronous event broadcasting, которая описана в статье Все, что вам нужно знать об ошибке ExpressionChangedAfterItHasBeenCheckedError.

Хук жизненного цикла ngAfterViewInit срабатывает после обработки изменений родительского компонента. Порядок хуков, связанных с дочерними компонентами, объясняется в отн. ="noreferrer">Все, что вам нужно знать об обнаружении изменений в Angular. Теперь Angular помнит, что при запуске обнаружения изменений для компонента App значение foo было undefined, но на этапе проверки значение foo обновляется дочерним компонентом Bpp. Следовательно, он выдает ошибку.

Что можно сделать, чтобы избежать этой ошибки здесь?

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

Имеет ли ошибка ExpressionChangedAfterItHasBeenCheckedError вредные последствия или ее можно игнорировать?

Плохой эффект заключается в том, что у вас будет несогласованное состояние в приложении App.foo==='foo' и представлении {{foo}}===undefined до следующего цикл дайджеста итерации. Ошибка не может быть отключена в режиме разработки, но она не появится в режиме производства.

Две фазы Angular-приложений также довольно хорошо объясняет ментальное модель этой ошибки.

person Max Koretskyi    schedule 07.09.2017
comment
У меня похожая проблема (которую я исправил с помощью detectChanges). И я слишком испускаю выход после подписки на Observable. Но как эта эмиссия синхронна, если она выполняется после подписки на Observable? - person Bernardo; 09.08.2018
comment
@ Бернардо, мне нужно увидеть демо и код stackblitz, чтобы ответить на ваш вопрос - person Max Koretskyi; 12.08.2018