AngularJS: Impedir o erro $ digest já em andamento ao chamar $ scope. $ Apply ()

838

Estou descobrindo que preciso atualizar minha página para meu escopo manualmente cada vez mais desde a criação de um aplicativo em angular.

A única maneira que sei fazer isso é chamar $apply()do escopo dos meus controladores e diretivas. O problema é que ele continua lançando um erro no console que lê:

Erro: $ digest já está em andamento

Alguém sabe como evitar esse erro ou conseguir a mesma coisa, mas de uma maneira diferente?

Lâmpada1
fonte
34
É algo realmente frustrante que precisamos usar $ apply cada vez mais.
OZ_
Também estou recebendo esse erro, mesmo que eu esteja chamando $ apply em um retorno de chamada. Estou usando uma biblioteca de terceiros para acessar dados em seus servidores, portanto não posso aproveitar o $ http, nem quero, pois precisaria reescrever a biblioteca para usar o $ http.
Trevor Trevor
45
use$timeout()
Onur Yıldırım
6
use $ timeout (fn) + 1, ele pode resolver o problema, a fase $ $ scope. $$ não é a melhor solução.
Huei Tan 26/03
1
Apenas enrole âmbito código / call. $ Aplicáveis a partir de dentro o tempo limite funções (não $ timeout) AJAX (não $ http) e eventos (não ng-*). Certifique-se de que, se você estiver chamando de dentro de uma função (chamada via timeout / ajax / events), ela também não esteja sendo executada no carregamento inicialmente.
247 Patrick

Respostas:

660

Não use esse padrão - isso acabará causando mais erros do que resolve. Mesmo que você pense que consertou algo, não o fez.

Você pode verificar se um $digest já está em andamento, marcando $scope.$$phase.

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

$scope.$$phaseretornará "$digest"ou"$apply" se um $digestou $applyestiver em andamento. Acredito que a diferença entre esses estados é que $digestprocessará os relógios do escopo atual e seus filhos e $applyos observadores de todos os escopos.

Ao ponto de @ dnc253, se você estiver ligando $digestou com $applyfrequência, pode estar fazendo errado. Geralmente, acho que preciso digerir quando preciso atualizar o estado do escopo como resultado de um evento DOM disparado fora do alcance do Angular. Por exemplo, quando um modal de inicialização do twitter fica oculto. Às vezes, o evento DOM é acionado quando a $digestestá em andamento, às vezes não. É por isso que eu uso essa verificação.

Eu adoraria saber uma maneira melhor se alguém conhece uma.


Dos comentários: por @anddoutoi

angular.js Anti Padrões

  1. Não faça if (!$scope.$$phase) $scope.$apply(), isso significa que você $scope.$apply()não é alto o suficiente na pilha de chamadas.
Lee
fonte
230
Parece-me que $ digerir / $ aplicar deve fazer isso por padrão
Roy Truelove
21
Observe que, em alguns casos, tenho que verificar apenas o escopo atual E o escopo raiz. Estou recebendo um valor para a fase $$ na raiz, mas não no meu escopo. Acho que tem algo a ver com o âmbito isolado de uma directiva, mas ..
Roy Truelove
106
"Pare de fazer if (!$scope.$$phase) $scope.$apply()", github.com/angular/angular.js/wiki/Anti-Patterns
anddoutoi
34
@anddoutoi: Concordou; seu link deixa bem claro que essa não é a solução; no entanto, não sei o que significa "você não é alto o suficiente na pilha de chamadas". Você sabe o que isso significa?
Trevor
13
@ threed: veja a resposta por aaronfrost. A maneira correta é usar o adiamento para acionar o resumo no próximo ciclo. Caso contrário, o evento será perdido e não atualizará o escopo.
Marek
663

De uma discussão recente com o pessoal da Angular sobre esse mesmo tópico: Por motivos de previsão futura, você não deve usar$$phase

Quando pressionada para a maneira "correta" de fazê-lo, a resposta está atualmente

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

Recentemente, me deparei com isso ao escrever serviços angulares para agrupar as APIs do facebook, google e twitter que, em graus variados, têm retornos de chamada entregues.

