Como uso $ scope. $ Watch e $ scope. $ No AngularJS?

1088

Eu não entendo como usar $scope.$watche $scope.$apply. A documentação oficial não é útil.

O que não entendo especificamente:

  • Eles estão conectados ao DOM?
  • Como posso atualizar as alterações do DOM no modelo?
  • Qual é o ponto de conexão entre eles?

Eu tentei este tutorial , mas é preciso entender $watche dar $applycomo certo.

O que fazer $applye $watchfazer, e como usá-los adequadamente?

ilyo
fonte

Respostas:

1737

Você precisa estar ciente de como o AngularJS funciona para entendê-lo.

Ciclo de resumo e escopo $

Em primeiro lugar, o AngularJS define um conceito do chamado ciclo de digestão . Esse ciclo pode ser considerado como um loop, durante o qual o AngularJS verifica se há alguma alteração em todas as variáveis observadas por todos os $scopes. Portanto, se você $scope.myVardefiniu em seu controlador e essa variável foi marcada para ser observada , você está dizendo implicitamente ao AngularJS para monitorar as alterações myVarem cada iteração do loop.

Uma pergunta natural de acompanhamento seria: tudo está ligado à $scopeobservação? Felizmente não. Se você observasse as alterações em todos os objetos do seu $scopecomputador, rapidamente um ciclo de digestão levaria séculos para ser avaliado e você enfrentaria problemas de desempenho rapidamente. É por isso que a equipe do AngularJS nos deu duas maneiras de declarar alguma $scopevariável como sendo observada (leia abaixo).

O $ watch ajuda a ouvir as alterações do escopo $

Existem duas maneiras de declarar uma $scopevariável como sendo observada.

  1. Ao usá-lo em seu modelo através da expressão <span>{{myVar}}</span>
  2. Adicionando-o manualmente através do $watchserviço

Anúncio 1) Esse é o cenário mais comum e tenho certeza de que você já o viu antes, mas não sabia que isso criou um relógio em segundo plano. Sim, tinha! O uso de diretivas AngularJS (como ng-repeat) também pode criar relógios implícitos.

Anúncio 2) É assim que você cria seus próprios relógios . $watchO serviço ajuda a executar algum código quando algum valor anexado ao $scopefoi alterado. Raramente é usado, mas às vezes é útil. Por exemplo, se você deseja executar algum código sempre que 'myVar' for alterado, faça o seguinte:

function MyController($scope) {

    $scope.myVar = 1;

    $scope.$watch('myVar', function() {
        alert('hey, myVar has changed!');
    });

    $scope.buttonClicked = function() {
        $scope.myVar = 2; // This will trigger $watch expression to kick in
    };
}

$ apply permite integrar alterações ao ciclo de resumo

Você pode pensar na $applyfunção como um mecanismo de integração . Veja bem, toda vez que você altera alguma variável observada anexada$scope diretamente ao objeto, o AngularJS saberá que a alteração ocorreu. Isso ocorre porque o AngularJS já sabia monitorar essas alterações. Portanto, se isso acontecer no código gerenciado pela estrutura, o ciclo de resumo continuará.

No entanto, às vezes você deseja alterar algum valor fora do mundo do AngularJS e ver as mudanças se propagarem normalmente. Considere isso - você tem um $scope.myVarvalor que será modificado no $.ajax()manipulador de um jQuery . Isso acontecerá em algum momento no futuro. O AngularJS não pode esperar que isso aconteça, pois não foi instruído a esperar no jQuery.

Para resolver isso, $applyfoi introduzido. Permite iniciar explicitamente o ciclo de digestão. No entanto, você deve usar isso apenas para migrar alguns dados para o AngularJS (integração com outras estruturas), mas nunca use esse método combinado ao código AngularJS regular, pois o AngularJS gerará um erro.

Como tudo isso está relacionado ao DOM?

Bem, você realmente deve seguir o tutorial novamente, agora que sabe tudo isso. O ciclo de resumo garantirá que a interface do usuário e o código JavaScript permaneçam sincronizados, avaliando todos os observadores conectados a todos os $scopes, desde que nada mude. Se não ocorrer mais alterações no loop de digestão, será considerado concluído.

