Angular.js: Onchange est déclenché avant la mise à jour du modèle pour la portée d'isolat

Créé le 21 oct. 2013  ·  11Commentaires  ·  Source: angular/angular.js

L'événement Onchange pour les éléments ayant une portée d'isolat est déclenché avant la mise à jour du modèle.

Exemple:

<!doctype html>
<html ng-app="App">
    <head>

        <link rel="stylesheet" href="http://code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css" />

        <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.js"></script>
        <script src="http://code.angularjs.org/1.0.6/angular.js"></script>
        <script src="http://code.jquery.com/ui/1.10.3/jquery-ui.js"></script>

        <script type="text/javascript">
            "use strict"

            var App = angular.module('App', []);
            App.controller('Ctrl', function ($scope,$timeout) {

                $scope.change1 = function () {
                    console.warn("inside change", $scope.y);
                }

                $scope.change2 = function () {
                    console.warn("inside change", $scope.x);    
                }

            });

            App.directive("wminput", function () {
                return {
                    restrict: "E",
                    replace: true,
                    scope: {
                        "onChange":"&",
                        "value": "="
                    },
                    template: '<input type="text" data-ng-model="value" data-ng-change="onChange();">'
                }
            });

        </script>

    </head>
    <body>
        <div data-ng-controller="Ctrl">
            "Onchange will be triggered after the model update"
            <input data-ng-change="change1()" data-ng-model="y">
            <br><br>
            "Onchange will be triggered before the model update"
            <wminput on-change="change2()" value="x"></wminput>
        </div>

    </body>
</html>
$compile forms low works as expected confusing bug

Commentaire le plus utile

Narretz, je ne suis pas du tout d'accord. Il existe un contrat conclu avec le développeur qui utilise le code selon lequel la liaison bidirectionnelle a la priorité. Lorsqu'un changement est effectué n'importe où le long de la chaîne de liaison, la chaîne entière doit être mise à jour avant de continuer. Donc, même si une autre directive hérite de cette directive, et quelque chose de plus bas se lie à une liaison d'une liaison ... si ce modèle est mis à jour, le modèle de portée du contrôleur d'origine au bas de la pile doit être mis à jour dans le même cycle de résumé avant continuer avec d'autres fonctions. C'est ce que l'on attend du consommateur.

Tous les 11 commentaires

Cas de coin intéressant ... http://plnkr.co/edit/lsrga8qMcbgNWATrVht3?p=preview

Cela se produit uniquement lorsque la fonction onChange est déléguée à la largeur de portée parent de la liaison «&».

Nous avons travaillé sur un outil RAD basé sur angularjs.
C'était un bogue majeur car chaque widget de l'application avait isolateScope.
Nous avons résolu le problème en créant une méthode proxy pour onChange.

Jetez un œil à: https://studio.wavemaker.com/

Cordialement, Vinay

Pourriez-vous publier un exemple de ce proxy et comment il est utilisé? Pourrait aider à déboguer ceci.

Consultez l'exemple à l' adresse :

Sera-t-il corrigé de sitôt? C'est tellement déroutant.

Après un examen plus approfondi, je dois dire que c'est un comportement attendu. Dans l'exemple plnkr, la valeur du ngModel est une chaîne à deux liaisons entre le contrôleur et la portée de la directive. Désormais, lorsque vous modifiez la valeur de la directive, ngModel met d'abord à jour la portée _isolate_ et déclenche le ngChange dans le même condensé. Puisque la liaison bidirectionnelle n'a pas encore pris en compte une modification de la portée de la directive, le ngChange dans la portée parent obtient la "vieille" valeur de x . Maintenant, l'observateur de liaison bidirectionnelle a besoin d'au moins deux cycles de résumé pour stabiliser la valeur, de sorte que la portée du contrôleur n'est mise à jour qu'après l'appel de ngChange.
Je reconnais que c'est déroutant, mais c'est comme ça que ça marche. Vous pouvez contourner ce problème en liant un objet à la directive qui contient la valeur que vous liez à ngModel. De cette façon, la valeur est définie par référence à la fois dans l'isolat et dans la portée du contrôleur en même temps et disponible lorsque ngChange est exécuté: http://plnkr.co/edit/fs7S6yX1a5aeo1Ese522?p=preview

