Vérification sale dans AngularJS

Rana Hasnain Khan 31 mai 2022
Vérification sale dans AngularJS

Nous présenterons comment effectuer une vérification sale dans AngularJs.

Implémenter le Dirty Checking dans AngularJS

AngularJS effectue une vérification sale sur les variables $scope pour la liaison de données bidirectionnelle. Ember.js effectue une liaison de données bidirectionnelle en corrigeant par programme les setters et les getters, mais dans Angular, la vérification sale lui permet de rechercher des variables qui pourraient être disponibles ou non.

# AngularJs
$scope.$watch( wExp, listener, objEq );

La fonction $scope.$watch est utilisée lorsque nous voulons voir quand une variable est modifiée. Nous devons choisir trois arguments pour appliquer cette fonction : wExp est ce que nous voulons regarder, listener est ce que nous voulons qu’il fasse quand il est mis à jour, et si nous voulons vérifier une variable ou un objet.

Nous pouvons ignorer cela en utilisant cette fonction lorsque nous voulons vérifier une variable. Passons en revue un exemple comme indiqué ci-dessous.

#AngularJs
$scope.newName = 'Subhan';

$scope.$watch( function( ) {
    return $scope.name;
}, function( newVal, oldVal ) {
    console.log('$scope.newName is updated!');
} );

Angular enregistrera votre fonction watcher dans le $scope. Vous pouvez vérifier cela en connectant le $scope à la console.

Dans $watch, nous pouvons également utiliser une chaîne à la place d’une fonction. Cela fonctionnera également de la même manière qu’une fonction fonctionne.

Lorsque nous ajoutons une chaîne dans le code source d’Angular, le code sera :

#AngularJs
if (typeof wExp == 'string' && get.constant) {
  var newFn = watcher.fn;
  watcher.fn = function(newVal, oldVal, scope) {
    newFn.call(this, newVal, oldVal, scope);
    arrayRemove(array, watcher);
  };
}

En appliquant ce code, wExp sera défini comme une fonction. Cela désignera notre auditeur avec la variable avec notre nom choisi.

la fonction $watchers dans AngularJS

Tous les watchers sélectionnés seront stockés dans la variable $$watchers dans $scope. Vous trouverez un tableau d’objets lorsque nous cochez $$watchers, comme indiqué ci-dessous.

#AngularJs
$$watchers = [
    {
        eq: false,
        fn: function( newVal, oldVal ) {},
        last: 'Subhan',
        exp: function(){},
        get: function(){}
    }
];

La fonction unRegWatch est renvoyée par la fonction $watch. Cela montre que si nous voulons attribuer le $scope.$watch initial à une variable, nous pouvons facilement y parvenir en lui ordonnant d’arrêter de regarder.

Confirmez simplement que nous avons ouvert et examiné le premier $scope enregistré avant de déconnecter l’observateur.

la fonction $scope.$apply dans AngularJS

Lorsque nous essayons d’exécuter controller/directive/etc., Angular exécute une fonction dans laquelle nous appelons $scope.$watch. Avant d’administrer la fonction $digest dans le rootScope, la fonction $apply exécutera une fonction que nous avons choisie.

La fonction Angular $apply est la suivante :

#AngularJs
$apply: function(expr) {
    try {
      beginPhase('$apply');
      return this.$eval(expr);
    } catch (e) {
      $exceptionHandler(e);
    } finally {
      clearPhase();
      try {
        $rootScope.$digest();
      } catch (e) {
        $exceptionHandler(e);
        throw e;
      }
    }
}

l’argument expr dans AngularJS

Lors de l’utilisation de $scope.$apply, parfois Angular ou nous ferons passer une fonction, l’argument expr est cette fonction.

Lors de l’utilisation de cette fonction, nous ne trouverons pas le besoin d’utiliser $apply le plus souvent. Voyons ce qui se passe lorsque ng-keydown utilise $scope.$apply.

Pour soumettre la directive, nous utilisons le code suivant.

#AngularJs
var EventNgDirective = {};
forEach(
  keydown keyup keypress submit focus blur copy cut paste'.split(' '),
  function(newName) {
    var newDirectiveName = newDirectiveNormalize('ng-' + newName);
    EventNgDirective[newDirectiveName] = ['$parse', function($parse) {
      return {
        compile: function($element, attr) {
          var fn = $parse(attr[directiveName]);
          return function ngEventHandler(scope, element) {
            element.on(lowercase(newName), function(event) {
              scope.$apply(function() {
                fn(scope, {$event:event});
              });
            });
          };
        }
      };
    }];
  }
);

Ce code bouclera sur les différents types d’événements qui peuvent être déclenchés, générant une nouvelle directive appelée ng(eventName). Lorsque la fonction de compilation de la directive est concernée, un gestionnaire d’événements est enregistré sur l’élément, où l’événement contient le nom de la directive.