Aqui está um exemplo de dentro de um serviço. (Por uma questão de brevidade, o restante do serviço - que configurou variáveis, injetou $ timeout etc. - foi deixado de fora.)

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);
            }
        });
    });
});

Observe que o argumento de atraso para $ timeout é opcional e será padronizado como 0 se não estiver definido ( $ timeout chama $ browser.defer que assume como padrão 0 se o atraso não estiver definido )

Um pouco não-intuitivo, mas essa é a resposta dos caras que escrevem Angular, então é bom o suficiente para mim!

betaorbust
fonte
5
Eu já me deparei com isso muitas vezes em minhas diretivas. Estava escrevendo um para redator e isso acabou funcionando perfeitamente. Eu estava em uma reunião com Brad Green e ele disse que o Angular 2.0 será enorme sem ciclo de digestão, usando a capacidade de observação nativa de JS e usando um polyfill para navegadores que não possuem. Nesse ponto, não precisaremos mais fazer isso. :)
Michael J. Calkins
Ontem, vi um problema em que chamar selectize.refreshItems () dentro de $ timeout causou o terrível erro de digitação recursiva. Alguma idéia de como isso poderia ser?
Iwein 12/03/2014
3
Se você usa em $timeoutvez de nativo setTimeout, por que não usa em $windowvez do nativo window?
LeeGee
2
@LeeGee: O objetivo do uso $timeoutneste caso é $timeoutgarantir que o escopo angular seja atualizado corretamente. Se um $ digest não estiver em andamento, ele fará com que um novo $ digest seja executado.
temor
2
@webicy Isso não é uma coisa. Quando o corpo da função passado para $ timeout é executado, a promessa já está resolvida! Não há absolutamente nenhuma razão para cancelisso. Dos documentos : "Como resultado disso, a promessa será resolvida com uma rejeição". Você não pode resolver uma promessa resolvida. Seu cancelamento não causará erros, mas também não fará nada de positivo.
Daemonexmachina
324

O ciclo de resumo é uma chamada síncrona. Ele não produzirá controle para o loop de eventos do navegador até que esteja pronto. Existem algumas maneiras de lidar com isso. A maneira mais fácil de lidar com isso é usar o $ timeout embutido, e uma segunda maneira é se você estiver usando sublinhado ou lodash (e deveria), chame o seguinte:

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

ou se você tiver lodash:

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

Tentamos várias soluções alternativas e odiamos injetar $ rootScope em todos os nossos controladores, diretivas e até em algumas fábricas. Portanto, o $ timeout e o _.defer foram os nossos favoritos até agora. Esses métodos dizem ao angular para aguardar até o próximo loop de animação, o que garantirá que o escopo atual. $ Apply esteja encerrado.

gelado
fonte
2
Isso é comparável ao uso de $ timeout (...)? Eu usei $ timeout em vários casos para adiar para o próximo ciclo de eventos e parece funcionar bem - alguém sabe se existe um motivo para não usar $ timeout?
Trevor
9
Isso realmente só deve ser usado se você já estiver usando underscore.js. Não vale a pena importar esta solução toda a biblioteca de sublinhados apenas para usar sua deferfunção. Eu prefiro a $timeoutsolução porque todo mundo já tem acesso $timeoutatravés do angular, sem nenhuma dependência de outras bibliotecas.
tennisgent
10
É verdade ... mas se você não estiver usando sublinhado ou lodash ... precisará reavaliar o que está fazendo. Essas duas bibliotecas mudaram a aparência do código.
gelado
2
Temos o lodash como uma dependência do Restangular (em breve, vamos eliminar o Restangular em favor da rota ng). Eu acho que é uma boa resposta, mas não é ótimo supor que as pessoas querem usar sublinhado / lodash. Por todos os meios, essas bibliotecas estão bem ... se você as utilizar o suficiente ... hoje em dia eu uso métodos ES5 que eliminam 98% do motivo pelo qual costumava incluir sublinhado.
BradGreens
2
Você está certo @SgtPooki. Modifiquei a resposta para incluir a opção de usar $ timeout também. $ timeout e _.defer aguardam até o próximo loop de animação, o que garantirá que o escopo atual. $ apply seja encerrado. Obrigado por me manter honesto e por me atualizar a resposta aqui.
gelado
267

