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>
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.
Veja o exemplo em: http://plnkr.co/edit/Xe6ErQrg7WL6aAIoM9Gd?p=preview
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:
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:
Passe o valor atualizado como argumento para o retorno de chamada.
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.
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()" />
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.