Lorsque cet événement est déclenché, $scope.$apply sera exécuté par Angular, lui offrant une fonction à exécuter.

C’est ainsi que la valeur $scope sera mise à jour avec la valeur de l’élément, mais cette liaison n’est qu’une liaison à sens unique. La raison en est que nous avons appliqué ng-keydown, qui nous permet de changer uniquement lorsque cet événement est appliqué.

En conséquence, une nouvelle valeur est obtenue!

Notre objectif principal est d’atteindre une liaison de données bidirectionnelle. Pour atteindre cet objectif, nous pouvons utiliser le ng-model.

Pour que ng-model fonctionne, il utilise à la fois $scope.$watch et $scope.$apply .

L’entrée que nous avons donnée rejoindra le gestionnaire d’événements par ng-model. À ce stade, $scope.$watch est appelé !

$scope.$watch est appelé dans le contrôleur de la directive.

#AngularJs
$scope.$watch(function ngModelWatch() {
    var value = ngModelGet($scope);

    if (ctrl.$modelValue !== value) {

        var formatters = ctrl.$formatters,
            idx = formatters.length;

        ctrl.$modelValue = value;
        while(idx--) {
            value = formatters[idx](value);
        }

        if (ctrl.$viewValue !== value) {
            ctrl.$viewValue = value;
            ctrl.$render();
        }
    }

    return value;
});

Lorsqu’un seul argument est utilisé lors de la définition de $scope.$watch, la fonction que nous avons choisie sera appliquée même si elle est mise à jour ou non. La fonction présente dans le ng-model examine si le modèle et la vue sont synchronisés.

S’il n’est pas synchronisé, la fonction donnera au modèle une nouvelle valeur et la mettra à jour. Cette fonction nous permet de savoir quelle est la nouvelle valeur en renvoyant la nouvelle valeur lorsque nous exécutons cette fonction dans $digest.

Pourquoi l’auditeur n’est pas renvoyé

Revenons au point de l’exemple où nous avons désenrôlé la fonction $scope.$watch dans la fonction similaire telle que nous l’avons décrite. Nous pouvons maintenant reconnaître la raison pour laquelle nous n’avons pas été informés de la mise à jour de $scope.name même lorsque nous l’avons mis à jour.

Comme nous le savons, $scope.$apply est exécuté par Angular sur chaque fonction de contrôleur de directive. La fonction $scope.$apply exécutera $digest uniquement à une condition lorsque nous aurons estimé la fonction de contrôleur de la directive.

Cela signifie que nous avons désinscrit la fonction $scope.$watch avant qu’elle n’ait pu être exécutée, il y a donc peu ou pas de chance qu’elle soit appelée.

la fonction $digest dans AngularJS

Vous pouvez appeler cette fonction sur le $rootScope par $scope.$apply. Nous pouvons exécuter le cycle de digestion sur le $rootScope, puis il passera sur les portées et exécutera à la place le cycle de digestion.

Le cycle de résumé déclenchera toutes nos fonctions wExp dans la variable $$watchers. Il les comparera à la dernière valeur connue.

Si les résultats sont négatifs, l’auditeur sera renvoyé.

Dans le cycle de digestion, lorsqu’il s’exécute, il boucle deux fois. Une fois, il bouclera sur les observateurs, puis bouclera à nouveau jusqu’à ce que le cycle ne soit plus sale.

Lorsque le wExp et la dernière valeur connue ne sont pas équivalents, on dit que le cycle est sale. Habituellement, ce cycle s’exécutera une fois et donnera une erreur s’il s’exécute plus de dix fois.

Angular peut exécuter $scope.$apply sur tout ce qui comporte une possibilité de changement de valeur de modèle.

Vous devez exécuter $scope.$apply(); lorsque nous avons mis à jour $scope en dehors d’Angular. Cela informera Angular que la portée a été mise à jour.

Concevons une version de base de la vérification sale.

Toutes les données que nous souhaitons sauvegarder seront dans la fonction Scope. Nous allons développer l’objet sur une fonction pour dupliquer $digest et $watch.

Comme nous n’avons besoin d’évaluer aucune fonction par rapport à Scope, nous n’utiliserons pas $apply. Nous utiliserons directement $digest.

Voici à quoi ressemblera notre Scope :

#AngularJs
var Scope = function( ) {
    this.$$watchers = [];
};

Scope.prototype.$watch = function( ) {

};

Scope.prototype.$digest = function( ) {

};

Il y a deux paramètres, wExp et listener, que $watch doit adopter. Nous pouvons définir les valeurs dans Scope.

Lorsque $watch est appelé, nous les envoyons dans la valeur $$watcher précédemment stockée dans Scope.

#AngularJs
var Scope = function( ) {
    this.$$watchers = [];
};

Scope.prototype.$watch = function( wExp, listener ) {
    this.$$watchers.push( {
        wExp: wExp,
        listener: listener || function() {}
    } );
};

