Por que $digest é diferente de $apply em AngularJS?

April 21, 2016

Bastante gente usa, mas muitas vezes sem saber as diferenças sutis entre as duas estruturas, que por sinal, são um pouco semelhantes. A intenção é fazer um post pequeno, mas esclarecedor.

Ambas $apply e $digest são funções que “confirmam” alterações feitas em escopos, porém, enquanto a primeira inicia sua atuação no $rootScope, – que é o escopo principal e tem todos os outros escopos como filhos – e propaga a alteração para todos os escopos filhos, a segunda inicia sua ação no escopo no qual é invocada e apenas propaga para seus filhos. Confuso? Acho que uma olhada no código ajuda a entender um pouco melhor.

Considerando que nós já tenhamos importado o AngularJS, o código seguinte mostra a criação de um controller qualquer dentro de um módulo. Nele, definimos uma mensagem diretamente no escopo principal e uma mensagem no escopo do nosso controller.

'use strict';

angular.module('myApp', [])
  .controller('myCtrl', function($rootScope, $scope) {
    $rootScope.rootMessage = 'Mensagem inicial do rootScope';
    $scope.childMessage = 'Mensagem inicial do childScope';
  });

No exemplo abaixo, criamos uma função changeChildScope que altera a mensagem que nós inserimos anteriormente no escopo do controller, o escopo filho e em seguida chama a função $digest, que por sua vez, vai aplicar a mudança apenas no escopo no qual ela foi chamada e nos filhos deste escopo.

'use strict';

angular.module('myApp', [])
  .controller('myCtrl', function($rootScope, $scope) {
    $rootScope.rootMessage = 'Mensagem inicial do rootScope';
    $scope.childMessage = 'Mensagem inicial do childScope';
  });
  
  (function changeChildScopeMessage() {
    $rootScope.rootMessage = 'Nova mensagem do rootScope';
    $scope.childMessage = 'Nova mensagem do childScope';

    $scope.$digest();
  })();

Ou seja, o $rootScope não vai mudar.

O trecho abaixo é semelhante ao anterior, diferindo apenas no fato de que chamamos a função $apply em vez de $digest, para que as mudanças desejadas sejam observadas partindo do $rootScope para seus filhos.

'use strict';

angular.module('myApp', [])
  .controller('myCtrl', function($rootScope, $scope) {
    $rootScope.rootMessage = 'Mensagem inicial do rootScope';
    $scope.childMessage = 'Mensagem inicial do childScope';
  });
  
  (function changeRootScopeAndChildScopeMessages() {
    $rootScope.rootMessage = 'Nova mensagem do rootScope';
    $scope.childMessage = 'Nova mensagem do childScope';

    $scope.$apply();
  })();

O que faz que agora as duas mensagens mudem.

Caso você queira observar as mudanças acontecendo, basta criar um esqueleto de HTML como o mostrado em seguida.

<body>
    <div ng-app="myApp">
        {{ rootMessage }}
        <div ng-controller="myCtrl">
            {{ childMessage }}
        </div>
    </div>
</body>

Para poder notar com uma velocidade mais lenta, pode inclusive forçar a chamada das funções changeChildScopeMessage e changeRootScopeAndChildScopeMessages usando uma setTimeout com um tempo de sua preferência.

Fiquei motivado a escrever este post após ter assistido este vídeo da Toptal, que é bastante sucinto e direto. Os exemplos de código foram retirados de lá.

Isso é tudo, pessoal. Comentem aí, vamos trocar uma ideia.

Comentários

comments powered by Disqus