¿Cuál es la diferencia entre markForCheck () y detectChanges ()


174

¿Cuál es la diferencia entre ChangeDetectorRef.markForCheck()y ChangeDetectorRef.detectChanges()?

Solo encontré información sobre SO en cuanto a la diferencia entre NgZone.run(), pero no entre estas dos funciones.

Para obtener respuestas con solo una referencia al documento, ilustre algunos escenarios prácticos para elegir uno sobre el otro.



@Milad ¿Cómo sabes que lo rechazó? Hay muchas personas que examinan este sitio.
Goodbye StackExchange

2
@FrankerZ, porque estaba escribiendo y vi el voto negativo y un segundo después la pregunta se actualizó diciendo que "Para respuestas con solo una referencia al documento, ¿ilustra algunos escenarios prácticos para elegir uno sobre el otro? Eso ayudará a aclararlo en mi mente".
Milad

3
El voto negativo fue incentivarlo a completar la respuesta original que fue copiada y pegada de los documentos que ya vi. ¡Y funcionó! Ahora la respuesta tiene mucha claridad y es la respuesta aceptada, gracias
Parlamento

3
¡Qué plan tortuoso @parlamento!
HankCa

Respuestas:


234

De documentos:

detectChanges (): void

Comprueba el detector de cambios y sus elementos secundarios.

Es decir, si hay un caso en el que algo dentro de su modelo (su clase) ha cambiado pero no ha reflejado la vista, es posible que deba notificar a Angular para detectar esos cambios (detectar cambios locales) y actualizar la vista.

Los posibles escenarios pueden ser:

1- El detector de cambio se separa de la vista (ver separación )

2- Ha ocurrido una actualización pero no ha estado dentro de la Zona Angular, por lo tanto, Angular no lo sabe.

Como cuando una función de terceros ha actualizado su modelo y desea actualizar la vista después de eso.

 someFunctionThatIsRunByAThirdPartyCode(){
     yourModel.text = "new text";
 }

Debido a que este código está fuera de la zona de Angular (probablemente), lo más probable es que necesite asegurarse de detectar los cambios y actualizar la vista, por lo tanto:

 myFunction(){
   someFunctionThatIsRunByAThirdPartyCode();

   // Let's detect the changes that above function made to the model which Angular is not aware of.
    this.cd.detectChanges();
 }

NOTA :

Hay otras formas de hacer que el trabajo anterior funcione, en otras palabras, hay otras formas de llevar ese cambio al ciclo de cambio angular.

** Podría incluir esa función de terceros dentro de una zona. Ejecutar:

 myFunction(){
   this.zone.run(this.someFunctionThatIsRunByAThirdPartyCode);
 }

** Podrías envolver la función dentro de un setTimeout:

myFunction(){
   setTimeout(this.someFunctionThatIsRunByAThirdPartyCode,0);
 }

3- También hay casos en los que actualiza el modelo una vez change detection cycleque finaliza, en los que obtiene este temido error:

"La expresión ha cambiado después de que se verificó";

Esto generalmente significa (del lenguaje Angular2):

Vi un cambio en su modelo que fue causado por una de mis formas aceptadas (eventos, solicitudes XHR, setTimeout y ...) y luego ejecuté mi detección de cambios para actualizar su vista y terminé, pero luego hubo otra funciona en su código que actualizó el modelo nuevamente y no quiero volver a ejecutar mi detección de cambios porque ya no hay una comprobación sucia como AngularJS: D y ¡deberíamos usar un flujo de datos unidireccional!

Definitivamente te encontrarás con este error: P.

Un par de formas de solucionarlo:

1- Forma correcta : asegúrese de que la actualización esté dentro del ciclo de detección de cambios (las actualizaciones de Angular2 son unidireccionales que ocurren una vez, no actualice el modelo después de eso y mueva su código a un lugar / hora mejor).

2- Manera perezosa : ejecuta detectChanges () después de esa actualización para hacer feliz a angular2, esta definitivamente no es la mejor manera, pero como preguntaste cuáles son los posibles escenarios, esta es una de ellas.

De esta manera estás diciendo: Sinceramente sé que ejecutaste la detección de cambios, pero quiero que lo vuelvas a hacer porque tuve que actualizar algo sobre la marcha después de que terminaste la comprobación.

3- Pon el código dentro de a setTimeout, porque setTimeoutestá parcheado por zona y se ejecutará detectChangesdespués de que haya terminado.


