Quelle est la différence entre markForCheck () et detectChanges ()

174

Quelle est la différence entre ChangeDetectorRef.markForCheck()et ChangeDetectorRef.detectChanges()?

Je n'ai trouvé que des informations sur SO quant à la différence entre NgZone.run(), mais pas entre ces deux fonctions.

Pour les réponses avec uniquement une référence au document, veuillez illustrer quelques scénarios pratiques pour choisir l'un par rapport à l'autre.

parlement
la source
@Milad Comment savez-vous qu'il a voté contre? Il y a beaucoup de gens qui parcourent ce site.
Au revoir StackExchange
2
@FrankerZ, parce que j'écrivais et que j'ai vu le vote défavorable et une seconde plus tard, la question a été mise à jour en disant que "Pour les réponses avec seulement une référence au document, veuillez illustrer quelques scénarios pratiques pour choisir l'un par rapport à l'autre? Cela aidera à le clarifier dans mon esprit".
Milad
3
Le vote défavorable était de vous inciter à compléter la réponse originale qui était juste copiée et collée à partir des documents que j'ai déjà vus. Et ça a marché! Maintenant, la réponse est très claire et est la réponse acceptée, merci :)
parlement
3
quel plan sournois @parlement!
HankCa

Réponses:

234

À partir de la documentation:

detectChanges (): void

Vérifie le détecteur de changement et ses enfants.

Cela signifie que s'il y a un cas où quelque chose à l'intérieur de votre modèle (votre classe) a changé mais qu'il n'a pas reflété la vue, vous devrez peut-être notifier Angular pour détecter ces changements (détecter les changements locaux) et mettre à jour la vue.

Les scénarios possibles pourraient être:

1- Le détecteur de changement est détaché de la vue (voir détacher )

2- Une mise à jour a eu lieu mais elle n'a pas été dans la zone angulaire, par conséquent, Angular ne le sait pas.

Comme lorsqu'une fonction tierce a mis à jour votre modèle et que vous souhaitez mettre à jour la vue après cela.

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

