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?
angularjs
angularjs-scope
angularjs-digest
Lâmpada1
fonte
fonte
$timeout()
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.Respostas:
Você pode verificar se um
$digest
já está em andamento, marcando$scope.$$phase
.$scope.$$phase
retornará"$digest"
ou"$apply"
se um$digest
ou$apply
estiver em andamento. Acredito que a diferença entre esses estados é que$digest
processará os relógios do escopo atual e seus filhos e$apply
os observadores de todos os escopos.Ao ponto de @ dnc253, se você estiver ligando
$digest
ou com$apply
frequê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$digest
está 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
fonte
if (!$scope.$$phase) $scope.$apply()
", github.com/angular/angular.js/wiki/Anti-PatternsDe 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
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.)
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!
fonte
$timeout
vez de nativosetTimeout
, por que não usa em$window
vez do nativowindow
?$timeout
neste caso é$timeout
garantir que o escopo angular seja atualizado corretamente. Se um $ digest não estiver em andamento, ele fará com que um novo $ digest seja executado.cancel
isso. 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.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:
ou se você tiver lodash:
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.
fonte
underscore.js
. Não vale a pena importar esta solução toda a biblioteca de sublinhados apenas para usar suadefer
função. Eu prefiro a$timeout
solução porque todo mundo já tem acesso$timeout
através do angular, sem nenhuma dependência de outras bibliotecas.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.$digest
que 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.
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.$digest
se você realmente sabe que precisa sincronizar apenas uma parte isolada do seu HTML (já que um novo$apply
será 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 )
fonte
$timeout
é criticado de passagem. Você pode dar mais motivos para evitar$timeout
?Método auxiliar pequeno e útil para manter este processo SECO:
fonte
scope.$apply(fn);
deveria serscope.$apply(fn());
porque fn () executará a função e não fn. Por favor me ajude a onde estou errado #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:
e se dentro do seu código você estiver usando
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:
fonte
Vejo http://docs.angularjs.org/error/$rootScope:inprog
O problema surge quando você
$apply
recebe 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
$apply
seja executado de forma assíncrona. Isso pode ser feito executando o código dentro de uma chamada$timeout
com o atraso definido como0
(que é o padrão). No entanto, chamar seu código para dentro$timeout
remove a necessidade de chamar$apply
, porque $ timeout acionará outro$digest
ciclo por si só, que, por sua vez, fará toda a atualização necessária etc.Solução
Em resumo, em vez de fazer isso:
faça isso:
Somente ligue
$apply
quando 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
$timeout
excessivo$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.fonte
$apply
mas ainda estou recebendo o erro.$apply
é síncrona (seu retorno de chamada é executado e o código a seguir $ apply) enquanto$timeout
nã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 usandosetTimeout
. Isso pode levar a falhas gráficas se você estiver atualizando duas vezes o mesmo modelo:$timeout
aguardará a atualização da visualização antes de atualizá-la novamente.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.fonte
you're not updating the the model correctly
?$scope.err_message = 'err message';
atualização não está correta?$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 digoyou'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.A forma mais curta de seguro
$apply
é:fonte
Você também pode usar evalAsync. Ele será executado algum tempo após a conclusão do resumo!
fonte
Primeiro de tudo, não conserte desta maneira
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
Se você estiver usando sublinhado ou lodash, poderá usar defer ():
fonte
Às vezes, você ainda receberá erros se usar dessa maneira ( https://stackoverflow.com/a/12859093/801426 ).
Tente o seguinte:
fonte
$rootScope
eanyScope.$root
é o mesmo cara.$rootScope.$root
é redundante.Você deve usar $ evalAsync ou $ timeout de acordo com o contexto.
Este é um link com uma boa explicação:
fonte
tente usar
ao invés de
$ 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:
Referências:
1. Escopo. $ ApplyAsync () vs. Escopo. $ EvalAsync () no AngularJS 1.3
fonte
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.
fonte
yearofmoo fez um ótimo trabalho ao criar uma função reutilizável $ safeApply para nós:
Uso:
fonte
Consegui resolver esse problema ligando em
$eval
vez de$apply
em lugares onde sei que a$digest
função estará em execução.De acordo com os documentos ,
$apply
basicamente faz isso:No meu caso, um
ng-click
altera 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
$apply
por$eval
dentro 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,
$eval
tudo o que você precisa fazer é ing.fonte
use em
$scope.$$phase || $scope.$apply();
vez dissofonte
Entendendo que os documentos angulares chamam de verificação de
$$phase
um antipadrão , tentei entrar$timeout
e_.defer
trabalhar.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:
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
$$phase
método de inspeção.fonte
Este é o meu serviço de utils:
e este é um exemplo para seu uso:
fonte
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çãoapply(<your scope>)
de qualquer lugar que você quiser.fonte
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.
fonte
semelhante às respostas acima, mas isso funcionou fielmente para mim ... em um serviço, adicione:
fonte
Você pode usar
para evitar o erro.
fonte
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 () {
}); }
fonte
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
e o próprio código para ouvir algum evento e chamar $ digest apenas no $ scope que você precisa
fonte
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:
fonte
Isso resolverá o seu problema:
fonte