Comment utiliser $ scope. $ Watch et $ scope. $ Dans AngularJS?

1088

Je ne comprends pas comment utiliser $scope.$watchet $scope.$apply. La documentation officielle n'est pas utile.

Ce que je ne comprends pas spécifiquement:

  • Sont-ils connectés au DOM?
  • Comment puis-je mettre à jour les modifications DOM apportées au modèle?
  • Quel est le point de connexion entre eux?

J'ai essayé ce tutoriel , mais il prend la compréhension $watchet $applypour acquis.

Que faire $applyet $watchfaire, et comment les utiliser correctement?

ilyo
la source

Réponses:

1737

Vous devez être conscient du fonctionnement d'AngularJS afin de le comprendre.

Cycle de résumé et portée $

D'abord et avant tout, AngularJS définit un concept d'un soi-disant cycle de digestion . Ce cycle peut être considéré comme une boucle, au cours de laquelle AngularJS vérifie s'il y a des changements à toutes les variables surveillées par tous les $scopes. Donc, si vous avez $scope.myVardéfini dans votre contrôleur et que cette variable a été marquée pour être surveillée , vous dites implicitement à AngularJS de surveiller les changements myVarà chaque itération de la boucle.

Une question de suivi naturelle serait: tout est-il attaché à la $scopesurveillance? Heureusement non. Si vous surveilliez les modifications de chaque objet de votre $scope, alors rapidement une boucle de résumé prendrait beaucoup de temps à évaluer et vous vous heurteriez rapidement à des problèmes de performances. C'est pourquoi l'équipe AngularJS nous a donné deux façons de déclarer une $scopevariable comme étant surveillée (lire ci-dessous).

$ watch aide à écouter les changements de portée de $

Il existe deux façons de déclarer une $scopevariable comme étant surveillée.

  1. En l'utilisant dans votre modèle via l'expression <span>{{myVar}}</span>
  2. En l'ajoutant manuellement via le $watchservice

Annonce 1) C'est le scénario le plus courant et je suis sûr que vous l'avez déjà vu, mais vous ne saviez pas que cela a créé une montre en arrière-plan. Oui, bien sûr! L'utilisation de directives AngularJS (telles que ng-repeat) peut également créer des surveillances implicites.

Annonce 2) Voici comment créer vos propres montres . $watchLe service vous aide à exécuter du code lorsque certaines valeurs attachées à $scopeont changé. Il est rarement utilisé, mais est parfois utile. Par exemple, si vous souhaitez exécuter du code à chaque fois que «myVar» change, vous pouvez effectuer les opérations suivantes:

function MyController($scope) {

    $scope.myVar = 1;

    $scope.$watch('myVar', function() {
        alert('hey, myVar has changed!');
    });

    $scope.buttonClicked = function() {
        $scope.myVar = 2; // This will trigger $watch expression to kick in
    };
}

$ apply permet d'intégrer les changements au cycle de résumé

Vous pouvez considérer la $applyfonction comme un mécanisme d'intégration . Vous voyez, chaque fois que vous modifiez directement une variable surveillée attachée à l'$scope objet, AngularJS saura que le changement s'est produit. En effet, AngularJS savait déjà surveiller ces changements. Donc, si cela se produit dans du code géré par le framework, le cycle de résumé se poursuivra.

Cependant, parfois vous voulez changer une valeur en dehors du monde AngularJS et voir les changements se propager normalement. Considérez ceci - vous avez une $scope.myVarvaleur qui sera modifiée dans le $.ajax()gestionnaire d' un jQuery . Cela se produira à un moment donné à l'avenir. AngularJS ne peut pas attendre que cela se produise, car il n'a pas été invité à attendre sur jQuery.

Pour y remédier, $applya été introduit. Il vous permet de démarrer le cycle de digestion de manière explicite. Cependant, vous ne devez l'utiliser que pour migrer certaines données vers AngularJS (intégration avec d'autres frameworks), mais ne jamais utiliser cette méthode combinée avec du code AngularJS normal, car AngularJS lèvera alors une erreur.

Comment tout cela est-il lié au DOM?

Eh bien, vous devriez vraiment suivre à nouveau le tutoriel, maintenant que vous savez tout cela. Le cycle de résumé s'assurera que l'interface utilisateur et le code JavaScript restent synchronisés, en évaluant chaque observateur attaché à tous les $scopes tant que rien ne change. Si plus aucun changement ne se produit dans la boucle de résumé, alors il est considéré comme terminé.

