2012-11-30 7 views
18

Una cosa importante da tenere a mente quando si ha a che fare con strumenti di terze parti e con eventi DOM esterni in AngularJS è utilizzare l'operazione del metodo $scope.$apply() per avviare le modifiche. Funziona in modo sorprendente, ma a volte lo scope stesso è già digerito attraverso un digest (che è fondamentalmente ciò che si innesca il metodo $ apply) e chiamando $ apply quando questo sta succedendo genererà un errore. Quindi, per ovviare a questo, dovrai prestare attenzione al flag $scope.$$phase che è impostato sull'oscilloscopio ogni volta che si verifica un digest.

Così ora, diciamo che si desidera modificare l'URL e fare fuoco fuori:

$scope.$apply(function() { 
    $location.path('/home'); 
}); 

E questo funziona come previsto, ma ora lascia supporre che il $ ambito è occupato a fare è cosa. Così, invece di controllare per la variabile fase di $$ e supporre che le modifiche saranno raccolti:

if($scope.$$phase) { 
    $location.path('/home'); 
} 
else { 
    $scope.$apply(function() { 
    $location.path('/home'); 
    }); 
} 

Questo è quello che ho fatto (non con la duplicazione del codice ovviamente) e sembra funzionare al 100% del tempo. Quello che mi preoccupa è che in che modo AngularJS preleva la modifica quando l'oscilloscopio è a metà della sua digestione?

Forse questo esempio non è abbastanza specifico. Supponiamo invece qualcosa di più grande. Immagina di avere un'enorme pagina web con un sacco di associazioni e supponiamo che la digestione mastichi linearmente la pagina (presumo che faccia qualcosa di simile rispetto alla priorità ... In questo caso, qualsiasi cosa si presenti nel Primo albero DOM) e aggiornare i collegamenti sulla pagina dall'alto al basso.

<div class="binding">{{ binding1 }}</div> 
<div class="binding">{{ binding2 }}</div> 
<div class="binding">{{ binding3 }}</div> 
<div class="binding">{{ binding4 }}</div> 
<div class="binding">{{ binding5 }}</div> 
<div class="binding">{{ binding6 }}</div> 
<div class="binding">{{ binding7 }}</div> 
<div class="binding">{{ binding8 }}</div> 

Supponiamo che sia in corso una digestione ed è vicino al centro della coda di digestione. Ora proviamo a provare e modificare un valore vincolante nella parte superiore della pagina da qualche parte.

if($scope.$$phase) { 
    $scope.binding1 = 'henry'; 
} 

Ora, in qualche modo, AngularJS preleva la modifica e aggiorna correttamente l'associazione. Anche se è possibile considerare che il cambiamento stesso avvenga in precedenza nella coda rispetto a HTML/DOM.

La mia domanda è: come fa AngularJS a gestire questa potenziale condizione di competizione? In qualche modo mi sento a mio agio se gli aggiornamenti 8 sono corretti (), ma poiché lo binding1 si aggiorna (subito senza bisogno di richiamare $ apply di nuovo), questo mi rende un po 'perso. Questo significa che un'altra digestione è stata inviata da qualche parte nel mezzo? O l'oggetto $ scope è più magico di quanto mi aspetti? Suppongo che questo problema sia stato fatto prima, ma dal momento che scoprire la fase $$ e $ scope in primo luogo è stato un po 'complicato, suppongo che anche questo potrebbe essere qualcosa che è caduto attraverso le fessure.

Qualche idea?

+0

Non capisco perché stai cambiando url usando $ scope.apply()? $ location è un servizio angolare quindi prende parte al ciclo di vita, come posso vedere nel codice (https://github.com/angular/angular.js/blob/master/src/ng/location.js) chiama $ rootScope. $ digest() stesso quando cambia l'URL. – matys84pl

+3

Riguardo le associazioni e le condizioni di gara. $ digest ricollegherà tutti gli osservatori finché non ci saranno cambiamenti. Come è possibile osservare aggiungendo i registri ai metodi watcher/binded, chiamerà ogni binding/watcher almeno due volte per essere sicuro che non ci siano cambiamenti e tutti i valori bindati siano stabili. Questi sono solo controlli sporchi che vengono eseguiti fino alla risoluzione di ogni valore (che non è stato modificato in 2 iterazioni del ciclo). Spero possa aiutare. – matys84pl

risposta

19

Riguardo gli attacchi e le condizioni di gara. $ digest interromperà tutti gli osservatori finché non ci saranno cambiamenti. Come è possibile osservare aggiungendo i registri ai metodi watcher/binded, chiamerà ogni binding/watcher almeno due volte per essere sicuro che non ci siano cambiamenti e tutti i valori bindati siano stabili.Questi sono solo controlli sporchi che vengono eseguiti fino alla risoluzione di ogni valore (che non è stato modificato in 2 iterazioni del ciclo). Spero possa aiutare.

Questo è spiegato in AngularJS docs qui: http://docs.angularjs.org/api/ng.$rootScope.Scope#$digest

NOTA: Questo è un copia/incolla del mio commento come richiesto da matsko.

1

Apply continuerà a chiamare digest finché non è sicuro che nulla è cambiato. Quindi se può avere una condizione di gara alla prima chiamata, ma una seconda compenserà sempre.

0

Si consiglia di non utilizzare tale soluzione in ogni caso: se lo stato del vostro $scope in incerta su questa funzione, si deve digerire più in alto sul tuo stack di chiamate in cui è sapere se si sta synchroneously trattati da angolare, quindi non c'è bisogno per digerire, o se stai modificando il modello in modo asincrono in modo da poterlo digerire.

Su un lato nota, $scope.$apply è fondamentalmente un $scope.$root.$digest con la gestione degli errori, quindi se si sta aggiornando un ambito isolato all'interno di una direttiva è possibile salvare le performance chiamando $digest invece di $apply:

// Somewhere in a callback 
$location.path('/home'); 
$scope.$digest(); 

PS: Questo serve per correggere la risposta e-satis: $digest si chiamerà anche se l'ambito è sporco.

Problemi correlati