Зачем нам нужен `ngDoCheck`

Я не могу понять, почему мне нужен ngDoCheck крючок жизненного цикла, отличный от простого уведомления, в частности, как писать код внутри него делает разницу в отношении обнаружения изменений. Большинство примеров, которые я нашел, показывают бесполезные примеры, такие как этот с множеством функций ведения журнала.

Кроме того, в сгенерированных классах я не вижу, что он используется для чего-то другого, кроме простого уведомления:

conmponent/wrapper.ngfactory.js

Wrapper_AppComponent.prototype.ngDoCheck = function(view,el,throwOnChange) {
  var self = this;
  var changed = self._changed;
  self._changed = false;
  if (!throwOnChange) {
    if (changed) {
      jit_setBindingDebugInfoForChanges1(view.renderer,el,self._changes);
      self._changes = {};
    }
    self.context.ngDoCheck(); <----------- this calls ngDoCheck on the component
                                           but the result is not used 
                                           anywhere and no params are passed
      }
      return changed;
    };

Ответы

Ответ 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 представляется бесполезным.

Ответ 2

Интерфейс DoCheck используется для обнаружения изменений, которые вручную игнорировали обнаружение изменений angular. Использование может быть при изменении ChangeDetectionStrategy вашего компонента, но вы знаете, что одно свойство объекта изменится.

Более эффективно проверять это одно изменение, чем позволить changeDetector выполнять весь ваш компонент

let obj = {
  iChange: 'hiii'
}

Если вы используете obj.iChange внутри вашего шаблона, angular не обнаружит его, если это значение изменится, потому что сама ссылка obj не изменяется. Вам нужно реализовать ngDoCheck, чтобы проверить, изменилось ли значение, и вызвать detectChanges в вашем файле changeDetector.

Из документации angular о DoCheck

В то время как крюк ngDoCheck может обнаружить, когда имя героя изменилось, у него есть ужасная стоимость. Этот крюк называется с огромной частотой - после каждого цикла обнаружения изменений независимо от того, где произошло изменение. В этом примере он вызывал более двадцати раз, прежде чем пользователь сможет что-либо сделать.

Большинство из этих начальных проверок запускается angular первым отображением несвязанных данных в другом месте на странице. Простое мытье в другое поле ввода вызывает вызов. Относительно мало вызовов выявляются фактические изменения соответствующих данных. Очевидно, что наша реализация должна быть очень легкой или пользовательский опыт будет страдать.

пример

@Component({
   selector: 'test-do-check',
   template: `
      <div [innerHtml]="obj.changer"></div>
   `, 
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class TestDoCheckComponent implements DoCheck, OnInit {

    public obj: any = {
       changer: 1
    };

    private _oldValue: number = 1;

    constructor(private _changeRef: ChangeDetectorRef){}

    ngOnInit() {
       setInterval(() => {
           this.obj.changer += 1;
       }, 1000);
    }

    ngDoCheck() {
       if(this._oldValue !== this.obj.changer) {
           this._oldValue = this.obj.changer;

           //disable this line to see the counter not moving
           this._changeRef.detectChanges();
       }
    }

}