Você pode anexar objetos ao $scopeobjeto explicitamente no Controller ou declarando-os no {{expression}}formulário diretamente na visualização.

Espero que ajude a esclarecer alguns conhecimentos básicos sobre tudo isso.

Leituras adicionais:

ŁukaszBachman
fonte
57
"Verifica angular se há alguma alteração em todas as variáveis ​​anexadas a todos os escopos $" - não acho que esteja certo. Acredito que apenas Angular (sujo) verifica as propriedades do escopo $ que tiveram $ relógios configurados (observe que o uso de {{}} em uma exibição criará um $ watch automaticamente). Consulte também a seção "Escopo $ observe as considerações de desempenho" na página Escopo .
Mark Rajcok
5
Esse pode ser o caso. Vou tentar encontrar algum tempo para ler mais sobre isso e editar minha resposta.
ŁukaszBachman
15
@ MarkRajcok, você estava certo. Alterei minha resposta e apontei para um artigo que mostra bem como isso é implementado.
ŁukaszBachman
3
que tal usar isso? (Método "Controlar como")
Leandro
2
Usar "Controlar como" não deve afetar as informações acima. Usar this.myVar coloca myVar no escopo.
Marcus Rådell
161

No AngularJS, atualizamos nossos modelos e nossas visualizações / modelos atualizam o DOM "automaticamente" (por meio de diretivas internas ou personalizadas).

$ apply e $ watch, ambos métodos de Scope, não estão relacionados ao DOM.

A página Conceitos (seção "Tempo de execução") tem uma boa explicação do loop $ digest, $ apply, da fila $ evalAsync e da lista de observação $. Aqui está a figura que acompanha o texto:

$ digest loop

Qualquer código que tenha acesso a um escopo - normalmente controladores e diretivas (suas funções de link e / ou controladores) - pode configurar uma " watchExpression " que o AngularJS avaliará em relação a esse escopo. Essa avaliação ocorre sempre que o AngularJS entra no loop $ digest (em particular, no loop "$ watch list"). Você pode observar propriedades individuais do escopo, definir uma função para observar duas propriedades juntas, observar o comprimento de uma matriz, etc.

Quando as coisas acontecem "dentro do AngularJS" - por exemplo, você digita em uma caixa de texto que possui a conexão de dados bidirecional do AngularJS ativada (ou seja, usa o modelo ng), um retorno de chamada $ http é acionado etc. - $ apply já foi chamado, então nós está dentro do retângulo "AngularJS" na figura acima. Todas as watchExpressions serão avaliadas (possivelmente mais de uma vez - até que nenhuma alteração adicional seja detectada).

Quando as coisas acontecem "fora do AngularJS" - por exemplo, você usou bind () em uma diretiva e, em seguida, esse evento é disparado, resultando na chamada de retorno de chamada ou em alguns retornos de chamada registrados pelo jQuery - ainda estamos no retângulo "Nativo". Se o código de retorno de chamada modificar qualquer coisa que qualquer $ watch esteja assistindo, chame $ apply para entrar no retângulo AngularJS, fazendo com que o loop $ digest seja executado e, portanto, o AngularJS notará a alteração e fará sua mágica.

