Angular.js: Onchange está sendo acionado antes da atualização do modelo para escopo isolado

Criado em 21 out. 2013  ·  11Comentários  ·  Fonte: angular/angular.js

O evento Onchange para os elementos com escopo isolado está sendo disparado antes da atualização do modelo.

Exemplo:

<!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

Comentários muito úteis

Narretz, discordo veementemente. Existe um contrato feito com o desenvolvedor consumindo o código que a ligação bidirecional tem precedência. Quando uma alteração é feita em qualquer lugar ao longo da cadeia de ligação, toda a cadeia deve ser atualizada antes de continuar. Portanto, mesmo se outra diretiva herdar desta diretiva, e algo mais abaixo se vincular a uma vinculação de uma ligação ... se esse modelo for atualizado, o modelo de escopo do controlador original na parte inferior da pilha deve ser atualizado no mesmo ciclo de resumo antes continuando para outras funções. É o que se espera do consumidor.

Todos 11 comentários

Caso interessante ... http://plnkr.co/edit/lsrga8qMcbgNWATrVht3?p=preview

Isso acontece apenas quando a função onChange é delegada ao escopo pai com a vinculação '&'.

Temos trabalhado em uma ferramenta RAD baseada no angularjs.
Esse era um bug importante, pois todos os widgets do aplicativo tinham isolateScope.
Resolvemos o problema criando um método proxy para onChange.

Dê uma olhada em: https://studio.wavemaker.com/

Atenciosamente, Vinay

Você poderia postar um exemplo desse proxy e como ele é usado? Pode ajudar na depuração disso.

Será consertado em breve? É tãããão confuso.

Após uma consideração mais detalhada, devo dizer que esse é o comportamento esperado. No exemplo do plnkr, o valor do ngModel é uma string de dois limites entre o controlador e o escopo da diretiva. Agora, quando você está mudando o valor na diretiva, o ngModel atualizará o escopo _isolate_ primeiro e disparará o ngChange no mesmo resumo. Visto que a ligação bidirecional ainda não detectou uma mudança no escopo da diretiva, o ngChange no escopo pai obtém o valor "antigo" de x . Agora, o observador de ligação bidirecional precisa de pelo menos dois ciclos de resumo para estabilizar o valor, portanto, o escopo do controlador só é atualizado após o ngChange ter sido chamado.
Concordo que isso é confuso, mas é assim que funciona. Você pode contornar isso vinculando um objeto à diretiva que contém o valor que você vinculou ao ngModel. Dessa forma, o valor é definido por referência no escopo isolado e no escopo do controlador ao mesmo tempo e disponível quando o ngChange é executado: http://plnkr.co/edit/fs7S6yX1a5aeo1Ese522?p=preview

Narretz, discordo veementemente. Existe um contrato feito com o desenvolvedor consumindo o código que a ligação bidirecional tem precedência. Quando uma alteração é feita em qualquer lugar ao longo da cadeia de ligação, toda a cadeia deve ser atualizada antes de continuar. Portanto, mesmo se outra diretiva herdar desta diretiva, e algo mais abaixo se vincular a uma vinculação de uma ligação ... se esse modelo for atualizado, o modelo de escopo do controlador original na parte inferior da pilha deve ser atualizado no mesmo ciclo de resumo antes continuando para outras funções. É o que se espera do consumidor.

Ligeiramente relacionado e igualmente confuso. Ao usar ngChange para acionar a validação em outro campo que compartilha um ngModel , a alteração é acionada antes de $modelValue ser atualizado. Tornando a validação complicada.

Usando $timeout como uma solução alternativa. Ainda confuso por que isso é necessário.

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

cumprimentos Jakob

Existem planos para rever este problema? Talvez reabrir?

Encontrei esse problema e li os comentários, embora a maneira como funciona atualmente devido ao escopo isolado faça sentido; o resultado que vem de esperar um modelo atualizado quando a chamada on-change dispara e obter o modelo uma iteração anterior definitivamente não é o comportamento que você espera como desenvolvedor.

Eu sigo a pergunta, isso vai ser revisitado?

Acho que não. O comportamento atual é esperado e, embora não seja desejável em alguns casos, há pelo menos tantos casos em que é. Mudá-lo agora (a) seria uma mudança significativa e (b) tornaria muitos casos de uso não suportados (por exemplo, onde você deseja que ngChange modifique o valor _antes_ de atualizar o pai). É um daqueles casos em que você não pode deixar todos felizes, então temos que ficar com a versão que:

  • Faz sentido.
  • Não quebra os usuários existentes.
  • Suporta casos de uso mais específicos / avançados, mesmo que ao custo de um pequeno código extra (veja abaixo).

A implementação atual de ngChange está fortemente ligada ao conceito de ngModel . A vinculação de diretivas é um conceito totalmente independente. Os dois podem funcionar bem juntos, mas não devem afetar a implementação / funcionamento interno um do outro.

#

Conforme já mencionado, se seu retorno de chamada específico precisar de acesso ao valor atualizado, existem algumas opções:

  1. Passe o valor atualizado como argumento para o retorno de chamada.

  2. Se você precisar que ele seja executado de forma assíncrona (ou seja, depois que o resumo terminar e todas as ligações tiverem sido propagadas), você sempre pode usar $timeout dentro do retorno de chamada.

  3. Se você quiser uma versão "assíncrona" de ngChange , também é trivial implementar como uma diretiva personalizada. (Você também pode sobrescrever a diretiva integrada, mas eu definitivamente não recomendaria isso.) Por exemplo:

.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()" />
Esta página foi útil?
0 / 5 - 0 avaliações