¿Por qué necesitamos `ngDoCheck`


Parece que no puedo entender por qué necesito ngDoCheck gancho de ciclo de vida que no sea para una simple notificación, particularmente cómo escribir código dentro de él hace una diferencia con respecto a la detección de cambios. La mayoría de los ejemplos que he encontrado muestran ejemplos inútiles, como este, con un montón de funcionalidad de registro.

Además, en las clases generadas no veo que se use para otra cosa que no sea simple notificación:

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;
    };
Author: Max Wizard K, 2017-03-07

3 answers

Este gran artículo Si cree que ngDoCheck significa que su componente está siendo revisado, lea este artículo explica el error en profundidad.

El contenido de esta respuesta se basa en la versión angular 2.x. x. Para la versión más reciente 4.x. x ver este post.

No hay nada en Internet sobre el funcionamiento interno de la detección de cambios, así que tuve que pasar aproximadamente una semana depurando fuentes, por lo que esta respuesta será bastante técnica en detalles.

An la aplicación angular es un árbol de vistas (AppView clase extendida por la clase específica del componente generada por el compilador). Cada vista tiene un modo de detección de cambios que vive en la propiedad cdMode. El valor predeterminado para cdMode es ChangeDetectorStatus.CheckAlways, que es cdMode = 2.

Cuando se ejecuta un ciclo de detección de cambios, cada vista principal comprueba si debe realizar la detección de cambios en la vista secundaria aquí :

  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

Donde this apunta a la vista child. Así que si cdMode es ChangeDetectorStatus.Checked=1, la detección de cambio se omite para el hijo inmediato y todos sus descendientes debido a esta línea.

if (this.cdMode === ChangeDetectorStatus.Checked ||
        this.cdMode === ChangeDetectorStatus.Errored)
      return;

Lo que hace changeDetection: ChangeDetectionStrategy.OnPush es simplemente establecer cdMode a ChangeDetectorStatus.CheckOnce = 0, por lo que después de la primera ejecución de detección de cambios, la vista hija tendrá su cdMode establecido en ChangeDetectorStatus.Checked = 1 debido a este código :

if (this.cdMode === ChangeDetectorStatus.CheckOnce) 
     this.cdMode = ChangeDetectorStatus.Checked;

Lo que significa que la próxima vez que se inicie un ciclo de detección de cambios no se realizará ninguna detección de cambios para la vista secundaria.

Hay pocas opciones de cómo ejecutar detección de cambio para dicha vista. Primero es cambiar la vista secundaria cdMode a ChangeDetectorStatus.CheckOnce, lo que se puede hacer usando this._changeRef.markForCheck() en ngDoCheck gancho de ciclo de vida:

  constructor(private _changeRef: ChangeDetectorRef) {   }

  ngDoCheck() {
    this._changeRef.markForCheck();
  }

Esto simplemente cambia cdMode de la vista actual y sus padres a ChangeDetectorStatus.CheckOnce, por lo que la próxima vez que se realice la detección de cambios, se comprobará la vista actual.

Mira un ejemplo completo aquí en las fuentes , pero aquí está la esencia:

      constructor(ref: ChangeDetectorRef) {
        setInterval(() => {
          this.numberOfTicks ++
          // the following is required, otherwise the view will not be updated
          this.ref.markForCheck();
          ^^^^^^^^^^^^^^^^^^^^^^^^
        }, 1000);
      }

La segunda opción es llamar a detectChanges en la propia vista que se ejecutará cambie la detección en la vista actual si cdMode no es ChangeDetectorStatus.Checked o ChangeDetectorStatus.Errored. Dado que con onPush angular establece cdMode a ChangeDetectorStatus.CheckOnce, angular ejecutará la detección de cambios.

Así que ngDoCheck no anula la detección cambiada, simplemente se llama en cada ciclo de detección cambiado y su único trabajo es establecer la vista actual cdMode como checkOnce, de modo que durante el siguiente ciclo de detección de cambios se verifique los cambios. Ver esta respuesta para más detalles. Si el modo de detección de cambio de la vista actual es checkAlways (establecido por defecto si no se usa la estrategia OnPush), ngDocCheck parece no ser de utilidad.

 44
Author: Max Wizard K,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-09-09 10:48:03

La interfaz DoCheck se utiliza para detectar manualmente los cambios que la detección de cambio angular ha pasado por alto. Un uso podría ser cuando cambias el ChangeDetectionStrategy de tu componente, pero sabes que una propiedad de un objeto cambiará.

Es más eficiente verificar este cambio que dejar que el changeDetector se ejecute a través de todo el componente

let obj = {
  iChange: 'hiii'
}

Si utiliza obj.iChange dentro de su plantilla, angular no lo detectará si este valor cambia, porque el la referencia de obj no cambia. Necesitas implementar un ngDoCheck para comprobar si el valor ha cambiado, y llamar a un detectChanges en el changeDetector de tu componente.

De la documentación de angular acerca de DoCheck

Mientras que el gancho ngDoCheck puede detectar cuando el nombre del héroe ha cambiado, tiene un costo espantoso. Este gancho se llama con una frecuencia enorme, después de cada ciclo de detección de cambios, sin importar dónde se produjo el cambio. Se llama más de veinte veces en este ejemplo antes de que el usuario pueda hacer nada.

La mayoría de estas comprobaciones iniciales se activan por la primera representación de Angular de datos no relacionados en otra parte de la página. El mero mousing en otro cuadro de entrada desencadena una llamada. Relativamente pocas llamadas revelan cambios reales en los datos pertinentes. Claramente nuestra implementación debe ser muy ligera o la experiencia del usuario se verá afectada.

Ejemplo probado

@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();
       }
    }

}
 7
Author: PierreDuc,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-07 09:20:21

/ / se requiere lo siguiente, de lo contrario la vista no se actualizará

Esto.ref.markForCheck(); ^^^^^^^^^^^^^^^^^^^^^^^^

Hola, Maxim @AngularInDepth.com View se actualiza sin llamar a esto.ref.markForCheck () . He probado en consturctor y ngOnInit. Comprueba esto

 0
Author: Yerkon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-03 05:12:33