AngularJS: Prevenire l'errore $ digest già in corso quando si chiama $ scope. $ Apply ()


838

Sto scoprendo che devo aggiornare la mia pagina al mio ambito manualmente sempre di più da quando ho creato un'applicazione in angolare.

L'unico modo che conosco per farlo è chiamare $apply()dal campo di applicazione dei miei controller e direttive. Il problema è che continua a generare un errore nella console che legge:

Errore: $ digest già in corso

Qualcuno sa come evitare questo errore o ottenere la stessa cosa ma in modo diverso?


34
È davvero frustrante il fatto che abbiamo bisogno di usare $ apply sempre di più.
OZ_

Ricevo anche questo errore, anche se sto chiamando $ apply in un callback. Sto usando una libreria di terze parti per accedere ai dati sui loro server, quindi non posso trarre vantaggio da $ http, né voglio poiché dovrei riscrivere la loro libreria per usare $ http.
Trevor,

45
uso$timeout()
Onur Yıldırım il

6
usa $ timeout (fn) + 1, può risolvere il problema,! $ scope. La fase $$ non è la soluzione migliore.
Huei Tan,

1
Solo avvolgere portata codice / call. $ Applica a decorrere dal all'interno timeout funzioni (non $ timeout) AJAX (non $ http) e gli eventi (non ng-*). Assicurarsi, se si sta chiamando dall'interno di una funzione (che si chiama via timeout / Ajax / eventi), che non è anche eseguito su carico inizialmente.
Patrick,

Risposte:


660

Non usare questo modello : questo finirà per causare più errori di quanti ne risolva. Anche se pensi che abbia risolto qualcosa, non l'ha fatto.

Puoi verificare se a $digestè già in corso controllando $scope.$$phase.

if(!$scope.$$phase) {
  //$digest or $apply
}

$scope.$$phasetornerà "$digest"o "$apply"se è in corso un $digesto $apply. Credo che la differenza tra questi stati sia che $digestelaborerà gli orologi dell'attuale ambito e dei suoi figli e $applyelaborerà gli osservatori di tutti gli ambiti.

Al punto di @dnc253, se ti trovi a chiamare $digesto $applyfrequentemente, potresti sbagliare. In genere trovo che devo digerire quando devo aggiornare lo stato dell'ambito a seguito di un evento DOM che viene generato fuori dalla portata di Angular. Ad esempio, quando un bootstrap di Twitter modale viene nascosto. A volte l'evento DOM viene generato quando $digestè in corso, a volte no. Ecco perché uso questo controllo.

Mi piacerebbe sapere un modo migliore se qualcuno lo conosce.


Dai commenti: di @anddoutoi

angular.js Anti Patterns

  1. Non farlo if (!$scope.$$phase) $scope.$apply(), significa che non sei $scope.$apply()abbastanza alto nello stack di chiamate.

230
Mi sembra che $ digest / $ apply dovrebbero farlo per impostazione predefinita
Roy Truelove,

21
Si noti che in alcuni casi devo verificare ma l'ambito corrente E l'ambito radice. Ho ottenuto un valore per la fase $$ sul root ma non sul mio ambito. Penso che abbia qualcosa a che fare con l'ambito isolato di una direttiva, ma ...
Roy Truelove,

106
"Smetti di fare if (!$scope.$$phase) $scope.$apply()", github.com/angular/angular.js/wiki/Anti-Patterns
anddoutoi

34
@anddoutoi: concordato; il tuo link chiarisce che questa non è la soluzione; tuttavia, non sono sicuro di cosa si intenda per "non sei abbastanza alto nello stack di chiamate". Sai cosa significa questo?
Trevor,

13
@threed: vedi la risposta di aaronfrost. Il modo corretto è utilizzare il differimento per attivare il digest nel ciclo successivo. Altrimenti l'evento andrà perso e non aggiornerà affatto l'ambito.
Marek,

663

