Assista a vários atributos de escopo $

441

Existe uma maneira de se inscrever em eventos em vários objetos usando $watch

Por exemplo

$scope.$watch('item1, item2', function () { });
Greg
fonte

Respostas:

585

A partir do AngularJS 1.3, existe um novo método chamado $watchGrouppara observar um conjunto de expressões.

$scope.foo = 'foo';
$scope.bar = 'bar';

$scope.$watchGroup(['foo', 'bar'], function(newValues, oldValues, scope) {
  // newValues array contains the current values of the watch expressions
  // with the indexes matching those of the watchExpression array
  // i.e.
  // newValues[0] -> $scope.foo 
  // and 
  // newValues[1] -> $scope.bar 
});
Paolo Moretti
fonte
7
qual é a diferença contra $ watchCollection ('[a, b]') ??
Kamil Tomšík
28
A diferença é que você pode usar uma matriz adequada, em vez de uma string que se parece com um array
Paolo Moretti
2
Eu tive um problema semelhante, mas usando o padrão controllerAs. No meu caso a correção consistiu em prepend às variáveis o nome do controlador:$scope.$watchGroup(['mycustomctrl.foo', 'mycustomctrl.bar'],...
cogumelos
1
Isso não parece funcionar para mim no Angular 1.6. Se eu colocar um log do console na função, posso ver que ele é executado apenas uma vez com newValuese oldValuescomo undefined, enquanto observa individualmente as propriedades funcionar normalmente.
Alejandro García Iglesias
290

A partir do AngularJS 1.1.4, você pode usar $watchCollection:

$scope.$watchCollection('[item1, item2]', function(newValues, oldValues){
    // do stuff here
    // newValues and oldValues contain the new and respectively old value
    // of the observed collection array
});

Exemplo de Plunker aqui

Documentação aqui

Răzvan Flavius ​​Panda
fonte
109
Observe que o primeiro parâmetro não é uma matriz. É uma string que se parece com uma matriz.
MK Safi
1
obrigado por isso. se ele funciona para mim, vou dar-lhe mil moedas de ouro - os virtuais, é claro :)
AdityaSaxena
5
Para ser claro (levei alguns minutos para perceber), o $watchCollectionobjetivo é entregar um objeto e fornecer um controle de alterações em qualquer uma das propriedades do objeto, enquanto que o $watchGroupobjetivo é fornecer uma variedade de propriedades individuais para observar alterações. Um pouco diferente para lidar com problemas semelhantes, mas diferentes. Ufa!
Mattygabe
1
De alguma forma, NewValues e oldValues são sempre iguais quando utilizar este método: plnkr.co/edit/SwwdpQcNf78pQ80hhXMr
mostruash
Obrigado. Isso resolveu meu problema. Existe algum problema / problema no desempenho do uso do $ watch?
Syed Nasir Abbas
118

$watch O primeiro parâmetro também pode ser uma função.

$scope.$watch(function watchBothItems() {
  return itemsCombinedValue();
}, function whenItemsChange() {
  //stuff
});

Se seus dois valores combinados forem simples, o primeiro parâmetro é apenas uma expressão angular normalmente. Por exemplo, nome e sobrenome:

$scope.$watch('firstName + lastName', function() {
  //stuff
});
Andrew Joslin
fonte
8
Parece um caso de uso que exige inclusão na estrutura por padrão. Mesmo uma propriedade computada tão simples quanto fullName = firstName + " " + lastNamerequer passar uma função como o primeiro argumento (em vez de uma expressão simples como 'firstName, lastName'). O Angular é TÃO poderoso, mas há algumas áreas em que pode ser muito mais amigável ao desenvolvedor de maneiras que não parecem comprometer os princípios de desempenho ou design.
XML
4
Bem, $ watch apenas usa uma expressão angular, que é avaliada no escopo em que é chamada. Então você poderia fazer $scope.$watch('firstName + lastName', function() {...}). Você também pode apenas fazer dois relógios: function nameChanged() {}; $scope.$watch('firstName', nameChanged); $scope.$watch('lastName', nameChanged);. Eu adicionei o caso simples à resposta.
Andrew Joslin
O sinal de mais em 'firstName + lastName' é concatenação de string. Tão angular apenas verifica se o resultado da concatenação é diferente agora.
Mb21
16
A concatenação de cadeias requer um pouco de cuidado - se você alterar "paran orman" para "para norman", não será acionada uma resposta; melhor colocar um divisor como "\ t | \ t" entre o primeiro eo segundo mandato, ou apenas furar a JSON que é definitiva
Dave Edelhart
embora amberjs seja péssimo, mas ele possui esse recurso! ouvir aqueles caras angulares. Eu acho que fazer relógios é a maneira mais racional possível.
Aladdin Mhemed
70

Aqui está uma solução muito semelhante ao seu pseudo-código original que realmente funciona:

$scope.$watch('[item1, item2] | json', function () { });

EDIT: Ok, acho que isso é ainda melhor:

$scope.$watch('[item1, item2]', function () { }, true);

Basicamente, estamos pulando a etapa json, que parecia idiota para começar, mas não estava funcionando sem ela. A chave é o terceiro parâmetro frequentemente omitido, que ativa a igualdade de objetos em oposição à igualdade de referência. Em seguida, as comparações entre nossos objetos de matriz criados realmente funcionam corretamente.