Vous pouvez attacher des objets à l' $scopeobjet soit explicitement dans le Controller, soit en les déclarant sous {{expression}}forme directement dans la vue.

J'espère que cela aide à clarifier certaines connaissances de base sur tout cela.

Lectures complémentaires:

ŁukaszBachman
la source
57
"Angular vérifie s'il y a des changements à toutes les variables attachées à toutes les portées $" - je ne pense pas que ce soit tout à fait correct. Je crois qu'Angular vérifie uniquement (sale) les propriétés $ scope qui ont configuré $ watch (notez que l'utilisation de {{}} dans une vue créera automatiquement $ watch). Voir également la section "Considérations relatives aux performances de Scope $ watch" sur la page Scope .
Mark Rajcok
5
C'est peut-être le cas. Je vais essayer de trouver un peu de temps pour en savoir plus et modifier ma réponse.
ŁukaszBachman
15
@MarkRajcok, vous aviez raison. J'ai modifié ma réponse et souligné un article qui montre bien comment cela est mis en œuvre.
ŁukaszBachman
3
qu'en est-il de l'utiliser? (Méthode "Control as")
Leandro
2
L'utilisation de "Control as" ne devrait avoir aucun impact sur les informations ci-dessus. L'utilisation de this.myVar place myVar sur la portée.
Marcus Rådell
161

Dans AngularJS, nous mettons à jour nos modèles et nos vues / modèles mettent à jour le DOM "automatiquement" (via des directives intégrées ou personnalisées).

$ apply et $ watch, tous deux étant des méthodes Scope, ne sont pas liés au DOM.

La page Concepts (section "Runtime") a une assez bonne explication de la boucle $ digest, $ apply, la file d'attente $ evalAsync et la liste de surveillance $. Voici l'image qui accompagne le texte:

$ boucle de résumé

Quel que soit le code ayant accès à une étendue - normalement les contrôleurs et les directives (leurs fonctions de liaison et / ou leurs contrôleurs) - peut configurer une " watchExpression " qu'AngularJS évaluera par rapport à cette étendue. Cette évaluation se produit chaque fois qu'AngularJS entre dans sa boucle $ digest (en particulier, la boucle "$ watch list"). Vous pouvez regarder des propriétés individuelles d'étendue, vous pouvez définir une fonction pour regarder deux propriétés ensemble, vous pouvez regarder la longueur d'un tableau, etc.

Lorsque des choses se produisent "à l'intérieur d'AngularJS" - par exemple, vous tapez dans une zone de texte où la liaison de données bidirectionnelle AngularJS est activée (c'est-à-dire, utilise le modèle ng), un rappel $ http se déclenche, etc. - $ apply a déjà été appelé, donc nous 'est à l'intérieur du rectangle "AngularJS" dans la figure ci-dessus. Toutes les watchExpressions seront évaluées (éventuellement plus d'une fois - jusqu'à ce qu'aucun autre changement ne soit détecté).

Lorsque des choses se produisent "en dehors d'AngularJS" - par exemple, vous avez utilisé bind () dans une directive, puis cet événement se déclenche, entraînant l'appel de votre rappel, ou certains déclenchements de rappel enregistrés jQuery - nous sommes toujours dans le rectangle "natif". Si le code de rappel modifie tout ce que regarde $ watch, appelez $ apply pour entrer dans le rectangle AngularJS, provoquant l'exécution de la boucle $ digest, et donc AngularJS remarquera le changement et fera sa magie.