Étant donné que ce code est en dehors de la zone d'Angular (probablement), vous devez probablement vous assurer de détecter les modifications et de mettre à jour la vue, ainsi:

 myFunction(){
   someFunctionThatIsRunByAThirdPartyCode();

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

REMARQUE :

Il existe d'autres façons de faire fonctionner ci-dessus, en d'autres termes, il existe d'autres façons d'apporter ce changement à l'intérieur du cycle de changement angulaire.

** Vous pouvez envelopper cette fonction tierce dans un zone.run:

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

** Vous pouvez envelopper la fonction dans un setTimeout:

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

3- Il y a aussi des cas où vous mettez à jour le modèle une fois que le change detection cycleest terminé, où dans ces cas vous obtenez cette erreur redoutée:

"L'expression a changé après avoir été vérifiée";

Cela signifie généralement (du langage Angular2):

J'ai vu un changement dans votre modèle qui a été causé par l'un de mes moyens acceptés (événements, requêtes XHR, setTimeout, et ...) puis j'ai exécuté ma détection de changement pour mettre à jour votre vue et je l'ai terminé, mais il y en a eu un autre fonction dans votre code qui a mis à jour le modèle à nouveau et je ne veux plus exécuter ma détection de changement car il n'y a plus de vérification sale comme AngularJS: D et nous devrions utiliser un flux de données à sens unique!

Vous rencontrerez certainement cette erreur: P.

Quelques façons de résoudre ce problème:

1- Bonne manière : assurez-vous que la mise à jour est dans le cycle de détection des changements (les mises à jour Angular2 sont un flux à sens unique qui se produit une fois, ne mettez pas à jour le modèle après cela et déplacez votre code vers un meilleur endroit / heure).

2- Méthode paresseuse : lancez detectChanges () après cette mise à jour pour rendre angular2 heureux, ce n'est certainement pas la meilleure façon, mais comme vous avez demandé quels sont les scénarios possibles, c'est l'un d'entre eux.

De cette façon, vous dites: je sais sincèrement que vous avez exécuté la détection des modifications, mais je veux que vous le refassiez car j'ai dû mettre à jour quelque chose à la volée après avoir terminé la vérification.

3- Mettez le code dans un setTimeout, car il setTimeoutest patché par zone et s'exécutera une detectChangesfois terminé.


À partir des documents

markForCheck() : void

Marque tous les ancêtres ChangeDetectionStrategy comme à cocher.

Ceci est principalement nécessaire lorsque la ChangeDetectionStrategy de votre composant est OnPush .

OnPush lui-même signifie, n'exécutez la détection de changement que si l'un de ces événements s'est produit:

1- L'une des @inputs du composant a été complètement remplacée par une nouvelle valeur, ou simplement, si la référence de la propriété @Input a complètement changé.

Donc, si ChangeDetectionStrategy de votre composant est OnPush et que vous avez:

   var obj = {
     name:'Milad'
   };

Et puis vous le mettez à jour / le mutez comme:

  obj.name = "a new name";

Cela ne mettra pas à jour la référence obj , donc la détection de changement ne fonctionnera pas, donc la vue ne reflète pas la mise à jour / la mutation.

Dans ce cas, vous devez indiquer manuellement à Angular de vérifier et de mettre à jour la vue (markForCheck);

Donc, si vous avez fait ceci:

  obj.name = "a new name";

Vous devez faire ceci:

  this.cd.markForCheck();

Au contraire, ci-dessous entraînerait l'exécution d'une détection de changement:

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

Ce qui a complètement remplacé l'obj précédent par un nouveau {};

2- Un événement s'est déclenché, comme un clic ou quelque chose comme ça ou l'un des composants enfants a émis un événement.

Des événements comme:

  • Cliquez sur
  • Keyup
  • Événements d'abonnement
  • etc.

Donc en bref:

  • À utiliser detectChanges()lorsque vous avez mis à jour le modèle après que Angular a exécuté sa détection de changement, ou si la mise à jour n'a pas du tout été dans le monde angulaire.

  • À utiliser markForCheck()si vous utilisez OnPush et que vous contournez le en ChangeDetectionStrategyfaisant muter certaines données ou si vous avez mis à jour le modèle dans un setTimeout ;

Milad
la source
6
Donc, si vous changez cet objet, la vue ne sera pas mise à jour, et même si vous exécutez detectChanges, cela ne fonctionnera pas car il n'y a pas eu de changements - ce n'est pas vrai. detectChangesvue des mises à jour. Voir cette explication détaillée .
Max Koretskyi
Concernant markForCheck dans la conclusion, ce n'est pas exact non plus. Voici un exemple modifié de cette question , il ne détecte pas les changements d'objet avec OnPush et markForCheck. Mais le même exemple fonctionnera s'il n'y a pas de stratégie OnPush.
Estus Flask
@Maximus, Concernant ton premier commentaire, j'ai lu ton post, merci pour ça c'était bien. Mais dans votre explication, vous dites que si la stratégie est OnPush, cela signifie que si this.cdMode === ChangeDetectorStatus.Checkedelle ne met pas à jour la vue, c'est pourquoi vous utiliseriez markForCheck.
Milad
Et en ce qui concerne vos liens vers plunker, les deux exemples fonctionnent bien pour moi, je ne sais pas ce que vous voulez dire
Milad
@Milad, ces commentaires sont venus de @estus :). Le mien était sur detectChanges. Et il n'y a pas cdModedans Angular 4.x.x. J'écris à ce sujet dans mon article. Content que tu aies aimé. N'oubliez pas que vous pouvez le recommander sur support ou me suivre :)
Max Koretskyi
99

La plus grande différence entre les deux est que detectChanges()déclenche réellement la détection des changements, mais markForCheck()ne déclenche pas la détection des changements.

detectChanges

Celui-ci est utilisé pour exécuter la détection de changement pour l'arborescence des composants en commençant par le composant sur lequel vous déclenchez detectChanges(). Ainsi, la détection des modifications s'exécutera pour le composant actuel et tous ses enfants. Angular contient des références à l'arborescence des composants racine dans le ApplicationRefet lorsqu'une opération asynchrone se produit, elle déclenche la détection de changement sur ce composant racine via une méthode wrapper 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()); <------------------

viewvoici la vue du composant racine. Il peut y avoir de nombreux composants racine comme je l'ai décrit dans la section Quelles sont les implications de l'amorçage de plusieurs composants .

@milad a décrit les raisons pour lesquelles vous pourriez avoir besoin de déclencher manuellement la détection des modifications.

markForCheck