Scope.prototype.$digest = function( ) {

};

Ici, nous remarquerons que listener est défini sur une fonction vide.

Vous pouvez facilement enregistrer un $watch pour toutes les variables lorsqu’aucun écouteur n’est proposé en suivant cet exemple.

Nous allons maintenant opérer $digest. Nous pouvons vérifier si l’ancienne valeur et la nouvelle valeur sont équivalentes.

Nous pouvons également renvoyer l’écouteur s’il n’est pas déjà déclenché. Pour y parvenir, nous allons alors boucler jusqu’à ce qu’ils soient équivalents.

La variable dirtyChecking nous a aidés à atteindre cet objectif, que les valeurs soient égales ou non.

#AngularJs
var Scope = function( ) {
    this.$$watchers = [];
};

Scope.prototype.$watch = function( wExp, listener ) {
    this.$$watchers.push( {
        wExp: wExp,
        listener: listener || function() {}
    } );
};

Scope.prototype.$digest = function( ) {
    var dirtyChecking;

    do {
            dirtyChecking = false;

            for( var i = 0; i < this.$$watchers.length; i++ ) {
                var newVal = this.$$watchers[i].wExp(),
                    oldVal = this.$$watchers[i].last;

                if( oldVal !== newVal ) {
                    this.$$watchers[i].listener(newVal, oldVal);

                    dirtyChecking = true;

                    this.$$watchers[i].last = newVal;
                }
            }
    } while(dirtyChecking);
};

Maintenant, nous allons concevoir un nouvel exemple de notre portée. Nous l’attribuerons à $scope ; ensuite, nous enregistrerons une fonction de montre.

Ensuite, nous le mettrons à jour et le digérerons.

#AngularJs
var Scope = function( ) {
    this.$$watchers = [];
};

Scope.prototype.$watch = function( wExp, listener ) {
    this.$$watchers.push( {
        wExp: wExp,
        listener: listener || function() {}
    } );
};

Scope.prototype.$digest = function( ) {
    var dirtyChecking;

    do {
            dirtyChecking = false;

            for( var i = 0; i < this.$$watchers.length; i++ ) {
                var newVal = this.$$watchers[i].wExp(),
                    oldVal = this.$$watchers[i].last;

                if( oldVal !== newVal ) {
                    this.$$watchers[i].listener(newVal, oldVal);

                    dirtyChecking = true;

                    this.$$watchers[i].last = newVal;
                }
            }
    } while(dirtyChecking);
};


var $scope = new Scope();

$scope.newName = 'Subhan';

$scope.$watch(function(){
    return $scope.newName;
}, function( newVal, oldVal ) {
    console.log(newVal, oldVal);
} );

$scope.$digest();

Nous avons implémenté avec succès la vérification sale. Vous observerez ses journaux lorsque nous observerons la console.

#AngularJs
Subhan undefined

Auparavant, $scope.newName n’était pas défini. Nous l’avons corrigé pour Subhan.

Nous allons connecter la fonction $digest à un événement keyup sur une entrée. En faisant cela, nous n’avons pas à le désigner nous-mêmes.

Cela signifie que nous pouvons également atteindre une liaison de données bidirectionnelle.

#AngularJs
var Scope = function( ) {
    this.$$watchers = [];
};

Scope.prototype.$watch = function( wExp, listener ) {
    this.$$watchers.push( {
        wExp: wExp,
        listener: listener || function() {}
    } );
};

Scope.prototype.$digest = function( ) {
    var dirty;

    do {
            dirtyChecking = false;

            for( var i = 0; i < this.$$watchers.length; i++ ) {
                var newVal = this.$$watchers[i].wExp(),
                    oldVal = this.$$watchers[i].last;

                if( oldVal !== newVal ) {
                    this.$$watchers[i].listener(newVal, oldVal);

                    dirtyChecking = true;

                    this.$$watchers[i].last = newVal;
                }
            }
    } while(dirtyChecking);
};


var $scope = new Scope();

$scope.newName = 'Subhan';

var element = document.querySelectorAll('input');

element[0].onkeyup = function() {
    $scope.newName = element[0].value;

    $scope.$digest();
};

$scope.$watch(function(){
    return $scope.newName;
}, function( newVal, oldVal ) {
    console.log('Value updated In Input - The new value is ' + newVal);

    element[0].value = $scope.newName;
} );

var updateScopeValue = function updateScopeValue( ) {
    $scope.name = 'Butt';
    $scope.$digest();
};

Avec cette technique, nous pouvons facilement mettre à jour la valeur de l’entrée, comme on le voit dans le $scope.newName. Vous pouvez également appeler updateScopeValue, et la valeur de l’entrée le montrera.

Rana Hasnain Khan avatar Rana Hasnain Khan avatar

Rana is a computer science graduate passionate about helping people to build and diagnose scalable web application problems and problems developers face across the full-stack.

LinkedIn