Muitas das respostas aqui contêm bons conselhos, mas também podem causar confusão. Simplesmente usar não$timeout é a melhor nem a solução certa. Leia também que, se você estiver preocupado com o desempenho ou a escalabilidade.

Coisas que você deve saber

  • $$phase é privado para a estrutura e existem boas razões para isso.

  • $timeout(callback)esperará até que o ciclo de resumo atual (se houver) seja concluído, execute o retorno de chamada e execute no final $apply.

  • $timeout(callback, delay, false)fará o mesmo (com um atraso opcional antes de executar o retorno de chamada), mas não acionará um $apply(terceiro argumento) que salvará os desempenhos se você não modificar seu modelo Angular ($ scope).

  • $scope.$apply(callback)invoca, entre outras coisas, o $rootScope.$digestque significa que ele redigestará o escopo raiz do aplicativo e todos os seus filhos, mesmo se você estiver dentro de um escopo isolado.

  • $scope.$digest()simplesmente sincronizará seu modelo com a visualização, mas não digerirá o escopo de seus pais, o que pode economizar muitas performances ao trabalhar em uma parte isolada do seu HTML com um escopo isolado (principalmente de uma diretiva). O $ digest não recebe um retorno de chamada: você executa o código e digere.

  • $scope.$evalAsync(callback)foi introduzido com o angularjs 1.2 e provavelmente resolverá a maioria dos seus problemas. Por favor, consulte o último parágrafo para saber mais sobre isso.

  • se você obtiver o $digest already in progress error, sua arquitetura está errada: você não precisa redigestar seu escopo ou não deve ser responsável por isso (veja abaixo).

Como estruturar seu código

Quando você recebe esse erro, está tentando digerir seu escopo enquanto ele ainda está em andamento: como você não conhece o estado do seu escopo naquele momento, não é responsável por lidar com a digestão.

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 você souber o que está fazendo e trabalhando em uma pequena diretiva isolada enquanto faz parte de um grande aplicativo Angular, pode preferir $ digest em vez de $ apply para salvar as performances.

Atualização desde o Angularjs 1.2

Um método novo e poderoso foi adicionado a qualquer escopo $: $evalAsync . Basicamente, ele executará seu retorno de chamada no ciclo de resumo atual, se houver algum; caso contrário, um novo ciclo de resumo começará a executar o retorno de chamada.

Isso ainda não é tão bom quanto $scope.$digestse você realmente sabe que precisa sincronizar apenas uma parte isolada do seu HTML (já que um novo $applyserá acionado se nenhum estiver em andamento), mas esta é a melhor solução quando você estiver executando uma função que você não pode saber se será executado de forma síncrona ou não , por exemplo, depois de buscar um recurso potencialmente armazenado em cache: às vezes, isso exigirá uma chamada assíncrona para um servidor, caso contrário, o recurso será buscado localmente de forma síncrona.

Nestes casos e em todos os outros em que você teve um !$scope.$$phase, não se esqueça de usar$scope.$evalAsync( callback )

floribon
fonte
4
$timeouté criticado de passagem. Você pode dar mais motivos para evitar $timeout?
mlhDev 3/17/17
88

Método auxiliar pequeno e útil para manter este processo SECO:

function safeApply(scope, fn) {
    (scope.$$phase || scope.$root.$$phase) ? fn() : scope.$apply(fn);
}
lambinador
fonte
6
Sua aplicação Safe me ajudou a entender o que estava acontecendo muito mais do que qualquer outra coisa. Obrigado por postar isso.
Jason Mais
4
Eu estava prestes a fazer a mesma coisa, mas isso não significa que há uma chance de as alterações que fazemos em fn () não serem vistas por $ digest? Não seria melhor atrasar a função, assumindo o escopo. $$ phase === '$ digest'?
Spencer Alger
Eu concordo, às vezes $ apply () é usado para acionar o resumo, apenas chamando o fn por si só ... isso não resultará em um problema?
CMCDragonkai
1
Eu sinto que scope.$apply(fn);deveria ser scope.$apply(fn());porque fn () executará a função e não fn. Por favor me ajude a onde estou errado #
madhu131313
1
@ZenOut A chamada para $ apply suporta muitos tipos diferentes de argumentos, incluindo funções. Se aprovada uma função, ela avalia a função.
precisa saber é o seguinte
33