Da una recente discussione con i ragazzi angolari su questo stesso argomento: per motivi di sicurezza futura, non dovresti usare$$phase

Quando viene premuto per il modo "giusto" per farlo, la risposta è attualmente

$timeout(function() {
  // anything you want can go here and will safely be run on the next digest.
})

Di recente mi sono imbattuto in questo quando scrivevo servizi angolari per avvolgere le API di Facebook, Google e Twitter che, a vari livelli, hanno consegnato callback.

Ecco un esempio all'interno di un servizio. (Per brevità, il resto del servizio - che ha impostato variabili, iniettato $ timeout ecc. - è stato lasciato fuori.)

window.gapi.client.load('oauth2', 'v2', function() {
    var request = window.gapi.client.oauth2.userinfo.get();
    request.execute(function(response) {
        // This happens outside of angular land, so wrap it in a timeout 
        // with an implied apply and blammo, we're in action.
        $timeout(function() {
            if(typeof(response['error']) !== 'undefined'){
                // If the google api sent us an error, reject the promise.
                deferred.reject(response);
            }else{
                // Resolve the promise with the whole response if ok.
                deferred.resolve(response);
            }
        });
    });
});

Si noti che l'argomento delay per $ timeout è facoltativo e per impostazione predefinita sarà 0 se non viene impostato ( $ timeout chiama $ browser.defer che per impostazione predefinita è 0 se il ritardo non è impostato )

Un po 'non intuitivo, ma questa è la risposta dei ragazzi che scrivono Angular, quindi è abbastanza buono per me!


5
L'ho incontrato molte volte nelle mie direttive. Ne scrivevo uno per redattore e questo si è rivelato perfettamente funzionante. Ero a un incontro con Brad Green e ha detto che Angular 2.0 sarà enorme senza alcun ciclo digest usando l'abilità di osservazione nativa di JS e usando un polyfill per i browser che non lo hanno. A quel punto non avremo più bisogno di farlo. :)
Michael J. Calkins,

Ieri ho riscontrato un problema in cui la chiamata a selectize.refreshItems () all'interno di $ timeout ha causato il temuto errore digest ricorsivo. Qualche idea su come potrebbe essere?
Iwein

3
Se usi $timeoutinvece di nativi setTimeout, perché non usi $windowinvece di nativi window?
LeeGee

2
@LeeGee: il punto di utilizzo $timeoutin questo caso è $timeoutche l'ambito angolare sia aggiornato correttamente. Se un $ digest non è in corso, verrà eseguito un nuovo $ digest.
soggezione

2
@webicy Non è una cosa. Quando viene eseguito il corpo della funzione passato a $ timeout, la promessa è già risolta! Non c'è assolutamente alcun motivo cancel. Dai documenti : "Di conseguenza, la promessa sarà risolta con un rifiuto." Non puoi risolvere una promessa risolta. La tua cancellazione non causerà errori, ma non farà nulla di positivo.
daemonexmachina,

324

Il ciclo digest è una chiamata sincrona. Non fornirà il controllo al ciclo degli eventi del browser fino a quando non viene eseguito. Ci sono alcuni modi per affrontarlo. Il modo più semplice per gestirlo è usare il timeout $ incorporato, e un secondo modo è se stai usando underscore o lodash (e dovresti esserlo), chiama quanto segue:

$timeout(function(){
    //any code in here will automatically have an apply run afterwards
});

o se hai lodash:

_.defer(function(){$scope.$apply();});

Abbiamo provato diverse soluzioni alternative e abbiamo odiato iniettare $ rootScope in tutti i nostri controller, direttive e persino alcune fabbriche. Quindi, $ timeout e _.defer sono stati i nostri preferiti finora. Questi metodi indicano a Angular di attendere fino al prossimo ciclo di animazione, il che garantirà che l'ambito corrente. $ Apply sia terminato.


