$ assiste a um objeto

317

Quero observar as alterações em um dicionário, mas, por algum motivo, o retorno de chamada do relógio não é chamado.

Aqui está um controlador que eu uso:

function MyController($scope) {
    $scope.form = {
        name: 'my name',
        surname: 'surname'
    }

    $scope.$watch('form', function(newVal, oldVal){
        console.log('changed');
    });
}

Aqui está o violino .

Espero que o retorno de chamada $ watch seja acionado sempre que o nome ou sobrenome for alterado, mas isso não acontece.

Qual é a maneira correta de fazer isso?

Vladimir Sidorenko
fonte
1
possível duplicata de How to deep watch a array in angularjs?
lort

Respostas:

570

Ligue $watchcom trueo terceiro argumento:

$scope.$watch('form', function(newVal, oldVal){
    console.log('changed');
}, true);

Por padrão, ao comparar dois objetos complexos em JavaScript, eles serão verificados quanto à igualdade de "referência", que pergunta se os dois objetos se referem à mesma coisa, em vez de igualdade de "valor", que verifica se os valores de todas as propriedades daquelas objetos são iguais.

De acordo com a documentação angular , o terceiro parâmetro é para objectEquality:

Quando objectEquality == true, a desigualdade da watchExpression é determinada de acordo com a angular.equalsfunção. Para salvar o valor do objeto para comparação posterior, a angular.copyfunção é usada. Portanto, isso significa que a observação de objetos complexos terá implicações adversas na memória e no desempenho.

rossipedia
fonte
2
Esta é uma boa resposta, mas eu imagino que há momentos em que você não gostaria de assistir de forma recursiva um objeto
Jason
16
Tem. Por padrão, se você não passar true, ele verificará a igualdade de referência se o valor monitorado for um objeto ou matriz. Nesse caso, você deve especificar as propriedades de forma explícita, como $scope.$watch('form.name')e$scope.$watch('form.surname')
rossipedia
3
Obrigado. Foi exatamente o que eu perdi. Jason, você está certo - você nem sempre quer objetos recursivos assistindo, mas no meu caso, era exatamente o que eu precisava.
Vladimir Sidorenko
1
O JS imutável e a comparação de referência podem ajudar a aumentar o desempenho.
Dragos Rusu
13

O formobjeto não está sendo alterado, apenas a namepropriedade é

violino atualizado

function MyController($scope) {
$scope.form = {
    name: 'my name',
}

$scope.changeCount = 0;
$scope.$watch('form.name', function(newVal, oldVal){
    console.log('changed');
    $scope.changeCount++;
});
}
Jason
fonte
12

Pouca dica de desempenho se alguém tiver um tipo de serviço de armazenamento de dados com pares chave -> valor:

Se você tiver um serviço chamado dataStore , poderá atualizar um carimbo de data / hora sempre que seu objeto de big data for alterado. Dessa forma, em vez de observar profundamente o objeto inteiro, você está observando apenas um carimbo de data e hora para alterações.

app.factory('dataStore', function () {

    var store = { data: [], change: [] };

    // when storing the data, updating the timestamp
    store.setData = function(key, data){
        store.data[key] = data;
        store.setChange(key);
    }

    // get the change to watch
    store.getChange = function(key){
        return store.change[key];
    }

    // set the change
    store.setChange = function(key){
        store.change[key] = new Date().getTime();
    }

});

E em uma diretiva, você está apenas observando o carimbo de data e hora para mudar

app.directive("myDir", function ($scope, dataStore) {
    $scope.dataStore = dataStore;
    $scope.$watch('dataStore.getChange("myKey")', function(newVal, oldVal){
        if(newVal !== oldVal && newVal){
            // Data changed
        }
    });
});
Ferenc Takacs
fonte
1
Acho que vale a pena mencionar que, nesse caso, você perderia a funcionalidade de ter acesso ao valor antigo dos dados que está armazenando. newVal, oldValneste exemplo, os valores de carimbo de data e hora e não os valores de objetos observados. Não invalida esta resposta, apenas acho que é uma desvantagem que vale a pena mencionar.
Michał Schielmann 14/11
É verdade que esse método é usado principalmente quando quero carregar uma estrutura de dados complicada como fonte de alguma coisa (uma nova versão de alguns dados, por exemplo). Se eu me preocupo com valores novos e antigos, não os armazenaria em objetos grandes e complicados; em vez disso, eu teria uma pequena representação de texto daquilo com o qual me preocupo e observo isso para alterar ou obtê-lo quando o carimbo de data e hora muda.
Ferenc Takacs
5

O motivo pelo qual seu código não funciona é porque, $watchpor padrão, a verificação de referência. Então, em poucas palavras, certifique-se de que o objeto que é passado para ele seja um novo objeto. Mas no seu caso, você está apenas modificando alguma propriedade do objeto de formulário e não criando uma nova. Para fazê-lo funcionar, você pode passar truecomo o terceiro parâmetro.

$scope.$watch('form', function(newVal, oldVal){
    console.log('invoked');
}, true);

Funcionará, mas você pode usar $ watchCollection, que será mais eficiente do que $ watch, porque $watchCollectionobservará propriedades rasas no objeto de formulário. Por exemplo

$scope.$watchCollection('form', function (newVal, oldVal) {
    console.log(newVal, oldVal);
});
sol4me
fonte
4

Enquanto você procura alterações no objeto de formulário, a melhor abordagem de observação é usar
$watchCollection. Consulte a documentação oficial para diferentes características de desempenho.

Ramesh Papaganti
fonte
1

Tente o seguinte:

function MyController($scope) {
    $scope.form = {
        name: 'my name',
        surname: 'surname'
    }

    function track(newValue, oldValue, scope) {
        console.log('changed');
    };

    $scope.$watch('form.name', track);
}
Tarun
fonte
0

Para quem deseja observar uma alteração em um objeto em uma variedade de objetos, isso pareceu funcionar para mim (como as outras soluções nesta página não):

function MyController($scope) {

    $scope.array = [
        data1: {
            name: 'name',
            surname: 'surname'
        },
        data2: {
            name: 'name',
            surname: 'surname'
        },
    ]


    $scope.$watch(function() {
        return $scope.data,
    function(newVal, oldVal){
        console.log(newVal, oldVal);
    }, true);
Maximillion Bartango
fonte
-3

você deve alterar em $ watch ....

function MyController($scope) {
    $scope.form = {
        name: 'my name',
    }

    $scope.$watch('form.name', function(newVal, oldVal){
        console.log('changed');
     
    });
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.22/angular.min.js"></script>
<div ng-app>
    <div ng-controller="MyController">
        <label>Name:</label> <input type="text" ng-model="form.name"/>
            
        <pre>
            {{ form }}
        </pre>
    </div>
</div>

Vijay Patel
fonte
8
Respondendo a uma pergunta de dois anos com uma resposta que é uma duplicata quase exata de uma existente? Não é legal.
Jared Smith