Угловой шаблон общения на один тик задерживается
Я играл с ng-template и столкнулся с какой-то задержкой.
Я создал один простой пример в stackblitz. https://stackblitz.com/edit/angular-template-series-v?file=app%2Fparent%2Fparent1.component.ts
Компоненты Parent отправляют компонент Hello своему дочернему элементу с помощью шаблонов.
Идея состояла в том, чтобы управлять шаблоном от Child с помощью ngIf.
Когда компонент Hello достигает цикла подключения ngOnInit, он генерирует вывод. Родительский компонент получает этот вывод и добавляет сообщение для отображения.
Но сообщение не отображается при создании компонента, а один цикл позже. В этом примере вам нужно дважды нажать кнопку "Переключить содержимое", чтобы появилось сообщение "Шаблон создан".
Как я могу решить эту проблему, чтобы сообщение появилось в том же цикле?
3 ответа
Это связано с тем, как обнаружение изменений работает в Angular. Это не связано с использованием ng-template
, как показано в этом модифицированном stackblitz (используя grand child без использования ng-шаблона).
2 вещи знать об обнаружении изменений:
- он запускается после событий (щелчок, отправка,..), запросов xhr или таймеров.
- это происходит сверху вниз дерева компонентов
В основном, когда вы нажимаете кнопку переключения:
- обработчик срабатывает (переключение
showContent
Ценность в вашем примере, но больше ничего.hello
компонент НЕ создан на данном этапе) - изменение обнаружения срабатывает, всегда начиная сверху вниз.
- Angular начинается с проверки необходимости обновления представления для родительского компонента. Поскольку ничего не изменилось, родительское представление не обновляется.
- Затем дочерний компонент обнаруживает, что его
showContentValue
изменился Это вызывает созданиеhello
составная часть. - После инициализации
hello
компонент,created
событие испускается. Родительский компонент получил это событие и соответствующим образом обновляет массив сообщений. Но на этом этапе содержимое родительского элемента уже проверено, поэтому Angular не будет вносить изменений в родительское представление.
При повторном нажатии кнопки переключения снова запускается тот же цикл, и на этот раз отображается сообщение, помещенное в массив в предыдущем цикле.
Вот модифицированный пример stackblitz с журналами для ловушек жизненного цикла
Заметка
Принудительное повторное обнаружение изменений решит вашу проблему здесь, но если вы не общаетесь напрямую от дочернего элемента к родительскому, вам лучше использовать общую службу (на основе наблюдаемых) для передачи сообщений между несвязанными компонентами.
Это потому, что вы нарушаете цикл обнаружения угловых изменений.
Вот что происходит после нажатия на первую кнопку переключения:
1- Вы нажимаете на переключатель
2- ZoneJS, который имеет хуки в событиях щелчка, получает уведомление (проще говоря)
3- Зона гарантирует, что событие нажатия завершено
4- Зона даст Angular знать, что async
событие произошло, мы должны проверить компонент и убедиться, что представление ( DOM) отражает модель (модель / мир javascript). Так change detection
будет запущен, и он обнаружит все изменения и обнаружит, что в шаблоне есть привязки (ng-if
).
5- После ngIf
становится правдой, и обратите внимание, что это все, так далеко, ng-container
создается и ваш templateOutlet
создается и ваш hello
Компонент будет предоставлен.
ПРИМЕЧАНИЕ СНОВА: это все синхронизация.
6 - Вид обновлен, обнаружение изменений теперь снято с крючка.
7 - Внутри вашего hello
Компонент вы запускаете другое событие, которое является created
и это уведомляет родительский компонент, и внутри вашего родительского компонента вы изменяете массив, и Angular не заботится об этом, потому что не было асинхронного события, и вы не обновили ни один из входных данных, вы просто запустили другой Синхронизировать событие после того, как обнаружение изменений закончило его работу.
8- Массив теперь обновлен, но представление нет (в родительском компоненте)
9 - Вы нажимаете снова, происходит то же самое, и Angular обновляет родительское представление при первом запуске обнаружения изменений, и вы видите, что массив отражается в представлении.
Доказать, что я прав:D:
onTemplateCreated() {
console.log('Got the event from chil');
this.template.push('Template created');
console.log('this.template',this.template);
}
Вы увидите, что после щелчка консоль показывает правильный массив, но представление не появляется, пока вы не нажмете снова.
Теперь давайте перейдем к моему коду
onTemplateCreated() {
console.log('Got the event from chil');
this.template = [...this.template,'Template created'];
}
ПОТОМУ ЧТО я не мутирую, а референция обновляется, Angular теперь должен позаботиться об этом изменении, НО еще есть проблема, детектор изменений Angular был завершен, и вы обновили модель, не выполняя никаких async
событие, поэтому у нас проблемы, потому что Angular не нравится, когда модель будет обновлена после того, как обнаружение изменений будет завершено,
Таким образом, вы получите сообщение об ошибке:
ExpressionChangedAfterItHasBeenCheckedError: Выражение изменилось после его проверки. Предыдущее значение
Чтобы это исправить, сначала прочтите другой пост, чтобы понять его, во-вторых, вам нужно сообщить Angular, что вы знаете, что делаете, и запустить обнаружение изменений вручную:
this._cd.detectChanges
ИЛИ, вы можете сделать это асинхронно:
onTemplateCreated() {
console.log('Got the event from chil');
setTimeout(()=>{
this.template = [...this.template,'Template created'];
})
}
Если вы запустите changeDetector в parent- он обновит страницу с первого клика.
Вот пример: stackblitz