Eu tive o mesmo problema com scripts de terceiros, como o CodeMirror, por exemplo, e o Krpano, e mesmo usando os métodos safeApply mencionados aqui não resolveu o erro para mim.

Mas o que o resolveu está usando o serviço $ timeout (não se esqueça de injetá-lo primeiro).

Assim, algo como:

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

e se dentro do seu código você estiver usando

isto

talvez porque esteja dentro do controlador de uma diretiva de fábrica ou precise apenas de algum tipo de ligação, você faria algo como:

.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();

  }]
)
Ciul
fonte
32

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

O problema surge quando você $applyrecebe uma chamada que, às vezes, é executada de forma assíncrona fora do código Angular (quando $ apply deve ser usado) e, às vezes, de forma síncrona dentro do código Angular (que causa o erro$digest already in progress erro).

Isso pode acontecer, por exemplo, quando você tem uma biblioteca que busca assincronamente itens de um servidor e os armazena em cache. Na primeira vez que um item é solicitado, ele é recuperado de forma assíncrona para não bloquear a execução do código. Na segunda vez, no entanto, o item já está no cache para que possa ser recuperado de forma síncrona.

A maneira de evitar esse erro é garantir que o código que chama $applyseja executado de forma assíncrona. Isso pode ser feito executando o código dentro de uma chamada $timeoutcom o atraso definido como 0(que é o padrão). No entanto, chamar seu código para dentro $timeoutremove a necessidade de chamar $apply, porque $ timeout acionará outro $digestciclo por si só, que, por sua vez, fará toda a atualização necessária etc.

Solução

Em resumo, em vez de fazer isso:

... your controller code...

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

... more of your controller code...

faça isso:

... your controller code...

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

... more of your controller code...

Somente ligue $applyquando você souber que o código em execução será sempre executado fora do código Angular (por exemplo, sua chamada para $ apply ocorrerá dentro de um retorno de chamada chamado por código fora do código Angular).

A menos que alguém esteja ciente de alguma desvantagem impactante do uso $timeoutexcessivo $apply, não vejo por que você nem sempre pode usar $timeout(com atraso zero) em vez de $apply, pois fará aproximadamente a mesma coisa.

Trevor
fonte
Obrigado, isso funcionou para o meu caso em que não estou me chamando, $applymas ainda estou recebendo o erro.
Ariscris
5
A principal diferença é que $applyé síncrona (seu retorno de chamada é executado e o código a seguir $ apply) enquanto $timeoutnão é: o código atual após o tempo limite é executado e, em seguida, uma nova pilha começa com o retorno de chamada, como se você estivesse usando setTimeout. Isso pode levar a falhas gráficas se você estiver atualizando duas vezes o mesmo modelo: $timeoutaguardará a atualização da visualização antes de atualizá-la novamente.
Floribon
Obrigado de fato, threed. Eu tinha um método chamado como resultado de alguma atividade $ watch e estava tentando atualizar a interface do usuário antes que meu filtro externo terminasse de executar. Colocar isso dentro de uma função $ timeout funcionou para mim.
21414 djmarquette
28

Quando você recebe esse erro, basicamente significa que ele já está no processo de atualização da sua exibição. Você realmente não precisa ligar $apply()dentro do seu controlador. Se sua visualização não estiver sendo atualizada conforme o esperado, e você receber esse erro após a chamada $apply(), provavelmente significa que não está atualizando o modelo corretamente. Se você postar alguns detalhes, poderíamos descobrir o problema principal.