2
È paragonabile all'uso di $ timeout (...)? Ho usato $ timeout in diversi casi per rinviare al prossimo ciclo di eventi e sembra funzionare bene - qualcuno sa se c'è un motivo per non usare $ timeout?
Trevor,

9
Questo dovrebbe davvero essere usato solo se lo stai già utilizzando underscore.js. Questa soluzione non vale la pena importare l'intera libreria di sottolineature solo per usare la sua deferfunzione. Preferisco di gran lunga la $timeoutsoluzione perché tutti hanno già accesso $timeouttramite angolare, senza dipendenze da altre librerie.
tennisgent

10
Vero ... ma se non stai usando il carattere di sottolineatura o lodash ... devi rivalutare ciò che stai facendo. Queste due librerie hanno cambiato l'aspetto del codice.
gelido

2
Abbiamo lodash come dipendenza per Restangular (elimineremo Restangular a favore di ng-route presto). Penso che sia una buona risposta, ma non è bello supporre che le persone vogliano usare il trattino basso / lodash. Certamente quelle librerie vanno bene ... se le usi abbastanza ... al giorno d'oggi uso i metodi ES5 che eliminano il 98% del motivo che ho usato per includere il trattino basso.
BradGreens,

2
Hai ragione @SgtPooki. Ho modificato la risposta per includere anche l'opzione per utilizzare $ timeout. $ timeout e _.defer aspetteranno entrambi fino al prossimo ciclo di animazione, il che assicurerà che l'ambito corrente. $ apply sia terminato. Grazie per avermi reso onesto e avermi fatto aggiornare la risposta qui.
gelido,

267

Molte delle risposte qui contengono buoni consigli ma possono anche creare confusione. Semplicemente non lo$timeout è la soluzione migliore né quella giusta. Inoltre, assicurati di leggerlo se sei preoccupato per prestazioni o scalabilità.

Cose che dovresti sapere

  • $$phase è privato del framework e ci sono buone ragioni per questo.

  • $timeout(callback)attenderà fino al completamento del ciclo di digest corrente (se presente), quindi eseguirà il callback, quindi verrà eseguito alla fine per intero $apply.

  • $timeout(callback, delay, false)farà lo stesso (con un ritardo opzionale prima di eseguire il callback), ma non genererà un $apply(terzo argomento) che salva le prestazioni se non si modifica il modello angolare ($ scope).

  • $scope.$apply(callback)invoca, tra le altre cose, il $rootScope.$digestche significa che ridigesterà l'ambito radice dell'applicazione e tutti i suoi figli, anche se ti trovi in ​​un ambito isolato.

  • $scope.$digest()sincronizzerà semplicemente il suo modello con la vista, ma non digerirà l'ambito dei genitori, il che può salvare molte prestazioni quando si lavora su una parte isolata del proprio HTML con un ambito isolato (principalmente da una direttiva). $ digest non accetta un callback: esegui il codice, quindi digerisci.

  • $scope.$evalAsync(callback)è stato introdotto con angularjs 1.2 e probabilmente risolverà la maggior parte dei tuoi problemi. Si prega di fare riferimento all'ultimo paragrafo per saperne di più.

  • se ottieni $digest already in progress error, allora la tua architettura è sbagliata: o non hai bisogno di ridigestire il tuo ambito, o non dovresti esserne responsabile (vedi sotto).

Come strutturare il tuo codice

Quando ricevi questo errore, stai cercando di digerire il tuo ambito mentre è già in corso: dal momento che non conosci lo stato del tuo ambito a quel punto, non sei responsabile di gestirne la digestione.

function editModel() {
  $scope.someVar = someVal;
  /* Do not apply your scope here since we don't know if that
     function is called synchronously from Angular or from an
     asynchronous code */
}

// Processed by Angular, for instance called by a ng-click directive
$scope.applyModelSynchronously = function() {
  // No need to digest
  editModel();
}

// Any kind of asynchronous code, for instance a server request
callServer(function() {
  /* That code is not watched nor digested by Angular, thus we
     can safely $apply it */
  $scope.$apply(editModel);
});