Comme je l'ai dit, ce type ne déclenche pas du tout la détection des changements. Il monte simplement du composant actuel au composant racine et met à jour leur état d'affichage en ChecksEnabled. Voici le code source:

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 détection de changement réelle pour le composant n'est pas planifiée, mais lorsqu'elle se produira dans le futur (dans le cadre du cycle CD actuel ou suivant), les vues du composant parent seront vérifiées même si elles avaient des détecteurs de changement détachés. Les détecteurs de changement peuvent être détachés en utilisant cd.detach()ou en spécifiant une OnPushstratégie de détection de changement. Tous les gestionnaires d'événements natifs marquent toutes les vues des composants parents pour vérification.

Cette approche est souvent utilisée dans le ngDoCheckhook de cycle de vie. Pour en savoir plus, lisez cet article si vous pensez ngDoCheckque votre composant est en cours de vérification .

Consultez également Tout ce que vous devez savoir sur la détection des changements dans Angular pour plus de détails.

Max Koretskyi
la source
1
Pourquoi detectChanges fonctionne-t-il sur le composant et ses enfants alors que markForCheck sur le composant et ses ancêtres?
pablo
@pablo, c'est par conception. Je ne connais pas vraiment la justification
Max Koretskyi
@ AngularInDepth.com est-ce que la détection des modifications bloque l'interface utilisateur s'il y a un traitement très intensif?
alt255
1
@jerry, l'approche recommandée est d'utiliser un tube asynchrone, qui suit en interne l'abonnement et à chaque nouveau déclencheur de valeur markForCheck. Donc, si vous n'utilisez pas de tuyau asynchrone, c'est probablement ce que vous devriez utiliser. Cependant, gardez à l'esprit que la mise à jour du magasin doit se produire à la suite d'un événement asynchrone pour que la détection des modifications démarre. C'est presque toujours le cas. Mais il y a des exceptions blog.angularindepth.com
Max Koretskyi
1
@MaxKoretskyiakaWizard merci pour la réponse. Oui, la mise à jour du magasin est principalement le résultat d'une récupération ou d'un paramètre isFetching avant. et après la récupération .. mais nous ne pouvons pas toujours utiliser async pipecar à l'intérieur de l'abonnement, nous avons généralement quelques choses à faire comme call setFromValues do some comparison.. et si asynclui-même appelle markForCheckquel est le problème si nous l'appelons nous-mêmes? mais encore une fois, nous avons généralement 2-3 sélecteurs ou parfois plus pour ngOnInitobtenir des données différentes ... et nous les appelons markForChecktous ... est-ce que c'est OK?
jerry
0

cd.detectChanges() exécutera la détection des modifications immédiatement à partir du composant actuel jusqu'à ses descendants.

cd.markForCheck()n'exécutera pas la détection des modifications, mais marquera ses ancêtres comme ayant besoin d'exécuter la détection des modifications. La prochaine fois que la détection des modifications s'exécutera n'importe où, elle s'exécutera également pour les composants qui ont été marqués.

  • Si vous souhaitez réduire le nombre de fois que la détection de changement est appelée utilisation cd.markForCheck(). Souvent, les modifications affectent plusieurs composants et la détection des modifications sera appelée quelque part. Vous dites essentiellement: assurons-nous simplement que ce composant est également mis à jour lorsque cela se produit. (La vue est immédiatement mise à jour dans chaque projet que j'ai écrit, mais pas dans chaque test unitaire).
  • Si vous ne pouvez pas être sûr que la détection des modifications cd.detectChanges()n'est pas en cours d' exécution , utilisez cd.markForCheck(). detectChanges()erreur dans ce cas. Cela signifie probablement que vous essayez de modifier l'état d'un composant ancêtre, ce qui va à l'encontre des hypothèses sur lesquelles la détection de changement d'Angular est conçue.
  • S'il est essentiel que la vue se mette à jour de manière synchrone avant une autre action, utilisez detectChanges(). markForCheck()peut ne pas mettre à jour votre vue à temps. Le test unitaire, quelque chose affecte votre vue, par exemple, peut vous obliger à appeler manuellement fixture.detectChanges()lorsque cela n'était pas nécessaire dans l'application elle-même.
  • Si vous modifiez l'état dans un composant avec plus d'ancêtres que de descendants, vous pouvez obtenir une augmentation des performances en utilisant, detectChanges()car vous n'exécutez pas inutilement la détection des modifications sur les ancêtres du composant.
Kevin Beal
la source