dnc253
fonte
heh, passei o dia inteiro descobrindo que o AngularJS simplesmente não pode assistir as ligações "magicamente" e às vezes eu deveria pressioná-lo com $ apply ().
OZ_
o que significa you're not updating the the model correctly? $scope.err_message = 'err message';atualização não está correta?
OZ_
2
O único momento em que você precisa ligar $apply()é quando você atualiza o modelo "fora" do angular (por exemplo, a partir de um plugin jQuery). É fácil cair na armadilha da vista que não parece correta e, assim, você lança vários $apply()s em todos os lugares, o que acaba com o erro visto no OP. Quando digo you're not updating the the model correctly, apenas quero dizer que toda a lógica de negócios não está preenchendo corretamente qualquer coisa que possa estar no escopo, o que leva à exibição de não parecer como o esperado.
Dnc253
@ dnc253 Eu concordo e escrevi a resposta. Sabendo o que sei agora, usaria $ timeout (function () {...}); Faz o mesmo que _.defer. Ambos adiam para o próximo ciclo de animação.
gelada
14

A forma mais curta de seguro $applyé:

$timeout(angular.noop)
Bruxo
fonte
11

Você também pode usar evalAsync. Ele será executado algum tempo após a conclusão do resumo!

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

Primeiro de tudo, não conserte desta maneira

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

Não faz sentido, porque $ phase é apenas um sinalizador booleano para o ciclo $ digest, portanto, seu $ apply () às vezes não será executado. E lembre-se de que é uma má prática.

Em vez disso, use $timeout

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

Se você estiver usando sublinhado ou lodash, poderá usar defer ():

_.defer(function(){ 
  $scope.$apply(); 
});
Sagar M
fonte
9

Às vezes, você ainda receberá erros se usar dessa maneira ( https://stackoverflow.com/a/12859093/801426 ).

Tente o seguinte:

if(! $rootScope.$root.$$phase) {
...
bullgare
fonte
5
usando as fases! $ scope. $$ e! $ scope. $ root. $$ phase (não! $ rootScope. $ root. $$ phase) funciona para mim. +1
asprotte
2
$rootScopee anyScope.$rooté o mesmo cara. $rootScope.$rooté redundante.
Floribon
5

tente usar

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

ao invés de

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

$ applyAsync Programe a chamada de $ apply para ocorrer posteriormente. Isso pode ser usado para enfileirar várias expressões que precisam ser avaliadas no mesmo resumo.

NOTA: No $ digest, $ applyAsync () somente liberará se o escopo atual for o $ rootScope. Isso significa que, se você chamar $ digest em um escopo filho, ele não liberará implicitamente a fila $ applyAsync ().

Exemplo:

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

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

Referências:

1. Escopo. $ ApplyAsync () vs. Escopo. $ EvalAsync () no AngularJS 1.3

  1. AngularJs Docs
Eduardo Eljaiek
fonte
4

Aconselho você a usar um evento personalizado em vez de acionar um ciclo de resumo.

Descobri que a transmissão de eventos personalizados e o registro de ouvintes para esses eventos são uma boa solução para desencadear uma ação que você deseja que ocorra, independentemente de você estar ou não em um ciclo de resumo.

Ao criar um evento personalizado, você também está sendo mais eficiente com seu código, porque está ativando apenas ouvintes inscritos no referido evento e NÃO acionando todos os relógios vinculados ao escopo como faria se invocasse o escopo. $ Apply.

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


$scope.$broadcast('customEventName', optionalCustomEventArguments);
nelsonomuto
fonte
3

yearofmoo fez um ótimo trabalho ao criar uma função reutilizável $ safeApply para nós:

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

Uso:

//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);
RNobel
fonte
2

Consegui resolver esse problema ligando em $evalvez de $applyem lugares onde sei que a $digestfunção estará em execução.

De acordo com os documentos , $applybasicamente faz isso:

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

No meu caso, um ng-clickaltera uma variável dentro de um escopo e um $ watch nessa variável altera outras variáveis ​​que precisam ser $applied. Este último passo causa o erro "digerir já em andamento".

Ao substituir $applypor $evaldentro da expressão de observação, as variáveis ​​de escopo são atualizadas conforme o esperado.

Portanto, parece que, se a digestão for executada de qualquer maneira devido a alguma outra alteração no Angular, $evaltudo o que você precisa fazer é ing.

teleclimber
fonte
2

use em $scope.$$phase || $scope.$apply();vez disso

Visakh B Sujathan
fonte
1

Entendendo que os documentos angulares chamam de verificação de $$phaseum antipadrão , tentei entrar $timeoute _.defertrabalhar.

O tempo limite e os métodos adiados criam um flash de {{myVar}}conteúdo não analisado no domínio como um FOUT . Para mim, isso não era aceitável. Deixa-me sem muito a ser dito dogmaticamente que algo é um hack e não tem uma alternativa adequada.

A única coisa que funciona sempre é:

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

Não entendo o perigo desse método, ou por que ele é descrito como um hack pelas pessoas nos comentários e pela equipe angular. O comando parece preciso e fácil de ler:

"Faça o resumo, a menos que já esteja acontecendo"

No CoffeeScript, é ainda mais bonito:

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

Qual é o problema com isso? Existe uma alternativa que não crie um FOUT? $ safeApply parece bom, mas também usa o $$phasemétodo de inspeção.

SimplGy
fonte
1
Eu adoraria ver uma resposta informada a esta pergunta!
Ben Wheeler
É um hack porque significa que você perde o contexto ou não entende o código neste ponto: ou você está dentro do ciclo de resumo angular e não precisa disso, ou está fora de forma assíncrona e então precisa. Se você não pode saber que naquele ponto do código, então você não é responsável para digeri-lo
floribon
1

Este é o meu serviço de 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 este é um exemplo para seu uso:

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']);
};
ranbuch
fonte
1

Eu tenho usado esse método e parece funcionar perfeitamente bem. Isso apenas aguarda o tempo que o ciclo termina e depois dispara apply(). Simplesmente chame a função apply(<your scope>)de qualquer lugar que você quiser.

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);
  }
}
Ashu
fonte
1