E se sai cosa stai facendo e lavorando su una piccola direttiva isolata mentre fa parte di una grande applicazione angolare, potresti preferire $ digest invece di $ si applica per salvare le prestazioni.

Aggiornamento da Angularjs 1.2

Un nuovo e potente metodo è stato aggiunto a qualsiasi $ scope: $evalAsync . Fondamentalmente, eseguirà il callback all'interno del ciclo digest attuale se si verifica uno, altrimenti un nuovo ciclo digest inizierà l'esecuzione del callback.

Questo non è ancora buono come $scope.$digestse sapessi davvero che devi solo sincronizzare una parte isolata del tuo HTML (poiché un nuovo $applyverrà attivato se nessuno è in corso), ma questa è la soluzione migliore quando stai eseguendo una funzione che non è possibile sapere se verrà eseguito in modo sincrono o meno , ad esempio dopo aver recuperato una risorsa potenzialmente memorizzata nella cache: a volte ciò richiederà una chiamata asincrona a un server, altrimenti la risorsa verrà recuperata localmente in modo sincrono.

In questi casi e tutti gli altri in cui hai avuto un !$scope.$$phase, assicurati di usare$scope.$evalAsync( callback )


4
$timeoutè criticato di passaggio. Puoi dare più motivi per evitare $timeout?
mlhDev,

88

Pratico metodo di supporto per mantenere questo processo ASCIUTTO:

function safeApply(scope, fn) {
    (scope.$$phase || scope.$root.$$phase) ? fn() : scope.$apply(fn);
}

6
Your safeApply mi ha aiutato a capire cosa stava succedendo molto più di ogni altra cosa. Grazie per averlo pubblicato.
Jason More,

4
Stavo per fare la stessa cosa, ma questo non significa che c'è una possibilità che i cambiamenti che facciamo in fn () non vengano visti da $ digest? Non sarebbe meglio ritardare la funzione, assumendo l'ambito. $$ phase === '$ digest'?
Spencer Alger,

Sono d'accordo, a volte $ apply () è usato per innescare il digest, semplicemente chiamando il fn da solo ... non si tradurrà in un problema?
CMCDragonkai,

1
Penso che scope.$apply(fn);dovrebbe essere scope.$apply(fn());perché fn () eseguirà la funzione e non fn. Ti prego, aiutami dove ho sbagliato
madhu131313,

1
@ZenOut La chiamata a $ apply supporta molti tipi diversi di argomenti, incluse le funzioni. Se viene passata una funzione, la valuta.
Boxmein

33

Ho avuto lo stesso problema con script di terze parti come CodeMirror per esempio e Krpano, e anche usando i metodi safeApply menzionati qui non ho risolto l'errore per me.

Ma cosa ha risolto è usare $ timeout service (non dimenticare di iniettarlo prima).

Quindi, qualcosa del tipo:

$timeout(function() {
  // run my code safely here
})

e se nel tuo codice stai usando

Questo

forse perché è all'interno del controller di una direttiva di fabbrica o ha solo bisogno di un qualche tipo di associazione, quindi faresti qualcosa del tipo:

.factory('myClass', [
  '$timeout',
  function($timeout) {

    var myClass = function() {};

    myClass.prototype.surprise = function() {
      // Do something suprising! :D
    };

    myClass.prototype.beAmazing = function() {
      // Here 'this' referes to the current instance of myClass

      $timeout(angular.bind(this, function() {
          // Run my code safely here and this is not undefined but
          // the same as outside of this anonymous function
          this.surprise();
       }));
    }

    return new myClass();

  }]
)

32

Vedi http://docs.angularjs.org/error/$rootScope:inprog

Il problema sorge quando si riceve una chiamata $applyche a volte viene eseguita in modo asincrono al di fuori del codice angolare (quando si deve usare $ apply) e talvolta in modo sincrono all'interno del codice angolare (che causa il$digest already in progress errore).