De los documentos

markForCheck() : void

Marca todos los antepasados ​​ChangeDetectionStrategy como para comprobarlos.

Esto se necesita principalmente cuando ChangeDetectionStrategy de su componente es OnPush .

OnPush en sí mismo significa, solo ejecuta la detección de cambios si alguno de estos ha sucedido:

1- Una de las entradas @ del componente se ha reemplazado completamente con un nuevo valor, o simplemente, si la referencia de la propiedad @Input ha cambiado por completo.

Entonces, si ChangeDetectionStrategy de su componente es OnPush y luego tiene:

   var obj = {
     name:'Milad'
   };

Y luego lo actualizas / mutas como:

  obj.name = "a new name";

Esto no actualizará la referencia obj , por lo tanto, la detección de cambios no se ejecutará, por lo tanto, la vista no refleja la actualización / mutación.

En este caso, debe indicarle manualmente a Angular que verifique y actualice la vista (markForCheck);

Entonces, si hiciste esto:

  obj.name = "a new name";

Usted necesita hacer ésto:

  this.cd.markForCheck();

Por el contrario, a continuación causaría que se ejecutara una detección de cambio:

    obj = {
      name:"a new name"
    };

Que reemplazó por completo el obj anterior con uno nuevo {};

2- Se ha disparado un evento, como un clic o algo así o cualquiera de los componentes secundarios ha emitido un evento.

Eventos como:

  • Hacer clic
  • Tecla Arriba
  • Eventos de suscripción
  • etc.

En resumen:

  • Úselo detectChanges()cuando haya actualizado el modelo después de que angular haya ejecutado su detección de cambios, o si la actualización no ha estado en el mundo angular en absoluto.

  • Úselo markForCheck()si está usando OnPush y está pasando ChangeDetectionStrategypor alto al mutar algunos datos o si ha actualizado el modelo dentro de un setTimeout ;


66
Entonces, si mutas ese obj, la vista no se actualizará, e incluso si ejecutas detectChanges, no funcionará porque no ha habido ningún cambio , esto no es cierto. detectChangesVista de actualizaciones. Vea esta explicación detallada .
Max Koretskyi

Con respecto a markForCheck en la conclusión, tampoco es precisa. Aquí hay un ejemplo modificado de esta pregunta , no detecta cambios de objetos con OnPush y markForCheck. Pero el mismo ejemplo funcionará si no hay una estrategia OnPush.
Estus Flask

@Maximus, En cuanto a tu primer comentario, leí tu publicación, gracias por eso fue bueno. Pero en su explicación dice que si la estrategia es OnPush, significa que si this.cdMode === ChangeDetectorStatus.Checkedno actualiza la vista, es por eso que usaría markForCheck.
Milad

Y con respecto a sus enlaces a Plunker, ambos ejemplos están funcionando bien para mí, no sé a qué se
refiere

@Milad, esos comentarios vinieron de @estus :). El mío estaba a punto detectChanges. Y no hay cdModeen Angular 4.x.x. Escribo sobre eso en mi artículo. Me alegra que te haya gustado. No olvides que puedes recomendarlo en medio o seguirme :)
Max Koretskyi

99

La mayor diferencia entre los dos es que en detectChanges()realidad activa la detección de cambios, mientras markForCheck()que no activa la detección de cambios.

detectChanges

Este se usa para ejecutar la detección de cambios para el árbol de componentes que comienza con el componente que activa detectChanges(). Por lo tanto, la detección de cambios se ejecutará para el componente actual y todos sus elementos secundarios. Angular contiene referencias al árbol de componentes raíz en ApplicationRefy cuando ocurre cualquier operación asíncrona, activa la detección de cambios en este componente raíz a través de un método de envoltura tick():