Narretz, je ne suis pas du tout d'accord. Il existe un contrat conclu avec le développeur qui utilise le code selon lequel la liaison bidirectionnelle a la priorité. Lorsqu'un changement est effectué n'importe où le long de la chaîne de liaison, la chaîne entière doit être mise à jour avant de continuer. Donc, même si une autre directive hérite de cette directive, et quelque chose de plus bas se lie à une liaison d'une liaison ... si ce modèle est mis à jour, le modèle de portée du contrôleur d'origine au bas de la pile doit être mis à jour dans le même cycle de résumé avant continuer avec d'autres fonctions. C'est ce que l'on attend du consommateur.

Légèrement lié, et également déroutant. Lorsque vous utilisez ngChange pour déclencher la validation sur un autre champ qui partage un ngModel , le changement est déclenché avant la mise à jour de $modelValue . Rendre la validation compliquée.

Utilisation de $timeout comme solution de contournement. Je ne sais toujours pas pourquoi c'est nécessaire.

Exemple:
http://codepen.io/jakobadam/pen/qmBdrJ?editors=1010

salutations, Jakob

Y a-t-il des plans pour revoir ce problème? Peut-être rouvrir?

Je suis tombé sur ce problème et j'ai lu les commentaires, alors que la façon dont cela fonctionne actuellement en raison de la portée d'isolat a du sens; le résultat qui vient de l'attente d'un modèle mis à jour lorsque l'appel lors du changement se déclenche et de l'obtention du modèle une itération précédente n'est certainement pas le comportement que vous attendez en tant que développeur.

J'adhère à la question, est-ce que ça va être revisité?

Je ne pense pas. Le comportement actuel est attendu et bien qu'il ne soit pas souhaitable dans certains cas, il y a au moins autant de cas où il l'est. Le changer maintenant serait (a) un changement de rupture et (b) rendrait de nombreux cas d'utilisation non supportés (par exemple où vous voulez que ngChange modifie la valeur _avant_ il met à jour le parent). C'est l'un de ces cas où tout le monde ne peut pas être satisfait, nous devons donc nous en tenir à la version qui:

  • Logique.
  • Ne casse pas les utilisateurs existants.
  • Prend en charge des cas d'utilisation plus spécifiques / avancés, même si cela coûte peu de code supplémentaire (voir ci-dessous).

L'implémentation actuelle de ngChange est étroitement liée au concept de ngModel . Les liaisons de directive sont un concept totalement indépendant. Les deux peuvent bien fonctionner ensemble, mais ne devraient pas affecter la mise en œuvre / le fonctionnement interne l'un de l'autre.

#

Comme déjà mentionné, si votre rappel spécifique a besoin d'accéder à la valeur mise à jour, il existe quelques options:

  1. Passez la valeur mise à jour comme argument au rappel.

  2. Si vous avez besoin qu'il soit exécuté de manière asynchrone (c'est-à-dire après la fin du résumé et la propagation de toutes les liaisons), vous pouvez toujours utiliser $timeout dans le rappel.

  3. Si vous voulez une version "asynchrone" de ngChange , il est également simple de l'implémenter en tant que directive personnalisée. (Vous pouvez également écraser la directive intégrée, mais je ne le recommanderais certainement pas.) Par exemple:

.directive('myAsyncChange', function($timeout) {
  return {
    restrict: 'A',
    require: 'ngModel',
    link: function(scope, element, attr, ctrl) {
      ctrl.$viewChangeListeners.push(function() {
        $timeout(function() {
          scope.$eval(attr.myAsyncChange);
        });
      });
    }
  };
});
<input ng-model="foo" my-async-change="onChange()" />
Cette page vous a été utile?
0 / 5 - 0 notes