Ciò può accadere, ad esempio, quando si dispone di una libreria che recupera in modo asincrono elementi da un server e li memorizza nella cache. La prima volta che un elemento viene richiesto, verrà recuperato in modo asincrono in modo da non bloccare l'esecuzione del codice. La seconda volta, tuttavia, l'elemento è già nella cache, quindi può essere recuperato in modo sincrono.

Il modo per prevenire questo errore è garantire che il codice che chiama $applysia eseguito in modo asincrono. Questo può essere fatto eseguendo il codice all'interno di una chiamata $timeoutcon il ritardo impostato su 0(che è l'impostazione predefinita). Tuttavia, chiamare il codice all'interno $timeoutrimuove la necessità di chiamare $apply, perché $ timeout ne attiverà un altro$digest ciclo da solo, che a sua volta eseguirà tutti gli aggiornamenti necessari, ecc.

Soluzione

In breve, invece di farlo:

... your controller code...

$http.get('some/url', function(data){
    $scope.$apply(function(){
        $scope.mydate = data.mydata;
    });
});

... more of your controller code...

Fai questo:

... your controller code...

$http.get('some/url', function(data){
    $timeout(function(){
        $scope.mydate = data.mydata;
    });
});

... more of your controller code...

Chiama solo $apply quando sai che il codice in esecuzione verrà sempre eseguito al di fuori del codice angolare (ad es. La tua chiamata a $ apply avverrà all'interno di un callback chiamato da un codice esterno al tuo codice angolare).

A meno che qualcuno non sia a conoscenza di uno svantaggio di impatto sull'uso di $timeoutover $apply, non vedo perché non si possa sempre usare $timeout(con zero delay) invece di $apply, poiché farà circa la stessa cosa.


Grazie, questo ha funzionato per il mio caso in cui non mi sto chiamando $applyma sto ancora ricevendo l'errore.
Ariscris,

5
La differenza principale è che $applyè sincrono (il suo callback viene eseguito, quindi il codice che segue $ si applica) mentre $timeoutnon lo è: il codice corrente dopo il timeout viene eseguito, quindi un nuovo stack inizia con il suo callback, come se si stesse utilizzando setTimeout. Ciò potrebbe causare problemi grafici se si aggiornasse due volte lo stesso modello: $timeoutattenderà l'aggiornamento della vista prima di aggiornarlo di nuovo.
floribon,

Grazie davvero, tre. Avevo un metodo chiamato come risultato di alcune attività $ watch e stavo cercando di aggiornare l'interfaccia utente prima che il mio filtro esterno avesse terminato l'esecuzione. Metterlo all'interno di una funzione $ timeout ha funzionato per me.
Djmarquette,

28

Quando viene visualizzato questo errore, significa sostanzialmente che è già in fase di aggiornamento della vista. Non dovresti davvero aver bisogno di chiamare dal $apply()tuo controller. Se la tua vista non si aggiorna come ti aspetteresti e quindi ricevi questo errore dopo aver chiamato $apply(), molto probabilmente significa che non stai aggiornando il modello correttamente. Se pubblichi alcuni dettagli, potremmo capire il problema principale.


eh, ho passato tutto il giorno a scoprire che AngularJS non è in grado di guardare i legami "magicamente" e dovrei spingerlo a volte con $ apply ().
OZ_

cosa significa affatto you're not updating the the model correctly? $scope.err_message = 'err message';l'aggiornamento non è corretto?
OZ_

2
L'unica volta che devi chiamare $apply()è quando aggiorni il modello "esterno" di angolare (ad esempio da un plugin jQuery). È facile cadere nella trappola della vista non guardando bene, e quindi lanci un sacco di $apply()s ovunque, che finisce con l'errore visto nell'OP. Quando dico you're not updating the the model correctlyintendo solo tutta la logica aziendale che non popola correttamente tutto ciò che potrebbe essere nell'ambito, il che porta alla vista non apparire come previsto.
dnc253,

@ dnc253 Sono d'accordo e ho scritto la risposta. Sapendo quello che so ora, userei $ timeout (function () {...}); Fa la stessa cosa di _.defer. Entrambi rimandano al prossimo ciclo di animazione.
gelido,

14

La forma più breve di sicurezza $applyè:

$timeout(angular.noop)

11

Puoi anche usare evalAsync. Funzionerà qualche volta dopo che digest è terminato!

scope.evalAsync(function(scope){
    //use the scope...
});

10

Prima di tutto, non aggiustarlo in questo modo

if ( ! $scope.$$phase) { 
  $scope.$apply(); 
}

Non ha senso perché $ phase è solo un flag booleano per il ciclo $ digest, quindi il tuo $ apply () a volte non verrà eseguito. E ricorda che è una cattiva pratica.

Invece, usa $timeout

    $timeout(function(){ 
  // Any code in here will automatically have an $scope.apply() run afterwards 
$scope.myvar = newValue; 
  // And it just works! 
});

Se stai utilizzando il trattino basso o lodash, puoi usare defer ():

_.defer(function(){ 
  $scope.$apply(); 
});

9

A volte otterrai comunque errori se lo usi in questo modo ( https://stackoverflow.com/a/12859093/801426 ).

Prova questo:

if(! $rootScope.$root.$$phase) {
...

5
usando entrambi! $ scope. $$ phase e! $ scope. $ root. $$ phase (non! $ rootScope. $ root. $$ phase) funziona per me. +1
asprotte,

2
$rootScopee anyScope.$rootsono lo stesso ragazzo. $rootScope.$rootè ridondante.
floribon,


5

prova ad usare

$scope.applyAsync(function() {
    // your code
});

invece di

if(!$scope.$$phase) {
  //$digest or $apply
}

$ applyAsync Pianifica che l'invocazione di $ apply si verifichi in un secondo momento. Questo può essere usato per mettere in coda più espressioni che devono essere valutate nello stesso digest.

NOTA: All'interno di $ digest, $ applyAsync () scaricherà solo se l'ambito corrente è $ rootScope. Ciò significa che se si chiama $ digest su un ambito figlio, non verrà eliminata implicitamente la coda $ applyAsync ().

Exmaple:

  $scope.$applyAsync(function () {
                if (!authService.authenticated) {
                    return;
                }

                if (vm.file !== null) {
                    loadService.setState(SignWizardStates.SIGN);
                } else {
                    loadService.setState(SignWizardStates.UPLOAD_FILE);
                }
            });

Riferimenti:

1. Scope. $ ApplyAsync () vs. Scope. $ EvalAsync () in AngularJS 1.3

  1. AngularJs Docs

4

Ti consiglierei di utilizzare un evento personalizzato anziché attivare un ciclo digest.

Sono venuto a scoprire che trasmettere eventi personalizzati e registrare ascoltatori per questi eventi è una buona soluzione per innescare un'azione che si desidera che si verifichi indipendentemente dal fatto che si sia o meno in un ciclo digest.

Creando un evento personalizzato stai anche diventando più efficiente con il tuo codice perché stai solo innescando i listener iscritti a detto evento e NON innescando tutti gli orologi legati all'ambito come faresti se invocassi l'ambito. $ Apply.

$scope.$on('customEventName', function (optionalCustomEventArguments) {
   //TODO: Respond to event
});


$scope.$broadcast('customEventName', optionalCustomEventArguments);

3

yearofmoo ha fatto un ottimo lavoro nel creare una funzione riutilizzabile $ safeApply per noi:

https://github.com/yearofmoo/AngularJS-Scope.SafeApply

Utilizzo:

//use by itself
$scope.$safeApply();

//tell it which scope to update
$scope.$safeApply($scope);
$scope.$safeApply($anotherScope);

//pass in an update function that gets called when the digest is going on...
$scope.$safeApply(function() {

});

//pass in both a scope and a function
$scope.$safeApply($anotherScope,function() {

});

//call it on the rootScope
$rootScope.$safeApply();
$rootScope.$safeApply($rootScope);
$rootScope.$safeApply($scope);
$rootScope.$safeApply($scope, fn);
$rootScope.$safeApply(fn);

2

Sono stato in grado di risolvere questo problema chiamando $evalinvece che $applyin luoghi in cui so che la $digestfunzione sarà in esecuzione.

Secondo i documenti , $applysostanzialmente fa questo:

function $apply(expr) {
  try {
    return $eval(expr);
  } catch (e) {
    $exceptionHandler(e);
  } finally {
    $root.$digest();
  }
}

Nel mio caso, un ng-clickcambia una variabile all'interno di un ambito e un $ watch su quella variabile cambia altre variabili che devono essere $applied. Quest'ultimo passaggio provoca l'errore "digest già in corso".

Sostituendo $applycon $evalall'interno dell'espressione watch le variabili dell'ambito vengono aggiornate come previsto.

Pertanto, sembra che se digest verrà eseguito comunque a causa di qualche altro cambiamento all'interno di Angular, $eval'ing è tutto ciò che devi fare.



1

Comprendendo che i documenti angolari chiamano il controllo di $$phaseun anti-schema , ho cercato di ottenere $timeoute _.deferlavorare.

I metodi di timeout e differiti creano un lampo di {{myVar}}contenuti non analizzati nel dom come un FOUT . Per me questo non era accettabile. Non mi viene detto molto dogmaticamente che qualcosa è un hack e non ha un'alternativa adeguata.

L'unica cosa che funziona ogni volta è:

if(scope.$$phase !== '$digest'){ scope.$digest() }.

Non capisco il pericolo di questo metodo o perché sia ​​descritto come un hack dalle persone nei commenti e dalla squadra angolare. Il comando sembra preciso e di facile lettura:

"Fai il digest a meno che uno non stia già accadendo"

In CoffeeScript è ancora più bello:

scope.$digest() unless scope.$$phase is '$digest'

Qual è il problema con questo? Esiste un'alternativa che non creerà un FOUT? $ safeApply ha un bell'aspetto ma utilizza anche il $$phasemetodo di ispezione.


1
Mi piacerebbe vedere una risposta informata a questa domanda!
Ben Wheeler,

È un hack perché significa che ti manca il contesto o non capisci il codice a questo punto: o sei all'interno del ciclo digest angolare e non ne hai bisogno, oppure sei asincrono al di fuori di quello e quindi ne hai bisogno. Se non lo sai in quel punto del codice, non sei responsabile di digerirlo
floribon,

1

Questo è il mio servizio utils:

angular.module('myApp', []).service('Utils', function Utils($timeout) {
    var Super = this;

    this.doWhenReady = function(scope, callback, args) {
        if(!scope.$$phase) {
            if (args instanceof Array)
                callback.apply(scope, Array.prototype.slice.call(args))
            else
                callback();
        }
        else {
            $timeout(function() {
                Super.doWhenReady(scope, callback, args);
            }, 250);
        }
    };
});

e questo è un esempio per il suo utilizzo:

angular.module('myApp').controller('MyCtrl', function ($scope, Utils) {
    $scope.foo = function() {
        // some code here . . .
    };

    Utils.doWhenReady($scope, $scope.foo);

    $scope.fooWithParams = function(p1, p2) {
        // some code here . . .
    };

    Utils.doWhenReady($scope, $scope.fooWithParams, ['value1', 'value2']);
};

1

Ho usato questo metodo e sembra funzionare perfettamente. Ciò attende solo il termine del ciclo e quindi si innesca apply(). Basta chiamare la funzione apply(<your scope>)da qualsiasi luogo tu voglia.

function apply(scope) {
  if (!scope.$$phase && !scope.$root.$$phase) {
    scope.$apply();
    console.log("Scope Apply Done !!");
  } 
  else {
    console.log("Scheduling Apply after 200ms digest cycle already in progress");
    setTimeout(function() {
        apply(scope)
    }, 200);
  }
}

1

Quando ho disabilitato il debugger, l'errore non si verifica più. Nel mio caso , è stato a causa del debugger che ha interrotto l'esecuzione del codice.


0

simile alle risposte sopra ma questo ha funzionato fedelmente per me ... in un servizio aggiungere:

    //sometimes you need to refresh scope, use this to prevent conflict
    this.applyAsNeeded = function (scope) {
        if (!scope.$$phase) {
            scope.$apply();
        }
    };

0

Puoi usare

$timeout

per prevenire l'errore.

 $timeout(function () {
                        var scope = angular.element($("#myController")).scope();
                        scope.myMethod();
                        scope.$scope();
                    },1);

E se non volessi usare $ timeout
rahim.nagori

0

Il problema sorge fondamentalmente quando, stiamo chiedendo ad angolare di eseguire il ciclo digest anche se è in corso un processo che sta creando problemi angolari alla comprensione. conseguenza conseguenza nella console.
1. Non ha alcun senso chiamare scope. $ Apply () all'interno della funzione $ timeout perché internamente fa lo stesso.
2. Il codice va con la funzione JavaScript vaniglia perché il suo angolo non angolare nativo è definito come setTimeout
3. Per fare ciò puoi usare

if (! Scope. $$ phase) {
scope. $ EvalAsync (function () {

}); }


0
        let $timeoutPromise = null;
        $timeout.cancel($timeoutPromise);
        $timeoutPromise = $timeout(() => {
            $scope.$digest();
        }, 0, false);

Ecco una buona soluzione per evitare questo errore ed evitare $ apply

è possibile combinarlo con debounce (0) se si chiama in base a un evento esterno. Sopra c'è il "rimbalzo" che stiamo usando e un esempio completo di codice

.factory('debounce', [
    '$timeout',
    function ($timeout) {

        return function (func, wait, apply) {
            // apply default is true for $timeout
            if (apply !== false) {
                apply = true;
            }

            var promise;
            return function () {
                var cntx = this,
                    args = arguments;
                $timeout.cancel(promise);
                promise = $timeout(function () {
                    return func.apply(cntx, args);
                }, wait, apply);
                return promise;
            };
        };
    }
])

e il codice stesso per ascoltare alcuni eventi e chiamare $ digest solo su $ scope necessario

        let $timeoutPromise = null;
        let $update = debounce(function () {
            $timeout.cancel($timeoutPromise);
            $timeoutPromise = $timeout(() => {
                $scope.$digest();
            }, 0, false);
        }, 0, false);

        let $unwatchModelChanges = $scope.$root.$on('updatePropertiesInspector', function () {
            $update();
        });


        $scope.$on('$destroy', () => {
            $timeout.cancel($update);
            $timeout.cancel($timeoutPromise);
            $unwatchModelChanges();
        });

-3

Trovato questo: https://coderwall.com/p/ngisma dove Nathan Walker (vicino alla fine della pagina) suggerisce un decoratore in $ rootScope per creare funzioni 'safeApply', codice:

yourAwesomeModule.config([
  '$provide', function($provide) {
    return $provide.decorator('$rootScope', [
      '$delegate', function($delegate) {
        $delegate.safeApply = function(fn) {
          var phase = $delegate.$$phase;
          if (phase === "$apply" || phase === "$digest") {
            if (fn && typeof fn === 'function') {
              fn();
            }
          } else {
            $delegate.$apply(fn);
          }
        };
        return $delegate;
      }
    ]);
  }
]);

-7

Questo risolverà il tuo problema:

if(!$scope.$$phase) {
  //TODO
}

Non fare se (! $ Scope. $$ phase) $ scope. $ Apply (), significa che $ scope. $ Apply () non è abbastanza alto nello stack di chiamate.
MGot90,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.