Mark Rajcok
fonte
5
Entendo a ideia, o que não entendo é como os dados são realmente transferidos. Eu tenho um modelo que é um objeto com muitos dados, eu uso alguns deles para manipular o DOM. então algumas delas são alteradas. Como coloco os dados alterados no lugar certo no modelo? No exemplo que usei, ele faz a manipulação e, no final, simplesmente usa scope.$apply(scope.model), não entendo quais dados são transferidos e como são transferidos para o local certo no modelo?
Ilyo 28/02
6
Não há transferência mágica de dados. Normalmente, com os aplicativos Angular, você deve alterar os modelos Angular, que geram atualizações de visualização / DOM. Se você atualizar o DOM fora do Angular, precisará atualizar manualmente os modelos. scope.$apply(scope.model)irá simplesmente avaliar scope.modelcomo uma expressão Angular e inserir um loop $ digest. No artigo que você scope.$apply()mencionou , provavelmente seria suficiente, já que o modelo já está sendo monitorado. A função stop () está atualizando o modelo (acredito que toUpdate é uma referência ao scope.model) e, em seguida, $ apply é chamado.
Mark Rajcok
Parece que os documentos do AngularJS foram retirados dessa resposta (o primeiro link não tem "tempo de execução" ou $watchna página e o segundo link está quebrado - a partir de agora, de qualquer maneira). Dolorosamente, as versões do arquivo não armazenavam em cache qualquer processo assíncrono que criou o conteúdo.
ruffin 14/01
52

AngularJS estende isso loop de eventos , criando algo chamado AngularJS context.

$ watch ()

Toda vez que você vincula algo na interface do usuário, insere um $watchem uma $watchlista .

User: <input type="text" ng-model="user" />
Password: <input type="password" ng-model="pass" />

Aqui temos $scope.user, que está vinculado à primeira entrada, e temos $scope.pass, que está vinculada à segunda entrada. Fazendo isso, adicionamos dois$watch es à $watchlista .

Quando nosso modelo é carregado, também conhecido como AKA, na fase de vinculação, o compilador procurará todas as diretivas e criará todas as $watches necessárias.

AngularJS fornece $watch, $watchcollectione $watch(true). Abaixo está um diagrama detalhado que explica todas as três tiradas dos observadores em profundidade .

Digite a descrição da imagem aqui

angular.module('MY_APP', []).controller('MyCtrl', MyCtrl)
function MyCtrl($scope,$timeout) {
  $scope.users = [{"name": "vinoth"},{"name":"yusuf"},{"name":"rajini"}];

  $scope.$watch("users", function() {
    console.log("**** reference checkers $watch ****")
  });

  $scope.$watchCollection("users", function() {
    console.log("**** Collection  checkers $watchCollection ****")
  });

  $scope.$watch("users", function() {
    console.log("**** equality checkers with $watch(true) ****")
  }, true);

  $timeout(function(){
     console.log("Triggers All ")
     $scope.users = [];
     $scope.$digest();

     console.log("Triggers $watchCollection and $watch(true)")
     $scope.users.push({ name: 'Thalaivar'});
     $scope.$digest();

     console.log("Triggers $watch(true)")
     $scope.users[0].name = 'Superstar';
     $scope.$digest();
  });
}

http://jsfiddle.net/2Lyn0Lkb/

$digest ciclo

Quando o navegador recebe um evento que pode ser gerenciado pelo contexto AngularJS, o $digestloop é acionado. Esse loop é feito de dois loops menores. Um processa a $evalAsyncfila e o outro processa o $watch list. O $digestloop irá percorrer a lista $watchque temos

app.controller('MainCtrl', function() {
  $scope.name = "vinoth";

  $scope.changeFoo = function() {
      $scope.name = "Thalaivar";
  }
});

{{ name }}
<button ng-click="changeFoo()">Change the name</button>

Aqui temos apenas um $watchporque o ng-click não cria nenhum relógio.

Nós pressionamos o botão.

  1. O navegador recebe um evento que entrará no contexto AngularJS
  2. O $digestloop será executado e solicitará a cada $ watch alterações.
  3. Como o $watchque estava observando alterações no $ scope.name relata uma alteração, forçará outro $digestloop.
  4. O novo loop não informa nada.
  5. O navegador recupera o controle e atualiza o DOM refletindo o novo valor de $ scope.name
  6. O importante aqui é que TODOS os eventos que entram no contexto AngularJS executam um $digestloop. Isso significa que toda vez que escrevermos uma letra em uma entrada, o loop será executado verificando todas as $watchpáginas desta página.

$ apply ()

