ExpressionChangedAfterItHasBeenCheckedError , без сомнения, моя любимая ошибка в Angular приложениях.

Я часто сталкивался с этой ошибкой, когда только начинал работать с Angular. Согласно GitHub, то же самое делают многие другие.

Когда будет выброшена ошибка ExpressionChangedAfterItHasBeenCheckedError?

Наиболее частые причины:

  1. Вы выполняете код в AfterViewInit , что часто случается при работе с ViewChild ,, поскольку он не определен до вызова AfterViewInit .
  2. Вы напрямую манипулируете DOM (например, используя jQuery). Angular не всегда может обнаружить эти изменения и правильно отреагировать.
  3. Это также может произойти из-за состояния гонки при вызове функций в шаблоне HTML.

О чем ExpressionChangedAfterItHasBeenCheckedError пытается меня предупредить?

ExpressionChangedAfterItHasBeenCheckedError выдается, когда выражение в вашем HTML изменилось после того, как Angular проверил его (это очень выразительная ошибка).

Эта ошибка возникает только в режиме разработки и не зря; это часто является признаком того, что вам следует реорганизовать свой код, поскольку Angular предупреждает вас, что это изменение в вашем выражении не будет учтено при включении производственного режима!

Одна из причин, по которой рабочий режим быстрее, чем режим разработки, заключается в том, что Angular пропускает некоторые проверки (например, обнаружение изменений после AfterViewInit ), которые выполняются в режиме разработки. Это означает, что код, который будет нормально работать в режиме разработки, не будет работать в производственном режиме.

Вот пример, который отлично работает в режиме разработки, но не в рабочем режиме:

Другой пример: изменение свойства @Input в ngOnInit на EventEmitter также может вызвать ExpressionChangedAfterItHasBeenCheckedError.

Как исправить ошибку ExpressionChangedAfterItHasBeenCheckedError

Исправьте одно: сообщите Angular, чтобы изменения вступили в силу.

В качестве быстрого исправления часто используются setTimeout или ChangeDetectorRef , чтобы ошибка исчезла.

Последнее лучше, так как в ChangeDetectorRef проверяется представление компонента и его дочерние элементы. С другой стороны, setTimeout заставит Angular проверять все приложение на предмет изменений, что намного дороже.

Исправление два: переместите код из ngAfterViewInit

Иногда ошибку даже проще исправить - просто переместите код на OnInit .

Если вам не нужно полагаться на ViewChild , или если какой-то код должен запускаться только после того, как Angular полностью инициализировал представление компонента, переход к OnInit решит вашу проблему.

Это связано с тем, что Angular выполнит обнаружение изменений после OnInit как в рабочем, так и в режиме разработки.

Исправление третье: используйте обнаружение изменений OnPush

Не нравится Angular magic и как она обнаруживает изменения? Просто отключите автоматическое обнаружение изменений в вашем компоненте и сообщите Angular самостоятельно, когда он должен обнаруживать изменения.

ChangeDetectionStrategy можно указать в декораторе компонентов для OnPush. Теперь Angular будет автоматически обнаруживать только Input изменения, а все остальное зависит от вас.

Хотя включение стратегииOnPush требует меньше магии, вы также получаете повышенную производительность, так как Angular делает меньше работы. Это может быть полезно для оптимизации больших и сложных компонентов.

С другой стороны, вы должны убедиться, что Angular принимает изменения. Предпочтительный способ сделать это - использовать markForCheck. Если вы не уведомите Angular об этих изменениях, вы обычно увидите ошибки или несоответствия пользовательского интерфейса (например, модальное окно не исчезает), если Angular не обнаружит другое изменение.

Так что не применяйте это слишком усердно, не проверив компонент тщательно.

Исправление четвертое: избегайте мутаций свойств @Input

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

Между прочим: если вы не можете этого избежать, вы можете использовать параметр isAsync в EventEmitter для автоматического запуска обнаружения изменений (он вызывает setTimeout внутренне).

Заключение

Теперь вы сможете понять, когда и почему происходит печально известный ExpressionChangedAfterItHasBeenCheckedError .

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

Обновление 2021: Документация Angular теперь содержит список распространенных ошибок, включая эту. Для некоторых ошибок есть даже видео, если вы предпочитаете учиться на видео, чем читать.