@Injectable()
export class ApplicationRef_ extends ApplicationRef {
  ...
  tick(): void {
    if (this._runningTick) {
      throw new Error('ApplicationRef.tick is called recursively');
    }

    const scope = ApplicationRef_._tickScope();
    try {
      this._runningTick = true;
      this._views.forEach((view) => view.detectChanges()); <------------------

viewAquí está la vista del componente raíz. Puede haber muchos componentes raíz como lo describí en ¿Cuáles son las implicaciones de arrancar múltiples componentes ?

@milad describió las razones por las cuales potencialmente podría necesitar activar la detección de cambios manualmente.

markForCheck

Como dije, este tipo no activa la detección de cambios en absoluto. Simplemente va hacia arriba desde el componente actual al componente raíz y actualiza su estado de visualización ChecksEnabled. Aquí está el código fuente:

export function markParentViewsForCheck(view: ViewData) {
  let currView: ViewData|null = view;
  while (currView) {
    if (currView.def.flags & ViewFlags.OnPush) {
      currView.state |= ViewState.ChecksEnabled;  <-----------------
    }
    currView = currView.viewContainerParent || currView.parent;
  }
}

La detección de cambio real para el componente no está programada, pero cuando sucederá en el futuro (ya sea como parte del ciclo de CD actual o siguiente), las vistas del componente principal se verificarán incluso si se han desconectado los detectores de cambio. Los detectores de cambio se pueden separar usando cd.detach()o especificando la OnPushestrategia de detección de cambio. Todos los controladores de eventos nativos marcan todas las vistas de componentes principales para su verificación.

Este enfoque se usa a menudo en el ngDoCheckgancho del ciclo de vida. Puede leer más en Si cree que ngDoChecksu componente está siendo revisado, lea este artículo .

Consulte también Todo lo que necesita saber sobre la detección de cambios en Angular para obtener más detalles.


1
¿Por qué detectChanges funciona en el componente y sus elementos secundarios mientras markForCheck en el componente y los antepasados?
pablo

@pablo, eso es por diseño. No estoy realmente familiarizado con la razón
Max Koretskyi

@ AngularInDepth.com ¿changedetection bloquea la interfaz de usuario si hay un procesamiento muy intenso?
alt255

1
@jerry, el enfoque recomendado es utilizar una tubería asíncrona, que rastrea internamente la suscripción y en cada nuevo desencadenante de valor markForCheck. Entonces, si no está utilizando una tubería asíncrona, probablemente sea lo que debería usar. Sin embargo, tenga en cuenta que la actualización de la tienda debe ocurrir como resultado de algún evento asíncrono para que comience la detección de cambios. Ese es siempre el caso. Pero hay excepciones blog.angularindepth.com/…
Max Koretskyi

1
@MaxKoretskyiakaWizard gracias por la respuesta. Sí, la actualización de la tienda es principalmente el resultado de una recuperación o la configuración de Recuperación anterior. y después de buscar ... pero no siempre podemos usarlo async pipeya que dentro de la suscripción generalmente tenemos algunas cosas que hacer call setFromValues do some comparison... y si asyncse llama markForCheck¿cuál es el problema si lo llamamos nosotros mismos? pero, de nuevo, generalmente tenemos 2-3 o, a veces, más selectores para ngOnInitobtener datos diferentes ... y los llamamos markForChecka todos ... ¿está bien?
Jerry

0

cd.detectChanges() ejecutará la detección de cambios inmediatamente desde el componente actual hacia abajo a través de sus descendientes.

cd.markForCheck()no ejecutará la detección de cambios, pero marcará a sus antepasados ​​como la necesidad de ejecutar la detección de cambios. La próxima vez que la detección de cambios se ejecute en cualquier lugar, también se ejecutará para aquellos componentes que fueron marcados.

  • Si desea reducir el número de veces que la detección de cambio se llama uso cd.markForCheck(). A menudo, los cambios afectan a múltiples componentes y en algún lugar se llamará la detección de cambios. Básicamente estás diciendo: solo asegurémonos de que este componente también se actualice cuando eso suceda. (La vista se actualiza inmediatamente en cada proyecto que he escrito, pero no en todas las pruebas unitarias).
  • Si no puede estar seguro de que actualmentecd.detectChanges() no se está ejecutando la detección de cambios, use cd.markForCheck(). detectChanges()error en ese caso. Esto probablemente significa que está tratando de editar el estado de un componente ancestro, que está trabajando en contra de los supuestos en torno a los cuales se diseñó la detección de cambios de Angular.
  • Si es crítico que la vista se actualice sincrónicamente antes de alguna otra acción, úsela detectChanges(). markForCheck()Es posible que no actualice su vista a tiempo. La prueba de unidad que afecta algo a su vista, por ejemplo, puede requerir que llame manualmente fixture.detectChanges()cuando eso no era necesario en la propia aplicación.
  • Si está cambiando el estado en un componente con más antepasados ​​que descendientes, puede obtener un aumento de rendimiento al usarlo detectChanges()ya que no está ejecutando innecesariamente la detección de cambios en los antepasados ​​del componente.
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.