Comment la liaison de données fonctionne-t-elle dans le cadre AngularJS
?
Je n'ai pas trouvé de détails techniques sur leur site . Son fonctionnement est plus ou moins clair lorsque les données sont propagées d'une vue à une autre. Mais comment AngularJS surveille-t-il les modifications des propriétés du modèle sans paramètres de réglage ou d’obtention?
J'ai trouvé qu'il y a observateurs de JavaScript qui peuvent faire ce travail. Mais ils ne sont pas pris en charge dans Internet Explorer 6 et Internet Explorer 7 . Alors, comment AngularJS sait-il que j’ai modifié par exemple ce qui suit et qu’il reflète ce changement dans une vue?
myobject.myproperty="new value";
AngularJS mémorise la valeur et la compare à une valeur précédente. C'est une sale vérification. S'il y a un changement de valeur, alors l'événement de changement est déclenché.
La méthode $apply()
, que vous appelez lorsque vous passez d'un monde non AngularJS à un monde AngularJS, appelle $digest()
. Un condensé est tout simplement vieux sale-vérification. Cela fonctionne sur tous les navigateurs et est totalement prévisible.
Pour contraster les vérifications anarchiques (AngularJS) et les auditeurs de modification ( KnockoutJS et Backbone.js ): Les vérifications anormales peuvent sembler simples, voire inefficaces (je traiterai de cela ultérieurement. ), il s'avère que c'est sémantiquement correct tout le temps, alors que les auditeurs de changement ont beaucoup de cas étranges et ont besoin d'éléments tels que le suivi des dépendances pour le rendre plus correct sémantiquement. Le suivi de dépendance KnockoutJS est une fonctionnalité astucieuse pour un problème qu’AngularJS n’a pas.
Donc, il peut sembler que nous soyons lents, car la vérification en profondeur est peu efficace C’est là que nous devons examiner des nombres réels plutôt que de nous limiter à des arguments théoriques, mais définissons d’abord certaines contraintes.
Les humains sont:
Lent - Toute vitesse supérieure à 50 ms est imperceptible pour l'homme et peut donc être considérée comme "instantanée".
Limité - Vous ne pouvez pas vraiment afficher plus de 2000 informations à un humain sur une seule page. N'importe quoi de plus que cela est une très mauvaise interface utilisateur, et les humains ne peuvent pas traiter cela de toute façon.
La vraie question est donc la suivante: combien de comparaisons pouvez-vous effectuer sur un navigateur en 50 ms? Il est difficile de répondre à cette question car de nombreux facteurs entrent en jeu, mais voici un cas test: http://jsperf.com/angularjs-digest/6 qui crée 10 000 observateurs. Sur un navigateur moderne, cela prend un peu moins de 6 ms. Sur Internet Explorer 8 cela prend environ 40 ms. Comme vous pouvez le constater, le problème ne se pose même pas avec les navigateurs lents ces jours-ci. Il y a une mise en garde: les comparaisons doivent être simples pour tenir dans le délai imparti ... Malheureusement, il est trop facile d'ajouter une comparaison lente dans AngularJS. Il est donc facile de créer des applications lentes si vous ne savez pas quoi faites. Mais nous espérons avoir une réponse en fournissant un module d'instrumentation, qui vous montrerait quelles sont les comparaisons lentes.
Il s'avère que les jeux vidéo et les GPU utilisent l'approche de vérification en profondeur, notamment parce qu'elle est cohérente. Tant qu'ils dépassent le taux de rafraîchissement du moniteur (généralement 50-60 Hz, ou toutes les 16,6-20 ms), toute performance dépassant ce seuil est un gaspillage.
Misko a déjà fourni une excellente description du fonctionnement des liaisons de données, mais je voudrais ajouter mon point de vue sur le problème de performances lié à la liaison de données.
Comme Misko l'a indiqué, environ 2000 liaisons sont le point de départ des problèmes, mais vous ne devriez pas avoir plus de 2000 informations sur une page. Cela peut être vrai, mais toutes les liaisons de données ne sont pas visibles pour l'utilisateur. Une fois que vous commencez à construire tout type de widget ou de grille de données avec une liaison bidirectionnelle, vous pouvez facilement taper sur 2000 liaisons, sans avoir un mauvais UX.
Prenons, par exemple, une liste déroulante dans laquelle vous pouvez taper du texte pour filtrer les options disponibles. Ce type de contrôle pourrait avoir environ 150 éléments et rester très utilisable. S'il y a une fonctionnalité supplémentaire (par exemple une classe spécifique sur l'option actuellement sélectionnée), vous commencez à obtenir 3 à 5 liaisons par option. Placez trois de ces widgets sur une page (par exemple, l'un pour sélectionner un pays, l'autre pour sélectionner une ville du pays en question et le troisième pour sélectionner un hôtel) et vous avez déjà entre 1 000 et 2 000 liaisons.
Ou envisagez une grille de données dans une application Web d'entreprise. 50 lignes par page n'est pas déraisonnable, chacune pouvant comporter 10 à 20 colonnes. Si vous construisez cela avec ng-repeats et/ou si vous avez des informations dans certaines cellules qui utilisent certaines liaisons, vous pourriez approcher 2 000 liaisons avec cette grille uniquement.
Je trouve que c'est un problème énorme quand je travaille avec AngularJS, et la seule solution que j'ai pu trouver jusqu'à présent est de construire des widgets sans utiliser de liaison bidirectionnelle, au lieu d'utiliser ngOnce, le désenregistrement observateurs et astuces similaires, ou directives de construction qui construisent le DOM avec jQuery et la manipulation du DOM. Je pense que cela va à l'encontre de l'objectif d'utiliser Angular en premier lieu.
J'aimerais entendre des suggestions sur d'autres moyens de gérer cela, mais alors je devrais peut-être écrire ma propre question. Je voulais mettre ça dans un commentaire, mais ça s'est avéré bien trop long pour ça ...
TL; DR
La liaison de données peut entraîner des problèmes de performances sur des pages complexes.
$scope
Angular maintient une array
simple des observateurs dans les objets $scope
. Si vous inspectez un $scope
, vous constaterez qu'il contient un array
appelé $$watchers
.
Chaque observateur est un object
qui contient entre autres
attribute
ou quelque chose de plus compliqué.$scope
comme sale.Il existe différentes manières de définir un observateur dans AngularJS.
Vous pouvez explicitement $watch
une attribute
sur $scope
.
$scope.$watch('person.username', validateUnique);
Vous pouvez placer une interpolation {{}}
dans votre modèle (un observateur sera créé pour vous sur le $scope
actuel).
<p>username: {{person.username}}</p>
Vous pouvez demander à une directive telle que ng-model
de définir l'observateur pour vous.
<input ng-model="person.username" />
$digest
vérifie tous les observateurs par rapport à leur dernière valeur.Lorsque nous interagissons avec AngularJS via les canaux normaux (ng-model, ng-repeat, etc.), un cycle de résumé est déclenché par la directive.
Un cycle de résumé est un parcours en profondeur de $scope
et de tous ses enfants. Pour chaque $scope
object
, nous itérons sur son $$watchers
array
et évaluons toutes les expressions. Si la nouvelle valeur d'expression est différente de la dernière valeur connue, la fonction de l'observateur est appelée. Cette fonction peut recompiler une partie du DOM, recalculer une valeur sur $scope
, déclencher une AJAX
request
, tout ce dont vous avez besoin.
Chaque étendue est parcourue et chaque expression de contrôle évaluée et vérifiée par rapport à la dernière valeur.
$scope
est saleSi un observateur est déclenché, l'application sait que quelque chose a changé et le $scope
est marqué comme sale.
Les fonctions d'observation peuvent modifier d'autres attributs sur $scope
ou sur un parent $scope
. Si une fonction $watcher
a été déclenchée, nous ne pouvons pas garantir que nos autres $scope
s sont toujours propres et nous exécutons à nouveau le cycle de synthèse complet.
Ceci est dû au fait que AngularJS a une liaison bidirectionnelle, de sorte que les données peuvent être transférées dans l’arborescence $scope
. Nous pouvons changer une valeur sur un $scope
supérieur qui a déjà été digéré. Peut-être que nous changeons une valeur sur le $rootScope
.
$digest
est sale, nous réexécutons le cycle entier $digest
Nous parcourons continuellement le cycle $digest
jusqu'à ce que le cycle de résumé soit propre (toutes les expressions $watch
ont la même valeur qu'au cycle précédent) ou nous atteignons la limite de résumé. Par défaut, cette limite est fixée à 10.
Si nous atteignons la limite de digestion, AngularJS soulèvera une erreur dans la console:
10 $digest() iterations reached. Aborting!
Comme vous pouvez le constater, chaque fois que quelque chose change dans une application AngularJS, AngularJS vérifie chaque observateur dans la hiérarchie $scope
pour voir comment répondre. Pour un développeur, cela représente un avantage considérable en termes de productivité, car vous n'avez maintenant besoin d'écrire presque aucun code de câblage. AngularJS remarquera simplement si une valeur a été modifiée et rendra le reste de l'application cohérent avec le changement.
Du point de vue de la machine, cela est extrêmement inefficace et ralentira notre application si nous créons trop d’observateurs. Misko a cité un chiffre d'environ 4000 observateurs avant que votre application ne soit lente sur les navigateurs plus anciens.
Cette limite est facile à atteindre si vous ng-repeat
sur une grande JSON
array
par exemple. Vous pouvez limiter ceci en utilisant des fonctionnalités telles que la liaison ponctuelle pour compiler un modèle sans créer d'observateurs.
Chaque fois que votre utilisateur interagit avec votre application, chaque observateur de votre application sera évalué au moins une fois. Une grande partie de l'optimisation d'une application AngularJS consiste à réduire le nombre d'observateurs dans votre arborescence $scope
. Un moyen facile de faire cela est avec une liaison unique .
Si vous avez des données qui changeront rarement, vous pouvez les lier une seule fois en utilisant la syntaxe ::, comme ceci:
<p>{{::person.username}}</p>
ou
<p ng-bind="::person.username"></p>
La liaison ne sera déclenchée que lorsque le modèle contenant est rendu et que les données sont chargées dans $scope
.
Ceci est particulièrement important lorsque vous avez un ng-repeat
avec de nombreux éléments.
<div ng-repeat="person in people track by username">
{{::person.username}}
</div>
Ceci est ma compréhension de base. C'est peut-être faux!
$watch
.$apply
.$apply
, la méthode $digest
est invoquée. Elle parcourt chacune des surveillances et vérifie si elles ont changé depuis la dernière fois que $digest
a été exécuté.En développement normal, la syntaxe de liaison de données dans le code HTML indique au compilateur AngularJS de créer les surveillées pour vous et les méthodes du contrôleur sont déjà exécutées dans $apply
. Donc, pour le développeur d'applications, tout est transparent.
Je me suis demandé cela moi-même pendant un moment. Sans setters, comment AngularJS
remarque-t-il les modifications apportées à l'objet $scope
? Est-ce qu'il les interroge?
Voici ce qu'il fait: Tout endroit "normal" que vous modifiez le modèle a déjà été appelé depuis le courage de AngularJS
, de sorte qu'il appelle automatiquement $apply
après l'exécution de votre code. Supposons que votre contrôleur utilise une méthode liée à ng-click
sur un élément. Parce que AngularJS
relie l'appel de cette méthode ensemble pour vous, il a une chance de faire un $apply
à l'endroit approprié. De même, pour les expressions apparaissant directement dans les vues, celles-ci sont exécutées par AngularJS
de sorte que le $apply
.
Lorsque la documentation indique qu'il faut appeler $apply
manuellement pour le code en dehors de AngularJS
, il s'agit d'un code qui, lorsqu'il est exécuté, ne provient pas de AngularJS
lui-même dans la pile d'appels.
Expliquer avec des images:
La référence dans l'étendue n'est pas exactement la référence dans le modèle. Lorsque vous liez des données à deux objets, vous avez besoin d'un troisième qui écoute le premier et modifie l'autre.
Ici, lorsque vous modifiez le <input>
, vous touchez le data-ref3 . Et le mécanisme classique de liaison de données changera data-ref4 . Comment les autres expressions {{data}}
vont-elles bouger?
Angular maintient une oldValue
et newValue
de chaque reliure. Et après chaque événement angulaire , la célèbre boucle $digest()
vérifie la liste de suivi pour voir si quelque chose a changé. Ces événements angulaires sont ng-click
, ng-change
, $http
terminé ... La $digest()
restera en boucle aussi longtemps que tout oldValue
diffère de newValue
.
Dans l'image précédente, vous remarquerez que data-ref1 et data-ref2 ont changé.
C'est un peu comme l'oeuf et le poulet. Vous ne savez jamais qui commence, mais j'espère que cela fonctionnera la plupart du temps comme prévu.
L'autre point est que vous pouvez facilement comprendre l'impact profond d'une simple liaison sur la mémoire et le processeur. Espérons que les ordinateurs de bureau sont assez gros pour gérer cela. Les téléphones portables ne sont pas si forts.
Évidemment, il n'y a pas de vérification périodique de Scope
s'il y a un changement dans les objets qui lui sont attachés. Tous les objets attachés à la portée ne sont pas surveillés. Scope conserve de manière prototype $$ watchers. Scope
n'itère que ce $$watchers
lorsque $digest
est appelé.
Angular ajoute un observateur aux observateurs de $$ pour chacun de ceux-ci
- {{expression}} - Dans vos modèles (et partout où il y a une expression) ou lorsque nous définissons ng-model.
- $ scope. $ watch (‘expression/function’) - Dans votre JavaScript, nous pouvons simplement attacher un objet scope pour angular à regarder.
$ watch la fonction prend trois paramètres:
La première est une fonction d'observation qui renvoie simplement l'objet ou nous pouvons simplement ajouter une expression.
La seconde est une fonction d'écoute qui sera appelée en cas de changement d'objet. Toutes les choses comme les modifications du DOM seront implémentées dans cette fonction.
Le troisième étant un paramètre optionnel prenant un booléen. Si c'est vrai, angular deep surveille l'objet et si son faux Angular ne fait que regarder une référence sur l'objet. La mise en œuvre approximative de $ watch ressemble à ceci
Scope.prototype.$watch = function(watchFn, listenerFn) {
var watcher = {
watchFn: watchFn,
listenerFn: listenerFn || function() { },
last: initWatchVal // initWatchVal is typically undefined
};
this.$$watchers.Push(watcher); // pushing the Watcher Object to Watchers
};
Il y a une chose intéressante dans Angular appelée Digest Cycle. Le cycle $ digest commence à la suite d'un appel à $ scope. $ Digest (). Supposons que vous modifiiez un modèle $ scope dans une fonction de gestionnaire via la directive ng-click. Dans ce cas, AngularJS déclenche automatiquement un cycle $ digest en appelant $ digest (). Outre ng-click, il existe plusieurs autres directives/services intégrés qui vous permettent de changer de modèle (par exemple, ng-model, $ timeout, etc.). et déclenche automatiquement un cycle $ digest. La mise en œuvre approximative de $ digest ressemble à ceci.
Scope.prototype.$digest = function() {
var dirty;
do {
dirty = this.$$digestOnce();
} while (dirty);
}
Scope.prototype.$$digestOnce = function() {
var self = this;
var newValue, oldValue, dirty;
_.forEach(this.$$watchers, function(watcher) {
newValue = watcher.watchFn(self);
oldValue = watcher.last; // It just remembers the last value for dirty checking
if (newValue !== oldValue) { //Dirty checking of References
// For Deep checking the object , code of Value
// based checking of Object should be implemented here
watcher.last = newValue;
watcher.listenerFn(newValue,
(oldValue === initWatchVal ? newValue : oldValue),
self);
dirty = true;
}
});
return dirty;
};
Si nous utilisons la fonction setTimeout () de JavaScript pour mettre à jour un modèle de scope, Angular n'a aucun moyen de savoir ce que vous pourriez changer. Dans ce cas, il est de notre responsabilité d’appeler manuellement $ apply (), ce qui déclenche un cycle $ digest. De même, si vous avez une directive qui configure un écouteur d'événements DOM et modifie certains modèles dans la fonction de gestionnaire, vous devez appeler $ apply () pour vous assurer que les modifications prennent effet. La grande idée de $ apply est que nous pouvons exécuter du code qui n'est pas au courant de Angular, ce code peut toujours changer les choses sur la portée. Si nous enveloppons ce code dans $ apply, il se chargera d'appeler $ digest (). Mise en oeuvre approximative de $ apply ().
Scope.prototype.$apply = function(expr) {
try {
return this.$eval(expr); //Evaluating code in the context of Scope
} finally {
this.$digest();
}
};
AngularJS gère le mécanisme de liaison de données à l'aide de trois fonctions puissantes: $ watch () , $ digest () et $ apply () . La plupart du temps, AngularJS appellera $ scope. $ Watch () et $ scope. $ Digest (), mais dans certains cas, vous devrez peut-être appeler ces fonctions manuellement pour mettre à jour de nouvelles valeurs.
$ watch () : -
Cette fonction est utilisée pour observer les changements dans une variable de la portée $. Il accepte trois paramètres: expression, écouteur et objet d'égalité, écouteur et objet d'égalité étant des paramètres facultatifs.
$ digest () -
Cette fonction effectue une itération sur toutes les surveillances de l'objet $ scope et de ses objets enfant $ scope.
(s'il en a). Lorsque $ digest () effectue une itération sur les contrôles, il vérifie si la valeur de l'expression a changé. Si la valeur a changé, AngularJS appelle le programme d'écoute avec la nouvelle valeur et l'ancienne valeur. La fonction $ digest () est appelée chaque fois que AngularJS le juge nécessaire. Par exemple, après un clic sur un bouton ou après un appel AJAX. Il se peut que, dans certains cas, AngularJS n’appelle pas la fonction $ digest () pour vous. Dans ce cas, vous devez l'appeler vous-même.
$ apply () -
Angular ne met automatiquement à jour par magie que les modifications de modèle qui se trouvent dans le contexte AngularJS. Lorsque vous modifiez un modèle en dehors du contexte Angular (comme les événements DOM du navigateur, les bibliothèques setTimeout, XHR ou tierces), vous devez informer Angular des modifications en appelant $ apply () manuellement. Lorsque l'appel de la fonction $ apply () se termine, AngularJS appelle $ digest () en interne, de sorte que toutes les liaisons de données sont mises à jour.
Il m'est arrivé de lier un modèle de données d'une personne à un formulaire. J'ai alors mappé directement les données avec le formulaire.
Par exemple, si le modèle avait quelque chose comme:
$scope.model.people.name
L'entrée de contrôle du formulaire:
<input type="text" name="namePeople" model="model.people.name">
Ainsi, si vous modifiez la valeur du contrôleur d'objet, cela sera automatiquement reflété dans la vue.
Un exemple où j'ai transmis que le modèle est mis à jour à partir des données du serveur est lorsque vous demandez un code postal et qu'un code postal basé sur écrit charge une liste de colonies et de villes associées à cette vue et définit par défaut la première valeur avec l'utilisateur. Et cela, j’ai très bien travaillé, c’est que angularJS
prend parfois quelques secondes pour actualiser le modèle; pour ce faire, vous pouvez mettre une visière tout en affichant les données.
La liaison de données unidirectionnelle est une approche dans laquelle une valeur est extraite du modèle de données et insérée dans un élément HTML. Il n'y a aucun moyen de mettre à jour le modèle depuis la vue. Il est utilisé dans les systèmes de gabarits classiques. Ces systèmes lient les données dans une seule direction.
La liaison de données dans Angular applications est la synchronisation automatique des données entre les composants de modèle et de vue.
La liaison de données vous permet de traiter le modèle comme la source unique de vérité de votre application. La vue est une projection du modèle à tout moment. Si le modèle est modifié, la vue reflète le changement et inversement.
AngularJs prend en charge liaison de données bidirectionnelle.
Signifie que vous pouvez accéder aux données Vue -> Contrôleur & Contrôleur -> Vue
par exemple
1)
// If $scope have some value in Controller.
$scope.name = "Peter";
// HTML
<div> {{ name }} </div>
O/P
Peter
Vous pouvez lier des données dans ng-model
Comme: -
2)
<input ng-model="name" />
<div> {{ name }} </div>
Ici, dans l'exemple ci-dessus, tout ce que l'utilisateur en entrée donnera, il sera visible dans la balise <div>
.
Si vous voulez lier l'entrée du code HTML au contrôleur: -
)
<form name="myForm" ng-submit="registration()">
<label> Name </lbel>
<input ng-model="name" />
</form>
Ici, si vous voulez utiliser l’entrée name
dans le contrôleur,
$scope.name = {};
$scope.registration = function() {
console.log("You will get the name here ", $scope.name);
};
ng-model
lie notre vue et la restitue dans l'expression {{ }}
.ng-model
représente les données présentées à l'utilisateur dans la vue et avec lesquelles l'utilisateur interagit.
Il est donc facile de lier des données dans AngularJs.
Voici un exemple de liaison de données avec AngularJS, utilisant un champ de saisie. Je vais expliquer plus tard
Code HTML
<div ng-app="myApp" ng-controller="myCtrl" class="formInput">
<input type="text" ng-model="watchInput" Placeholder="type something"/>
<p>{{watchInput}}</p>
</div>
Code AngularJS
myApp = angular.module ("myApp", []);
myApp.controller("myCtrl", ["$scope", function($scope){
//Your Controller code goes here
}]);
Comme vous pouvez le voir dans l'exemple ci-dessus, AngularJS utilise ng-model
pour écouter et regarder ce qui se passe sur les éléments HTML, en particulier sur les champs input
. Quand quelque chose arrive, fais quelque chose. Dans notre cas, ng-model
est lié à notre vue, en utilisant la notation de moustache {{}}
. Tout ce qui est saisi à l'intérieur du champ de saisie est affiché instantanément à l'écran. Et c’est la beauté de la liaison de données, en utilisant AngularJS dans sa forme la plus simple.
J'espère que cela t'aides.
Voir un exemple de travail ici sur Codepen
Angular.js crée un observateur pour chaque modèle que nous créons en vue. Chaque fois qu'un modèle est modifié, une classe "ng-dirty" est ajoutée au modèle, de sorte que l'observateur observe tous les modèles ayant la classe "ng-dirty" et met à jour leurs valeurs dans le contrôleur, et inversement.
liaison de données:
Qu'est-ce que la liaison de données?
Chaque fois que l'utilisateur modifie les données dans la vue, il se produit une mise à jour de cette modification dans le modèle d'étendue, et inversement.
Comment est-ce possible?
Réponse courte: À l'aide du cycle de digestion.
Description: Angular js définit l'observateur sur le modèle de portée, qui déclenche la fonction d'écoute en cas de modification du modèle.
$scope.$watch('modelVar' , function(newValue,oldValue){
// Code de mise à jour de Dom avec une nouvelle valeur
});
Alors, quand et comment appelle-t-on la fonction de surveillance?
La fonction Watcher est appelée dans le cadre du cycle de digestion.
Le cycle de résumé est appelé automatiquement déclenché dans le cadre de angular js dans des directives/services tels que ng-model, ng-bind, $ timeout, ng-click et autres .. qui vous permettent de déclencher le cycle de résumé.
Fonction du cycle de digestion:
$scope.$digest() -> digest cycle against the current scope.
$scope.$apply() -> digest cycle against the parent scope
i.e$rootScope.$apply()
Remarque: $ apply () est égal à $ rootScope. $ Digest () cela signifie que la vérification incorrecte commence à partir de la racine ou de la portée supérieure ou de la portée parent jusqu'à toutes les portées enfant $ de l'application angular js .
Les fonctionnalités ci-dessus fonctionnent dans les navigateurs IE pour les versions mentionnées, simplement en vérifiant que votre application est bien angular js, ce qui signifie que vous utilisez le fichier de script d'infrastructure angularjs référencé dans la balise script .
Je vous remercie.