A mágica por trás do Angular: o $digest
Escrito por Rodrigo Waltenberg em 14/07/2016
Atenção: Este é um artigo sobre AngularJS. Como ele será descontinuado em prol do Angular, sugerimos a leitura desse outro artigo: O que é Angular?
De todas as funcionalidades que o Angular nos oferece, a que ganhou maior destaque e admiração do público foi a "mágica" do data binding (mais especificamente o two-way data binding), que atualiza as informações ao vivo na página.
Hoje nós vamos desvendar essa mágica e entender direitinho como o Angular consegue fazer essa proeza. Então, com o perdão do pleonasmo, vamos começar do começo.
Mas antes, veja o exemplo abaixo em funcionamento (digite um nome no campo):
See the Pen AngularJS Two-way Data Binding by AlgaWorks (@algaworks) on CodePen.light
Estendendo os eventos do navegador
Os navegadores estão sempre aguardando por eventos. É por isso que eles sabem quando clicamos em um botão, ou escrevemos numa caixa de texto.
Esses eventos executam funções que podemos registrar e assim chamar um script em resposta à ação do usuário. Vamos ver isso funcionando com JavaScript puro:
See the Pen oLxVzd by AlgaWorks (@algaworks) on CodePen.light
O Angular estende todos esses eventos, criando o seu próprio contexto, o "angular context".
Toda vez que você faz um data binding no Angular você, sem saber, coloca um $watch – uma observação – na lista lista de observação do Angular. Ou seja, o Angular faz uma lista das propriedades que ele tem que ficar observando se tem alguma mudança.
Então, quando fazemos algo assim:
<input type="text" ng-model="nome" />
Estamos colocando um $watch com essa variável "nome" na lista de observação.
Tá bom, então toda vez que fazemos um data bind estamos criando um $watch e colocando na lista de observação. E agora?
O ciclo digestivo do Angular
Ok, lembra daquele contexto próprio do Angular nos eventos do navegador?
Então, toda vez que o navegador dispara um evento que pode ser gerenciado pelo angular context, um loop chamado $digest é colocado pra rodar.
Este $digest é composto por dois loops menores, um pra rodar a lista de $evalAsync – que não vamos tratar neste post – e um outro pra processar a Lista de Observação.
E como seria esse processo? O loop vai passar por cada $watch e retirar duas informações:
Houve alteração no valor da variável?
Qual o novo valor da variável?
Quando terminar todos os $watch, ele vai passar mais uma vez pra conferir se está tudo do mesmo jeito, se tiver alguma alteração, ele roda de novo e de novo (até 10 vezes, depois disso ele solta uma exception) até ele ter certeza que está tudo certinho, então o loop acaba e ele faz as alterações na interface.
Isso é chamado de dirty-checking ou "conferência suja", porque ele precisa conferir um por um de cada $watch, não apenas o que foi alterado. Isso normalmente demora uma fração de segundo, mas quanto mais $watch no projeto, menos rápido fica.
Aplicando o contexto
Isso quer dizer que CADA ação do usuário, cada tecla apertada, cada clique do mouse roda um $digest?
Ainda bem que não. Na verdade, o $digest só vai rodar se aquele evento estiver dentro do Angular Context.
E como saber se o evento está dentro do contexto do Angular? A resposta é o $apply.
Se você chamar o método $apply quando um evento é disparado, ele vai rodar dentro do Angular Context e vai disparar o $digest.
O Angular vem com um monte de diretivas e métodos personalizados que chamam o $apply pra gente automaticamente, como o ng-click, o $timeout, etc.
Vamos ver então como fica um bind sem o $apply:
See the Pen Sem $apply by AlgaWorks (@algaworks) on CodePen.light
Veja que a mensagem nunca aparece porque o $digest não é chamado. Agora com o $apply:
See the Pen Com $apply by AlgaWorks (@algaworks) on CodePen.light
Quando o conteúdo do $apply é executado, ele chama automaticamente o $digest e o nosso bind é atualizado. Isso não quer dizer que sem o $apply o bind não vai atualizar nunca, um outro elemento que chamar o $digest vai fazer a atualização do conteúdo, olha só:
See the Pen Sem $apply c/ Botão by AlgaWorks (@algaworks) on CodePen.light
Ao usar a diretiva ng-click no botão, o Angular já colocou o evento click dele no Angular Context e o $digest vai ser chamado.
Então lembre-se: sempre que você fizer alguma alteração no escopo através de um evento que não está dentro do contexto do Angular, como um callback de algum componente do jQuery por exemplo, use o $apply pra levar essa alteração para o Angular Context.
Pronto! Agora que a "mágica" do data-binding do Angular foi desvendada você estará muito mais preparado para lidar com um dos "problemas" mais reportados com Angular na internet: "bindings que não funcionam".
Legal, né? Para não perder dicas como essa e ficar por cima das últimas novidades de desenvolvimento Front-end do mercado, assine nossa Lista VIP de Front-end agora! Não se preocupe, não mandamos spam. :)
Faça parte também da nossa Comunidade Front-end no Facebook.
ATUALIZAÇÃO: A tecnologia AngularJS está sendo descontinuada em prol do Angular, e nós já temos um e-book para ajudar você nessa atualização. A boa notícia é que ele vai além do Angular, ensinando você como se tornar um desenvolvedor fullstack.