Ответ 1
Эта замечательная статья Если вы считаете, что ngDoCheck
означает, что ваш компонент проверен - прочитайте эту статью, объясняет глубину ошибки.
Содержимое этого ответа основано на версии angular версии 2.x.x. Для последней версии 4.x.x см. этот пост.
В Интернете нет ничего о внутренней работе по обнаружению изменений, поэтому мне пришлось потратить около недели на отладочные источники, поэтому этот ответ будет довольно подробным в деталях.
Приложение angular представляет собой дерево views (AppView
класс, который расширяется специальным классом компонента, сгенерированным компилятором), Каждый вид имеет режим обнаружения изменений, который живет в свойстве cdMode
. Значение по умолчанию для cdMode
равно ChangeDetectorStatus.CheckAlways
, что равно cdMode = 2
.
Когда цикл обнаружения изменений выполняется, каждое родительское представление проверяет, должно ли оно выполнять обнаружение изменений в дочернем представлении здесь:
detectChanges(throwOnChange: boolean): void {
const s = _scope_check(this.clazz);
if (this.cdMode === ChangeDetectorStatus.Checked ||
this.cdMode === ChangeDetectorStatus.Errored)
return;
if (this.cdMode === ChangeDetectorStatus.Destroyed) {
this.throwDestroyedError('detectChanges');
}
this.detectChangesInternal(throwOnChange); <---- performs CD on child view
где this
указывает на представление child
. Поэтому, если cdMode
- ChangeDetectorStatus.Checked=1
, обнаружение изменения пропускается для непосредственного дочернего элемента и всех его потомков из-за этой строки.
if (this.cdMode === ChangeDetectorStatus.Checked ||
this.cdMode === ChangeDetectorStatus.Errored)
return;
То, что changeDetection: ChangeDetectionStrategy.OnPush
делает, просто устанавливает cdMode
в ChangeDetectorStatus.CheckOnce = 0
, поэтому после первого запуска обнаружения изменений дочерний вид будет иметь свой cdMode
, установленный в ChangeDetectorStatus.Checked = 1
из-за этот код:
if (this.cdMode === ChangeDetectorStatus.CheckOnce)
this.cdMode = ChangeDetectorStatus.Checked;
Это означает, что в следующий раз, когда начнется цикл обнаружения изменений, не будет обнаружено изменений для дочернего вида.
Существует несколько вариантов запуска обнаружения изменений для такого вида. Сначала нужно изменить дочерний вид cdMode
на ChangeDetectorStatus.CheckOnce
, что можно сделать с помощью this._changeRef.markForCheck()
в ngDoCheck
hook:
constructor(private _changeRef: ChangeDetectorRef) { }
ngDoCheck() {
this._changeRef.markForCheck();
}
Это просто меняет cdMode
текущего представления и его родителей на ChangeDetectorStatus.CheckOnce
, поэтому в следующий раз при обнаружении изменения выполняется проверка текущего вида.
Посмотрите полный пример здесь, в источниках, но вот его суть:
constructor(ref: ChangeDetectorRef) {
setInterval(() => {
this.numberOfTicks ++
// the following is required, otherwise the view will not be updated
this.ref.markForCheck();
^^^^^^^^^^^^^^^^^^^^^^^^
}, 1000);
}
Второй вариант - это вызов detectChanges
в самом представлении, который запустит обнаружение изменений в текущем представлении, если cdMode
не является ChangeDetectorStatus.Checked
или ChangeDetectorStatus.Errored
. Поскольку с onPush
angular устанавливает cdMode
в ChangeDetectorStatus.CheckOnce
, angular будет запускать обнаружение изменений.
Итак, ngDoCheck
не переопределяет измененное обнаружение, оно просто вызывает каждый измененный цикл обнаружения, и только задание - устанавливать текущий вид cdMode
как checkOnce
, так что в течение следующего цикла обнаружения изменений он проверял перемены. Подробнее см. этот ответ. Если текущий режим обнаружения изменения изображения checkAlways
(устанавливается по умолчанию, если стратегия onPush не используется), ngDocCheck
представляется бесполезным.