Karen Zilles
fonte
Eu tive uma pergunta semelhante. E esta é a resposta correta para mim.
Calvin Cheng
Ambos têm uma pequena sobrecarga de desempenho se os itens na expressão da matriz não forem tipos simples.
Nulo
Isso é verdade ... está fazendo uma comparação completa dos objetos componentes. Que pode ou não ser o que você deseja. Veja a resposta atualmente aprovada para uma versão usando o angular moderno que não faz uma comparação completa.
Karen Zilles
Por alguma razão, quando estava tentando usar o $ watchCollection em uma matriz, recebi esse erro, TypeError: Object #<Object> has no method '$watchCollection'mas esta solução me ajuda a resolver meu problema!
abottoni
Legal, você pode até observar valores dentro dos objetos:$scope.$watch('[chaptercontent.Id, anchorid]', function (newValues, oldValues) { ... }, true);
Serge van den Oever
15

Você pode usar funções no $ watchGroup para selecionar os campos de um objeto no escopo.

        $scope.$watchGroup(
        [function () { return _this.$scope.ViewModel.Monitor1Scale; },   
         function () { return _this.$scope.ViewModel.Monitor2Scale; }],  
         function (newVal, oldVal, scope) 
         {
             if (newVal != oldVal) {
                 _this.updateMonitorScales();
             }
         });
Yang Zhang
fonte
1
Por que você usa _this? O escopo não é carregado para as funções sem defini-lo explicitamente?
Sg.cc 21/05
1
Eu uso o texto datilografado, o código apresentado é o resultado compilado.
Yang Zhang
12

Por que não simplesmente envolvê-lo em um forEach?

angular.forEach(['a', 'b', 'c'], function (key) {
  scope.$watch(key, function (v) {
    changed();
  });
});

Trata-se da mesma sobrecarga de fornecer uma função para o valor combinado, sem precisar se preocupar com a composição do valor .

Erik Aigner
fonte
Nesse caso, log () seria executado várias vezes, certo? Eu acho que, no caso de funções $ watch aninhadas, isso não aconteceria. E não deve estar na solução para assistir a vários atributos ao mesmo tempo.
John Doe
Não, o log é executado apenas uma vez por alteração por propriedade monitorada. Por que seria invocado várias vezes?
Erik Aigner
Certo, quero dizer que não funcionaria bem se quiséssemos assistir todos eles simultaneamente.
John Doe
1
Claro, por que não? Eu faço dessa maneira em um projeto meu. changed()é chamado sempre que algo muda em qualquer uma das propriedades. Esse é exatamente o mesmo comportamento como se uma função para o valor combinado fosse fornecida.
Erik Aigner
1
É um pouco diferente, pois se vários itens da matriz forem alterados ao mesmo tempo, o $ watch disparará o manipulador apenas uma vez em vez de uma vez por item alterado.
Bingles
11

Uma solução um pouco mais segura para combinar valores pode ser o seguinte como sua $watchfunção:

function() { return angular.toJson([item1, item2]) }

ou

$scope.$watch(
  function() {
    return angular.toJson([item1, item2]);
  },
  function() {
    // Stuff to do after either value changes
  });
guyk
fonte
5

O primeiro parâmetro $ watch pode ser expressão ou função angular . Veja a documentação sobre $ scope. $ Watch . Ele contém muitas informações úteis sobre como o método $ watch funciona: quando watchExpression é chamado, como angular compara resultados, etc.

Artem Andreev
fonte
4

e quanto a:

scope.$watch(function() { 
   return { 
      a: thing-one, 
      b: thing-two, 
      c: red-fish, 
      d: blue-fish 
   }; 
}, listener...);
wacamo
fonte
2
Sem o terceiro trueparâmetro para scope.$watch(como no seu exemplo), listener()seria acionado toda vez, porque o hash anônimo tem uma referência diferente a cada vez.
Peter V. Mørch 27/01
Eu encontrei esta solução funcionou para a minha situação. Para mim, era mais como: return { a: functionA(), b: functionB(), ... }. Em seguida, verifico os objetos retornados dos valores novos e antigos.
RevNoah
4
$scope.$watch('age + name', function () {
  //called when name or age changed
});

Aqui, a função será chamada quando os valores de idade e nome forem alterados.

Akash Shinde
fonte
2
Também não se esqueça, neste caso, não usar os argumentos NewValue e oldValue que passes angulares para o retorno de chamada, porque eles vão ser a concatenação resultante e não apenas o campo que foi alterado
Akash Shinde
1

O Angular introduzido $watchGroupna versão 1.3, usando o qual podemos observar várias variáveis, com um único $watchGroupbloco $watchGroupassume o array como primeiro parâmetro no qual podemos incluir todas as nossas variáveis ​​a serem observadas.

$scope.$watchGroup(['var1','var2'],function(newVals,oldVals){
   console.log("new value of var1 = " newVals[0]);
   console.log("new value of var2 = " newVals[1]);
   console.log("old value of var1 = " oldVals[0]);
   console.log("old value of var2 = " oldVals[1]);
});
Vinit Solanki
fonte