ExpressionChangedAfterItHasBeenCheckedError
, без сомнения, моя любимая ошибка в Angular приложениях.
Я часто сталкивался с этой ошибкой, когда только начинал работать с Angular. Согласно GitHub, то же самое делают многие другие.
Когда будет выброшена ошибка ExpressionChangedAfterItHasBeenCheckedError?
Наиболее частые причины:
- Вы выполняете код в
AfterViewInit
, что часто случается при работе сViewChild
,, поскольку он не определен до вызоваAfterViewInit
. - Вы напрямую манипулируете DOM (например, используя jQuery). Angular не всегда может обнаружить эти изменения и правильно отреагировать.
- Это также может произойти из-за состояния гонки при вызове функций в шаблоне 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 теперь содержит список распространенных ошибок, включая эту. Для некоторых ошибок есть даже видео, если вы предпочитаете учиться на видео, чем читать.