AngularJS: ligação do modelo ng não é atualizada quando alterada com jQuery

98

Este é o meu HTML:

<input id="selectedDueDate" type="text" ng-model="selectedDate" />

Quando digito na caixa, o modelo é atualizado por meio do mecanismo de ligação bidirecional. Doce.

No entanto, quando faço isso via JQuery ...

$('#selectedDueDate').val(dateText);

Não atualiza o modelo. Por quê?

Greg
fonte
1
Por que você faria o segundo para começar? Você está usando uma estrutura e decide contorná-la e definir o valor por meio da manipulação do DOM. O melhor conselho que se pode dar com o Angular para iniciantes: esqueça que o jquery existe. em 90% dos casos, angular será suficiente e os 10% restantes podem ser obtidos por meio do jqlite dentro de um link de diretiva (o elemento é na verdade um elemento empacotado do jqlite pronto para ser manipulado).
Anulado em
1
pergunta muito importante
Syed Md. Kamruzzaman
há boas razões pelas quais você pode querer mudar os modelos angulares com manipulação de dom, talvez você seja um desenvolvedor trabalhando com uma ferramenta de teste A / B e queira preencher formulários nos bastidores, ou você esteja trabalhando em um script greasemonkey para preencher automaticamente os formulários.
Shadaez

Respostas:

134

Angular não sabe dessa mudança. Para isso, você deve ligar $scope.$digest()ou fazer a alteração dentro de $scope.$apply():

$scope.$apply(function() { 
   // every changes goes here
   $('#selectedDueDate').val(dateText); 
});

Veja isto para entender melhor a verificação suja

ATUALIZAÇÃO : Aqui está um exemplo

Renan Tomal Fernandes
fonte
Fiz isso como você diz: fiddle.jshell.net/AladdinMhaimeed/agvTz/8, mas não funciona
Aladdin Mhemed
1
Veja este fiddle.jshell.net/agvTz/38 Você deve chamar o function$ scope. $ Apply passando uma função como argumento. E $ apply deve ser chamado quando você fizer as alterações em $ scope, portanto, dentro de onSelect. Ah, espero que você coloque a manipulação do DOM dentro do controlador apenas para o exemplo, em um aplicativo real isso está errado;)
Renan Tomal Fernandes 16/10/12
então o que devo fazer para ter uma boa prática angular? separar a manipulação de DOM em uma diretiva?
Aladdin Mhemed,
2
Sim, aqui um exemplo fiddle.jshell.net/agvTz/39 a diretiva pode ser usada quantas vezes você quiser com um simples datepicker="scopeKeyThatYouWant"na entrada
Renan Tomal Fernandes
Basta chamar .. $ scope. $ Digest () para estabelecer. vai desacelerar?
Thant Zin
29

Apenas use;