Se você ligar $applyquando um evento for disparado, ele passará pelo contexto angular, mas se você não o chamar, ele será executado fora dele. É tão fácil quanto isso. $applychamará o$digest() loop internamente e iterará em todos os relógios para garantir que o DOM seja atualizado com o valor recém-atualizado.

O $apply()método acionará observadores em toda a $scopecadeia, enquanto o $digest()método acionará apenas observadores na corrente $scopee na sua children. Quando nenhum dos $scopeobjetos superiores precisar saber sobre as alterações locais, você poderá usá-lo $digest().

Thalaivar
fonte
18

Eu achei muito vídeos em profundidade que abrangem $watch, $apply, $digeste digerir ciclos em:

A seguir, são apresentados alguns slides usados ​​nesses vídeos para explicar os conceitos (por precaução, se os links acima foram removidos / não estão funcionando).

Digite a descrição da imagem aqui

Na imagem acima, "$ scope.c" não está sendo observado, pois não é usado em nenhuma das ligações de dados (na marcação). Os outros dois ( $scope.ae $scope.b) serão assistidos.

Digite a descrição da imagem aqui

Na imagem acima: Com base no respectivo evento do navegador, o AngularJS captura o evento, realiza o ciclo de digestão (passa por todos os relógios em busca de alterações), executa funções de relógio e atualiza o DOM. Se não houver eventos no navegador, o ciclo de resumo pode ser acionado manualmente usando $applyou $digest.

Mais sobre $applye $digest:

Digite a descrição da imagem aqui

user203687
fonte
17

Existem $watchGroupe $watchCollectiontambém. Especificamente, $watchGroupé realmente útil se você deseja chamar uma função para atualizar um objeto que possui várias propriedades em uma visualização que não é um objeto dom, por exemplo, outra visualização em canvas, WebGL ou solicitação do servidor.

Aqui, o link da documentação .

Utkarsh Bhardwaj
fonte
Eu teria comentado sobre o, $watchCollectionmas vejo que você já fez. Aqui está a documentação sobre isso no site do AngularJS. Eles fornecem um visual muito bonito da $watchprofundidade. Observe que as informações estão próximas à parte inferior da página.
precisa
15

Apenas termine de ler TODAS as anteriores, chatas e sonolentas (desculpe, mas é verdade). Muito técnico, detalhado, detalhado e seco. Por que estou escrevendo? Como o AngularJS é massivo, muitos conceitos interconectados podem deixar qualquer pessoa louca. Muitas vezes me perguntei: não sou inteligente o suficiente para entendê-las? Não! É porque tão poucos conseguem explicar a tecnologia em uma linguagem for-dummie sem todas as terminologias! Ok, deixe-me tentar:

1) São coisas orientadas a eventos. (Eu ouço a risada, mas continue a ler)

Se você não sabe o que é orientado a eventos, pense em colocar um botão na página, conecte-o com uma função usando "on-click", esperando que os usuários cliquem nele para acionar as ações que você planta dentro do função. Ou pense em "gatilho" do SQL Server / Oracle.

2) $ watch está "ao clicar".

O que é especial é que são necessárias duas funções como parâmetros, a primeira fornece o valor do evento, a segunda leva o valor em consideração ...

3) $ digest é o chefe que verifica incansavelmente , bla-bla-bla, mas um bom chefe.

4) $ apply fornece o caminho quando você deseja fazê-lo manualmente , como uma prova de falha (caso o clique não aconteça, você o força a executar.)

Agora, vamos torná-lo visual. Imagine isso para facilitar ainda mais a ideia:

Em um restaurante,

- GARÇONS

devem receber pedidos dos clientes, isso é

$watch(
  function(){return orders;},
  function(){Kitchen make it;}
);

- GERENTE correndo para garantir que todos os garçons estejam acordados, respondendo a qualquer sinal de alterações dos clientes. Isto é$digest()

- O PROPRIETÁRIO tem o poder final de conduzir todos, mediante solicitação, este é$apply()

Jeb50
fonte
2
Isso pode ser entendido por uma criança de 5 anos. Eu aprecio esse tipo de resposta. +1
Chris22