Mark Rajcok
la source
5
Je comprends l'idée, ce que je ne comprends pas, c'est comment les données sont effectivement transférées. J'ai un modèle qui est un objet avec beaucoup de données, j'en utilise une partie pour manipuler le DOM. alors une partie est changée. Comment placer les données modifiées au bon endroit dans le modèle? Dans l'exemple que j'ai utilisé, il fait la manipulation et à la fin utilise simplement scope.$apply(scope.model), je ne comprends pas quelles données sont transférées et comment sont-elles transférées au bon endroit dans le modèle?
ilyo
6
Aucun transfert de données magique n'a lieu. Normalement, avec les applications Angular, vous devez modifier les modèles Angular, qui entraînent ensuite les mises à jour de vue / DOM. Si vous mettez à jour le DOM en dehors d'Angular, vous devrez mettre à jour manuellement les modèles. scope.$apply(scope.model)sera simplement évalué scope.modelcomme une expression angulaire, puis entrera dans une boucle $ digest. Dans l'article que vous référencez, scope.$apply()cela suffirait probablement , car le modèle est déjà surveillé. La fonction stop () met à jour le modèle (je crois que toUpdate est une référence à scope.model), puis $ apply est appelé.
Mark Rajcok
On dirait que les documents AngularJS sont passés de sous cette réponse (le premier lien n'a pas de "runtime" ou $watchsur la page, et le deuxième lien est cassé - pour l'instant, de toute façon). Péniblement, les versions d'archives n'ont pas mis en cache le processus asynchrone ayant créé le contenu.
ruffin
52

AngularJS étend cette boucle d'événements , créant quelque chose appelé AngularJS context.

$ watch ()

Chaque fois que vous liez quelque chose dans l'interface utilisateur, vous insérez un $watchdans une $watchliste .

User: <input type="text" ng-model="user" />
Password: <input type="password" ng-model="pass" />

Ici, nous avons $scope.user, qui est lié à la première entrée, et nous avons $scope.pass, qui est lié à la seconde. Ce faisant, nous ajoutons deux $watches à la $watchliste .

Lorsque notre modèle est chargé, AKA dans la phase de liaison, le compilateur recherchera chaque directive et créera tous les $watches nécessaires.

AngularJS fournit $watch, $watchcollectionet $watch(true). Ci-dessous est un diagramme soigné expliquant les trois prises en profondeur par les observateurs .

Entrez la description de l'image ici

angular.module('MY_APP', []).controller('MyCtrl', MyCtrl)
function MyCtrl($scope,$timeout) {
  $scope.users = [{"name": "vinoth"},{"name":"yusuf"},{"name":"rajini"}];

  $scope.$watch("users", function() {
    console.log("**** reference checkers $watch ****")
  });

  $scope.$watchCollection("users", function() {
    console.log("**** Collection  checkers $watchCollection ****")
  });

  $scope.$watch("users", function() {
    console.log("**** equality checkers with $watch(true) ****")
  }, true);

  $timeout(function(){
     console.log("Triggers All ")
     $scope.users = [];
     $scope.$digest();

     console.log("Triggers $watchCollection and $watch(true)")
     $scope.users.push({ name: 'Thalaivar'});
     $scope.$digest();

     console.log("Triggers $watch(true)")
     $scope.users[0].name = 'Superstar';
     $scope.$digest();
  });
}

http://jsfiddle.net/2Lyn0Lkb/

$digest boucle

Lorsque le navigateur reçoit un événement qui peut être géré par le contexte AngularJS, la $digestboucle est déclenchée. Cette boucle est constituée de deux boucles plus petites. L'un traite la $evalAsyncfile d'attente et l'autre le $watch list. Le $digestva parcourir la liste de ce $watchque nous avons

app.controller('MainCtrl', function() {
  $scope.name = "vinoth";

  $scope.changeFoo = function() {
      $scope.name = "Thalaivar";
  }
});

{{ name }}
<button ng-click="changeFoo()">Change the name</button>

Ici, nous n'en avons qu'une $watchcar ng-click ne crée aucune montre.

Nous appuyons sur le bouton.

  1. Le navigateur reçoit un événement qui entrera dans le contexte AngularJS
  2. La $digestboucle s'exécutera et demandera à chaque $ watch les changements.
  3. Puisque le $watchqui surveillait les changements dans $ scope.name signale un changement, il forcera une autre $digestboucle.
  4. La nouvelle boucle ne rapporte rien.
  5. Le navigateur récupère le contrôle et il mettra à jour le DOM en reflétant la nouvelle valeur de $ scope.name
  6. L'important ici est que chaque événement entrant dans le contexte AngularJS exécute une $digestboucle. Cela signifie que chaque fois que nous écrivons une lettre dans une entrée, la boucle s'exécutera en vérifiant chaque élément $watchde cette page.

$ appliquer ()

Si vous appelez $applylorsqu'un événement est déclenché, il passera par le contexte angulaire, mais si vous ne l'appelez pas, il s'exécutera en dehors de celui-ci. C'est aussi facile que ça. $applyappellera le$digest() boucle en interne et itérera sur toutes les montres pour s'assurer que le DOM est mis à jour avec la nouvelle valeur mise à jour.