$('#selectedDueDate').val(dateText).trigger('input');
Jan Kuri
fonte
Eu usei trigger('input')com um selecionador de data em seu onSelectevento. Ele atualiza o valor corretamente.
iman
Isso funcionou para mim. Eu estava atualizando o valor de uma entrada associada de um teste de diretiva e envolver a .val('something'chamada em um $apply(ou chamar $digestdepois) não funcionou.
Tom Seldon
2
Depois de horas de procura, esta foi a que funcionou! Obrigado!
Dan,
Alhamdulillah, perfeito, está funcionando para mim. obrigado por economizar minhas horas
Syed Md. Kamruzzaman
Verdade. Eu estava usando o $('#selectedDueDate').val(dateText).trigger('change');que não estava funcionando para mim. .trigger('input');funciona como mágica
jafarbtech
17

Descobri que, se você não colocar a variável diretamente no escopo, ela será atualizada de forma mais confiável.

Tente usar algum "dateObj.selectedDate" e no controlador adicione a selectedDate a um objeto dateObj da seguinte forma:

$scope.dateObj = {selectedDate: new Date()}

Isso funcionou para mim.

Ben Taliadoros
fonte
4
Isso funcionou para mim também. Eu perdi mais de 2 horas tentando descobrir por que a ligação bidirecional não estava funcionando. Funcionou bem na página, mas o escopo no controlador não estava sendo atualizado. Então tentei sua abordagem por desespero (porque não fazia sentido para mim que isso fosse verdade) e, caramba, funcionou! Obrigado!
TMc
3
O mesmo aqui - algum guru do angularJs pode explicar por que isso funciona? É como se a visão tivesse seu próprio escopo aninhado e às vezes você fica preso atualizando apenas o escopo da visão, não o escopo do controlador
Tom Carver
1
Funciona bem para mim! Eu me pergunto por que diabos não funciona da mesma forma no escopo simples.
Stephen de
1
Tem pouco a ver com angular e muito a ver com o funcionamento do javascript. Quando você atribui a variável de escopo a um objeto, você a atribui por referência, em oposição a por valor, como feito quando uma var é definida como igual a um valor primitivo. Falei sobre isso no meu post aqui. stackoverflow.com/questions/14527377/… . Eu referenciei este golpe que fiz naquele post para ilustrar plnkr.co/edit/WkCT6aYao3qCmzJ8t5xg?p=preview .
Nick Brady,
4

Tente isto

var selectedDueDateField = document.getElementById("selectedDueDate");
var element = angular.element(selectedDueDateField);
element.val('new value here');
element.triggerHandler('input');
Raio
fonte
É utilizável quando você não tem acesso ao $ escopo do controlador do angular. Quando você escreve um script com uma Tampermonkey, por exemplo.
wassertim
3

Basta executar a seguinte linha no final de sua função:

$ scope. $ apply ()

Rohan M Nabar
fonte
2

Aconteça o que acontecer fora do escopo do Angular, o Angular nunca saberá disso.

O ciclo Digest coloca as alterações do modelo -> controlador e, em seguida, do controlador -> modelo.

Se você precisa ver o modelo mais recente, você precisa acionar o ciclo de resumo

Mas existe uma chance de um ciclo de resumo em andamento, então precisamos verificar e inicializar o ciclo.

De preferência, sempre execute uma aplicação segura.

       $scope.safeApply = function(fn) {
            if (this.$root) {
                var phase = this.$root.$$phase;
                if (phase == '$apply' || phase == '$digest') {
                    if (fn && (typeof (fn) === 'function')) {
                        fn();
                    }
                } else {
                    this.$apply(fn);
                }
            }
        };


      $scope.safeApply(function(){
          // your function here.
      });
Rajendra kumar Vankadari
fonte
1

AngularJS passa string, números e booleanos por valor enquanto passa arrays e objetos por referência. Assim, você pode criar um objeto vazio e tornar sua data uma propriedade desse objeto. Dessa forma, o angular detectará mudanças no modelo.

No controlador

app.module('yourModule').controller('yourController',function($scope){
$scope.vm={selectedDate:''}
});

Em html

<div ng-controller="yourController">
<input id="selectedDueDate" type="text" ng-model="vm.selectedDate" />
</div>
Himanshu Mittal
fonte
1

Você tem que acionar o evento de mudança do elemento de entrada porque o modelo ng escuta os eventos de entrada e o escopo será atualizado. No entanto, o gatilho jQuery regular não funcionou para mim. Mas aqui está o que funciona como um encanto

$("#myInput")[0].dispatchEvent(new Event("input", { bubbles: true })); //Works

Seguir não funcionou

$("#myInput").trigger("change"); // Did't work for me

Você pode ler mais sobre como criar e despachar eventos sintéticos .

Hari Das
fonte
0

Eu escrevi este pequeno plugin para jQuery que fará todas as chamadas para .val(value)atualizar o elemento angular, se presente:

(function($, ng) {
  'use strict';

  var $val = $.fn.val; // save original jQuery function

  // override jQuery function
  $.fn.val = function (value) {
    // if getter, just return original
    if (!arguments.length) {
      return $val.call(this);
    }

    // get result of original function
    var result = $val.call(this, value);

    // trigger angular input (this[0] is the DOM object)
    ng.element(this[0]).triggerHandler('input');

    // return the original result
    return result; 
  }
})(window.jQuery, window.angular);

Basta inserir este script após jQuery e angular.js e as val(value)atualizações devem funcionar bem.


Versão reduzida:

!function(n,t){"use strict";var r=n.fn.val;n.fn.val=function(n){if(!arguments.length)return r.call(this);var e=r.call(this,n);return t.element(this[0]).triggerHandler("input"),e}}(window.jQuery,window.angular);

Exemplo:


Esta resposta foi copiada literalmente de minha resposta a outra pergunta semelhante .

dav_i
fonte
0

Apenas use:

$('#selectedDueDate').val(dateText).trigger('input');

ao invés de:

$('#selectedDueDate').val(dateText);
Wekerle Tibor
fonte