Quando desabilitei o depurador, o erro não está mais acontecendo. No meu caso , foi por causa do depurador parar a execução do código.

jmojico
fonte
0

semelhante às respostas acima, mas isso funcionou fielmente para mim ... em um serviço, adicione:

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

Você pode usar

$timeout

para evitar o erro.

 $timeout(function () {
                        var scope = angular.element($("#myController")).scope();
                        scope.myMethod();
                        scope.$scope();
                    },1);
Satish Singh
fonte
E se eu não quiser usar $ Timeout
rahim.nagori
0

O problema está basicamente chegando quando, estamos solicitando que o angular execute o ciclo de resumo, mesmo estando em processo, o que está criando um problema angular para o entendimento. exceção de conseqüência no console.
1. Não faz sentido chamar scope. $ Apply () dentro da função $ timeout porque internamente faz o mesmo.
2. O código acompanha a função JavaScript baunilha porque seu angular nativo não angular é definido, como setTimeout
3. Para fazer isso, você pode usar

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

}); }

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

Aqui está uma boa solução para evitar esse erro e evitar $ apply

você pode combinar isso com debounce (0) se chamar com base em evento externo. Acima está o 'debounce' que estamos usando e um exemplo completo de código

.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 o próprio código para ouvir algum evento e chamar $ digest apenas no $ scope que você precisa

        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();
        });
Sergey Sahakyan
fonte
-3

Encontre isto: https://coderwall.com/p/ngisma, em que Nathan Walker (perto da parte inferior da página) sugere um decorador em $ rootScope para criar func 'safeApply', código:

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;
      }
    ]);
  }
]);
Warren Davis
fonte
-7

Isso resolverá o seu problema:

if(!$scope.$$phase) {
  //TODO
}
eebbesen
fonte
Não faça se (! $ Scope. $$ phase) $ scope. $ Apply (), isso significa que seu $ scope. $ Apply () não é alto o suficiente na pilha de chamadas.
MGot90