Come triggersre un metodo quando Angular ha terminato di aggiungere aggiornamenti di ambito al DOM?

Sto cercando un modo per eseguire codice quando aggiungo le modifiche a una variabile $ scope, in questo caso $ scope.results. Devo farlo per chiamare un codice legacy che richiede che gli elementi siano nel DOM prima che possa essere eseguito.

Il mio codice reale sta triggersndo una chiamata AJAX e sta aggiornando una variabile di ambito per aggiornare l’interfaccia utente. Quindi attualmente il mio codice è in esecuzione immediatamente dopo aver inviato all’ambito, ma il codice legacy non funziona perché gli elementi dom non sono ancora disponibili.

Potrei aggiungere un brutto ritardo con setTimeout (), ma ciò non garantisce che il DOM sia veramente pronto.

La mia domanda è, ci sono dei modi in cui posso bind a un evento simile a “reso”?

var myApp = angular.module('myApp', []); myApp.controller("myController", ['$scope', function($scope){ var resultsToLoad = [{id: 1, name: "one"},{id: 2, name: "two"},{id: 3, name: "three"}]; $scope.results = []; $scope.loadResults = function(){ for(var i=0; i < resultsToLoad.length; i++){ $scope.results.push(resultsToLoad[i]); } } function doneAddingToDom(){ // do something awesome like trigger a service call to log } }]); angular.bootstrap(document, ['myApp']); 

Link al codice simulato: http://jsfiddle.net/acolchado/BhApF/5/

Grazie in anticipo!

La coda $ evalAsync viene utilizzata per pianificare il lavoro che deve avvenire al di fuori del frame dello stack corrente, ma prima che il browser visualizzi il rendering. – http://docs.angularjs.org/guide/concepts#runtime

Ok, allora cos’è un “stack frame”? Un commento Github rivela di più:

se si accoda da un controller, lo sarà prima, ma se si accoda da direttiva, sarà dopo. – https://github.com/angular/angular.js/issues/734#issuecomment-3675158

Sopra, Misko sta discutendo quando il codice che viene accodato per l’esecuzione da $ evalAsync viene eseguito, in relazione a quando il DOM viene aggiornato da Angular. Suggerisco di leggere prima i due commenti Github per ottenere il contesto completo.

Quindi se il codice viene accodato usando $ evalAsync da una direttiva , dovrebbe essere eseguito dopo che il DOM è stato manipolato da Angular, ma prima del rendering del browser. Se è necessario eseguire qualcosa dopo il rendering del browser, o dopo che un controller aggiorna un modello, utilizzare $timeout(..., 0);

Vedi anche https://stackoverflow.com/a/13619324/215945 , che ha anche un esempio di violino che usa $ evalAsync ().

Ho biforcato il tuo violino. http://jsfiddle.net/xGCmp/7/

Ho aggiunto una direttiva chiamata emit-when. Ci vogliono due parametri. L’evento da emettere e la condizione che deve essere soddisfatta per l’evento da emettere. Questo funziona perché quando la funzione di collegamento viene eseguita nella direttiva, sappiamo che l’elemento è stato reso nel DOM. La mia soluzione è emettere un evento quando l’ultimo elemento in ng-repeat è stato reso.

Se avessimo una soluzione tutto Angolare, non raccomanderei di farlo. È un po ‘hacky. Ma potrebbe essere una soluzione ottimale per gestire il tipo di codice legacy che hai citato.

 var myApp = angular.module('myApp', []); myApp.controller("myController", ['$scope', function($scope){ var resultsToLoad = [ {id: 1, name: "one"}, {id: 2, name: "two"}, {id: 3, name: "three"} ]; function doneAddingToDom() { console.log(document.getElementById('renderedList').children.length); } $scope.results = []; $scope.loadResults = function(){ $scope.results = resultsToLoad; // If run doneAddingToDom here, we will find 0 list elements in the DOM. Check console. doneAddingToDom(); } // If we run on doneAddingToDom here, we will find 3 list elements in the DOM. $scope.$on('allRendered', doneAddingToDom); }]); myApp.directive("emitWhen", function(){ return { restrict: 'A', link: function(scope, element, attrs) { var params = scope.$eval(attrs.emitWhen), event = params.event, condition = params.condition; if(condition){ scope.$emit(event); } } } }); angular.bootstrap(document, ['myApp']); 

L’utilizzo del timeout non è il modo corretto per farlo. Utilizzare una direttiva per aggiungere / manipolare il DOM. Se si utilizza il timeout, assicurarsi di utilizzare $ timeout che è collegato ad Angular (ad esempio, restituisce una promise).

Se sei come me, noterai che in molti casi il timeout di $ con un’attesa di 0 viene eseguito molto prima che il DOM sia veramente stabile e completamente statico. Quando voglio che il DOM sia stabile, voglio che sia stabile . E così la soluzione che ho trovato è quella di impostare un osservatore sull’elemento (o come nell’esempio sotto l’intero documento), per l’evento “DOMSubtreeModified”. Una volta che ho aspettato 500 millisecondi e non ci sono state modifiche DOM, trasmetto un evento come “domRendered”.

IE:

  //todo: Inject $rootScope and $window, //Every call to $window.setTimeout will use this function var broadcast = function () {}; if (document.addEventListener) { document.addEventListener("DOMSubtreeModified", function (e) { //If less than 500 milliseconds have passed, the previous broadcast will be cleared. clearTimeout(broadcast) broadcast = $window.setTimeout(function () { //This will only fire after 500 ms have passed with no changes $rootScope.$broadcast('domRendered') }, 500) }); //IE stupidity } else { document.attachEvent("DOMSubtreeModified", function (e) { clearTimeout(broadcast) broadcast = $window.setTimeout(function () { $rootScope.$broadcast('domRendered') }, 500) }); } 

Questo evento può essere collegato, come tutte le trasmissioni, in questo modo:

 $rootScope.$on("domRendered", function(){ //do something }) 

Avevo una direttiva personalizzata e avevo bisogno della proprietà height() risultante element all’interno della mia direttiva, il che significava che avevo bisogno di leggerla dopo che angular aveva eseguito l’intero $digest e il browser aveva scaricato il layout.

Nella funzione di link della mia direttiva;

Questo non ha funzionato in modo affidabile, non abbastanza tardi;

 scope.$watch(function() {}); 

Questo non era ancora abbastanza tardi;

 scope.$evalAsync(function() {}); 

Sembra che il seguente lavoro funzioni (anche con lo 0ms su Chrome) dove curiosamente persino ẁindow.setTimeout() con scope.$apply() ẁindow.setTimeout() no;

 $timeout(function() {}, 0); 

Flicker era una preoccupazione, quindi alla fine ho fatto ricorso a requestAnimationFrame() con fallback a $timeout all’interno della mia direttiva (con appropriati prefissi dei produttori, a seconda dei casi). Semplificato, questo sembra essenzialmente;

 scope.$watch("someBoundPropertyIexpectWillAlterLayout", function(n,o) { $window.requestAnimationFrame(function() { scope.$apply(function() { scope.height = element.height(); // OK, this seems to be accurate for the layout }); }); }); 

Quindi, ovviamente, posso solo usare a;

 scope.$watch("height", function() { // Adjust view model based on new layout metrics }); 

intervallo funziona per me, ad esempio:

 interval = $interval(function() { if ($("#target").children().length === 0) { return; } doSomething(); $interval.cancel(interval); }, 0);