Угловые запуски два цикла обнаружения изменений в начале приложения.
Другими словами, он вызывает Application.tick()
метод два раза
1) После начальной загрузки mainкомпонент (https://github.com/angular/angular/blob/aaaa34021c2d56f798d20e5a1f31b23972055170/packages/core/src/application_ref.ts#L539-L541)
private _loadComponent(componentRef: ComponentRef<any>): void {
this.attachView(componentRef.hostView);
this.tick();
2) И при первом повороте виртуальной машины (когда в zonejs нет микрозадачи) (https://github.com/angular/angular/blob/aaaa34021c2d56f798d20e5a1f31b23972055170/packages/core/src/application_ref.ts#L385-L386)
this._zone.onMicrotaskEmpty.subscribe(
{next: () => { this._zone.run(() => { this.tick(); }); }});
Учитывая это, давайтевернитесь к нашему Application.tick () методу, который запускает обнаружение изменений в дереве представлений (представления компонентов или встроенные представления).
tick(): void {
...
try {
...
this._views.forEach((view) => view.detectChanges());
if (this._enforceNoNewChanges) {
this._views.forEach((view) => view.checkNoChanges());
}
} catch (e) {
...
} finally {
...
}
}
Что мы можем заметить здесь?
Мы можем заметить, что в режиме разработки (потому что this._enforceNoNewChanges = isDevMode();
https://github.com/angular/angular/blob/aaaa34021c2d56f798d20e5a1f31b23972055170/packages/core/src/application_ref.ts#L383) Угловые прогоны изменяют цикл обнаружения дважды .
Еще один момент здесьзаключается в том, что метод tick
выполняется в блоке try catch
.
Итак, что мы имеем до сих пор?
2 сd cycles * 2 view.detectChanges() on the tree = 4
Также на каждом view.detectChanges()
Angular проверяет, имеют ли привязки шаблонаизменено или нет. Для этого Angular выполняет каждое выражение в шаблоне (в результате ваш метод getServerStatus()
будетвыполняется при каждом обходе дерева).Если во время второго метода cd withih tick произошли некоторые изменения в привязке, то Angular выдает ошибку Expression has changed after it was checked
.Как вы можете догадаться, это не остановит последующие циклы CD, спасибо try catch
block.
Ради простоты, скажем, у вас есть следующий шаблон:
{{ getServerStatus() }}
Так что же происходитздесь?
Start app serverStatus
loadComponent => tick
|
|__ view.detectChanges()
||
\/
call getServerStatus() 'offlineoffline'
|__ view.checkNoChanges()
||
\/
call getServerStatus() 'offlineofflineofflineoffline'
'offlineoffline' !== 'offlineofflineofflineoffline'
||
\/
ExpressionChangedAfterItHasBeenCheckedError (template is not updated!!)
onMicrotaskEmpty => tick
|
|__ view.detectChanges()
||
\/
call getServerStatus() 'offline'.repeat(8)
|__ view.checkNoChanges()
||
\/
call getServerStatus() 'offline'.repeat(16)
'offline'.repeat(8) !== 'offline'.repeat(16)
||
\/
ExpressionChangedAfterItHasBeenCheckedError (template is not updated!!)
В результате вы получите ровно 8 повторений serverStatus