Il digest loop e tutto il processo di valutazione dei watch è spesso detto contesto Angular. L'espressione sta ad indicare il fatto che in quella fase Angular prende il controllo della nostra applicazione e sincronizza variabili e DOM. Il concetto è fondamentale per comprendere il perché talvolta ci troviamo in situazioni in cui ci aspettiamo un comportamento e invece l'effetto non è proprio quello atteso oppure addirittura non accade proprio nulla.
Consideriamo il seguente esempio:
<div ng-controller="myCtrl">
Numero: <input type="text" ng-model="numero"></br>
<button id="btnRaddoppia">Raddoppia</button></br>
<span>{{doppio}}</span>
</div>
<script type="text/javascript">
angular.module("myApp", [])
.controller("myCtrl", function($scope) {
document.getElementById("btnRaddoppia")
.addEventListener("click", function() {
$scope.doppio = $scope.numero * 2;
});
});
</script>
In esso definiamo una casella di testo e un pulsante. Ci aspettiamo che quando l'utente inserisce un numero nella casella di testo e clicca sul pulsante, sotto al pulsante venga visualizzato il doppio del numero inserito:
Tuttavia, inserendo un numero e cliccando sul pulsante non accade proprio nulla. Abbiamo definito le due variabili di scope numero e doppio utilizzate nella view e quindi Angular ha creato due watch per monitorare la variazione dei loro valori. Però il clic sul pulsante non ha avviato il digest loop. Eppure se seguiamo con un debugger il codice JavaScript notiamo che al clic sul pulsante il codice del gestore d'evento viene eseguito correttamente. Il problema è che tale codice viene eseguito fuori dal contesto Angular e pertanto non avviene la valutazione dei watch, cioè non viene attivato il digest loop.
Eseguire codice nel contesto Angular
Per eseguirlo nel contesto Angular e avviare quindi il digest loop dobbiamo ricorrere al metodo $apply() dello scope, come mostrato nel seguente esempio:
angular.module("myApp", [])
.controller("myCtrl", function($scope) {
document.getElementById("btnRaddoppia")
.addEventListener("click", function() {
$scope.$apply(function() {
$scope.doppio = $scope.numero * 2;
});
});
});
Il metodo $apply() si occupa essenzialmente di eseguire la funzione passata come argomento e avviare subito dopo il digest loop.
Naturalmente potevamo evitare di ricorrere a $apply()
se utilizzavamo la direttiva ng-click
. Questa direttiva, infatti, come molte altre direttive, esegue il codice associato ed invoca $apply()
al posto nostro avviando implicitamente il digest loop.
Nota: Prima di utilizzare $apply()
è importante assicurarci di essere fuori dal contesto Angular. In caso contrario otterremo un errore. Ad esempio, otterremo un errore se utilizziamo $apply()
all'interno della funzione associata ad una direttiva ng-click
.
Come regola generale sarebbe opportuno utilizzare le direttive predefinite che Angular ci mette a disposizione. Questo ci evita in linea di massima di dover utilizzare funzionalità interne come $apply()
. La conoscenza degli internals
può comuque tornarci utile quando vogliamo integrare nel contesto Angular funzionalità implementate in una libreria non-Angular.