Data-Binding: Entendendo a mágica do AngularJS
Introdução
BEM… Hoje irei explicar como é realizado o Data-Binding por baixo dos panos no AngularJS. Esse “tão” famoso Data-Binding que o AngularJS trouxe transformou a área de Front-End com uma abordagem muito simples.
Droga, alguns de vocês devem estar pensando “poxa, é famoso essa fita de Data-Binding, mas me explica, o que ele faz?”.
Então turma, essa característica do AngularJS funciona da seguinte forma: quando alteramos algo na view (ex: inserção de texto em um input) automaticamente atualiza-se seu correspondente no $scope
model.
Exemplo 1: exemplo bem básico da mágica realizada com simples linhas de código HTML e Javascript.
“Bão” demais para ser verdade né. Só para você ver como isso afetou o mundo Front-End, para poder realizar o mesmo feito acima no exemplo antes de ter o AngularJS, muitos utilizavam JQuery para a manipulação de DOM Elements.
Exemplo 2: exemplo realizado em JQuery ilustrando uma maneira de se fazer o que foi feito no exemplo 1
Agora, como que esse “trem” funciona por debaixo dos panos? A resposta para essa pergunta está em um termo…. $digest()
.
Explicando o $digest()
Ao demarcar tags HTMLs com expressões AngularJS (ex: {{ message }}
, data-ng-model="message"
), o framework irá associar um watcher
para atualizar a view toda vez que o $scope
model alterar. Um exemplo de criar um watcher
abaixo:
$scope.$watch('message', function(newValue, oldValue) {
// Atualiza o DOM Element
});
O segundo parâmetro da função é um listener, onde irá ser chamado quando o valor de message
for alterado.
OK, agora sabemos que quando demarcamos com expressões o AngularJS criar watchers. Agora a pergunta que não quer calar, quando que é chamado esses watchers? A resposta é… $digest()
!
O ciclo $digest
inicia todos os watchers criados para verificar se ocorreu mudanças no $scope
model para serem atualizados. Ele é executado a partir da função $scope.$digest()
. Um exemplo é quando usamos diretivas do AngularJS (ex: ng-click
) com associação ao $scope
model, automaticamente o AngularJS chama a função para inicializar o ciclo. Existem diretivas/serviços (ex: ng-model
, $http
) que disparam automaticamente o ciclo $digest
.
Explicando o $apply()
“Uai” credo, surgiu outro cara na parada. Quem é esse fera? Calma galera, ele é nosso amigo. Lembra que o $scope.$digest()
inicia o ciclo $digest
relacionado ao $scope
model? Então, o $scope.$apply()
chama o $rootScope.$digest()
que é o $scope
model pai de todos os $scopes
. "Tá" mas explica isso direito... em outras palavras ao invés de chamar o $digest()
só para $scope
model específico, ele chama para todos os que existirem para ver se não estamos perdendo nada.
Agora, vamos assumir que você adicionou um ng-click
em um botão passando o nome de uma função no $scope
model. O AngularJS irá encapsular essa função dentro de uma chamada $scope.$apply()
. Então ele vai chamar a função e no final irá executar o $digest()
. Após isso irá ocorrer o que dissemos, irá iniciar os watchers criados e verificará os valores se foram modificados. Se forem modificados, irá executar seus listeners. :)
Quando chamar manualmente o ciclo $digest
?
Existem algumas situações onde é necessário a chamada do ciclo manualmente. Um dos casos mais comuns são quando usamos algum plugin JQuery (ex: JQuery DatePicker). No AngularJS, existem diretivas/serviços que fazem parte do contexto onde ele automaticamente chama o ciclo $digest()
. Agora esses tipos de situações que não seja dentro do AngularJS, trabalham fora do contexto do AngularJS, ou seja, o AngularJS não sabe das mudanças geradas e com isso não irá disparar o ciclo $digest()
. Nesses momentos, devemos realizar a chamada manual para inicializar o ciclo.
Vamos supor que você utilize um setTimeout()
para atualizar uma variável do $scope
model:
Note que a resposta não aparece na tela porque o AngularJS jamais saberá o que ocorreu mesmo alterando o $scope
model.
Para que o AngularJS reconheça a mudança, devemos chamar o $scope.$apply()
. Segue abaixo o exemplo modificado:
PS: Ao invés de utilizar o setTimeout()
, devemos utilizar o serviço $timeout que o AngularJS disponibiliza e que já realiza a chamada do $scope.$apply()
ao termino do timeout.
Conclusão
Depois de toda essa explicação, o ponto principal é que o AngularJS detecta as mudanças que você realizou dentro do contexto. Se não detectar mudanças, então provavelmente ela está fora do contexto dele e então é necessário a chamada manualmente do ciclo $digest
.