La $apply()méthode déclenchera des observateurs sur toute la $scopechaîne tandis que la $digest()méthode déclenchera uniquement des observateurs sur le courant $scopeet son children. Si aucun des $scopeobjets supérieurs n'a besoin de connaître les modifications locales, vous pouvez utiliser $digest().

Thalaivar
la source
18

J'ai trouvé très en profondeur des vidéos qui couvrent $watch, $apply, $digestet digère cycles:

Voici quelques diapositives utilisées dans ces vidéos pour expliquer les concepts (au cas où, si les liens ci-dessus sont supprimés / ne fonctionnent pas).

Entrez la description de l'image ici

Dans l'image ci-dessus, "$ scope.c" n'est pas surveillé car il n'est utilisé dans aucune des liaisons de données (dans le balisage). Les deux autres ( $scope.aet $scope.b) seront surveillés.

Entrez la description de l'image ici

À partir de l'image ci-dessus: En fonction de l'événement de navigateur respectif, AngularJS capture l'événement, effectue un cycle de résumé (passe par toutes les surveillances pour les changements), exécute les fonctions de surveillance et met à jour le DOM. S'il ne s'agit pas d'événements de navigateur, le cycle de résumé peut être déclenché manuellement en utilisant $applyou$digest .

En savoir plus $applyet $digest:

Entrez la description de l'image ici

user203687
la source
17

Il y en a $watchGroupet $watchCollectionaussi. Plus précisément, $watchGroupest vraiment utile si vous souhaitez appeler une fonction pour mettre à jour un objet qui a plusieurs propriétés dans une vue qui n'est pas un objet dom, par exemple une autre vue dans le canevas, WebGL ou une demande de serveur.

Ici, le lien de documentation .

Utkarsh Bhardwaj
la source
J'aurais commenté le $watchCollectionmais je vois que vous l'avez déjà fait. Voici une documentation à ce sujet sur le site AngularJS. Ils offrent un très beau visuel de la $watchprofondeur. Notez que les informations sont proches du bas de la page.
JabberwockyDecompiler
15

Il suffit de terminer la lecture de TOUT ce qui précède, ennuyeux et somnolent (désolé mais c'est vrai). Très technique, en profondeur, détaillé et sec. Pourquoi j'écris? Parce que AngularJS est énorme, de nombreux concepts interconnectés peuvent rendre n'importe qui fou. Je me suis souvent demandé: ne suis-je pas assez intelligent pour les comprendre? Non! C'est parce que si peu de gens peuvent expliquer la technologie dans un langage pour les nuls sans toutes les terminologies! D'accord, laissez-moi essayer:

1) Ce sont toutes des choses événementielles.(J'entends le rire, mais lisez la suite)

Si vous ne savez pas ce qu'est un événement, alors pensez à placer un bouton sur la page, connectez-le avec une fonction en utilisant "on-click", en attendant que les utilisateurs cliquent dessus pour déclencher les actions que vous plantez à l'intérieur du une fonction. Ou pensez au «déclencheur» de SQL Server / Oracle.

2) $ watch est "au clic".

La particularité est qu'elle prend 2 fonctions comme paramètres, la première donne la valeur de l'événement, la seconde prend la valeur en considération ...

3) $ digest est le patron qui vérifie sans relâche , bla-bla-bla mais un bon patron.

4) $ apply vous donne la voie lorsque vous voulez le faire manuellement , comme une preuve de sécurité (au cas où le clic ne se déclenche pas, vous le forcez à s'exécuter.)

Maintenant, rendons-le visuel. Imaginez ceci pour le rendre encore plus facile à saisir l'idée:

Dans un restaurant,

- SERVEURS

sont censés prendre les commandes des clients, c'est

$watch(
  function(){return orders;},
  function(){Kitchen make it;}
);

- GESTIONNAIRE courant pour s'assurer que tous les serveurs sont éveillés, sensibles à tout signe de changement de la part des clients. C'est$digest()

- LE PROPRIÉTAIRE a le pouvoir ultime de conduire tout le monde sur demande, c'est$apply()

Jeb50
la source
2
Cela peut être compris par un enfant de 5 ans. J'apprécie ce genre de réponse. +1
Chris22