Às vezes, preciso usar $scope.$apply
no meu código e às vezes gera um erro de "resumo já em andamento". Então, comecei a encontrar uma maneira de contornar isso e encontrei esta pergunta: AngularJS: Impedir o erro $ digest já em andamento ao chamar $ scope. $ Apply () . No entanto, nos comentários (e no wiki do angular) você pode ler:
Não faça if (! $ Scope. $$ phase) $ scope. $ Apply (), isso significa que $ scope. $ Apply () não é alto o suficiente na pilha de chamadas.
Então agora eu tenho duas perguntas:
- Por que exatamente isso é um antipadrão?
- Como posso usar $ scope. $ Apply com segurança?
Outra "solução" para evitar o erro "resumo já em andamento" parece estar usando $ timeout:
$timeout(function() {
//...
});
Aquele é o caminho para ir? É mais seguro? Portanto, aqui está a verdadeira questão: Como posso eliminar totalmente a possibilidade de um erro de "resumo já em andamento"?
PS: Estou usando apenas $ scope. $ Apply em callbacks não angularjs que não são síncronos. (pelo que eu sei, essas são situações em que você deve usar $ scope. $ apply se quiser que suas alterações sejam aplicadas)
fonte
scope
de dentro do angular ou de fora do angular. Então de acordo com isso você sempre sabe se precisa ligarscope.$apply
ou não. E se você estiver usando o mesmo código parascope
manipulação angular / não angular , você está fazendo errado, ele deve estar sempre separado ... então, basicamente, se você se deparar com um caso em que precisa verificarscope.$$phase
, seu código não é projetado de forma correta, e sempre há uma maneira de fazê-lo 'da maneira certa'digest already in progress
erroRespostas:
Depois de mais algumas pesquisas, consegui resolver a questão de saber se é sempre seguro usar
$scope.$apply
. A resposta curta é sim.Resposta longa:
Devido à forma como seu navegador executa Javascript, não é possível que duas chamadas de resumo entrem em conflito por acaso .
Portanto, o erro "resumo já em andamento" só pode ocorrer em uma situação: Quando um $ apply é emitido dentro de outro $ apply, por exemplo:
Esta situação não pode ocorrer se usarmos $ scope.apply em um callback não angularjs puro, como por exemplo o callback de
setTimeout
. Portanto, o código a seguir é 100% à prova de balas e não há necessidade de fazer umif (!$scope.$$phase) $scope.$apply()
mesmo este é seguro:
O que NÃO é seguro (porque $ timeout - como todos os ajudantes angularjs - já chama
$scope.$apply
por você):Isso também explica por que o uso de
if (!$scope.$$phase) $scope.$apply()
é um antipadrão. Você simplesmente não precisa disso se usar$scope.$apply
da maneira correta: Em um callback puro js comosetTimeout
por exemplo.Leia http://jimhoskins.com/2012/12/17/angularjs-and-apply.html para obter uma explicação mais detalhada.
fonte
$document.bind('keydown', function(e) { $rootScope.$apply(function() { // a passed through function from the controller gets executed here }); });
Eu realmente não sei por que tenho que fazer $ aplicar aqui, porque estou usando $ document.bind ..function $DocumentProvider(){ this.$get = ['$window', function(window){ return jqLite(window.document); }]; }
Não há nenhum aplicativo ali.$timeout
semanticamente significa executar o código após um atraso. Pode ser uma coisa funcionalmente segura de se fazer, mas é um hack. Deve haver uma maneira segura de usar $ apply quando você não consegue saber se um$digest
ciclo está em andamento ou se já está dentro de um$apply
.É definitivamente um antipadrão agora. Eu vi um resumo explodir mesmo se você verificar a fase $$. Você simplesmente não deve acessar a API interna indicada por
$$
prefixos.Você deveria usar
como este é o método preferido em Angular ^ 1.4 e é especificamente exposto como uma API para a camada de aplicativo.
fonte
Em qualquer caso, quando seu resumo está em andamento e você envia outro serviço para fazer o resumo, isso simplesmente dá um erro, ou seja, um resumo já está em andamento. então, para curar isso, você tem duas opções. você pode verificar se há outro resumo em andamento, como uma votação.
Primeiro
se a condição acima for verdadeira, você pode aplicar seu $ escopo. $ aplicar de outra forma não e
a segunda solução é usar $ timeout
ele não permitirá que o outro resumo comece até $ timeout completar sua execução.
fonte
$scope.$apply();
.$timeout
é a chave! funciona e depois descobri que também é recomendado.scope.$apply
desencadeia um$digest
ciclo que é fundamental para a vinculação de dados bidirecionalUm
$digest
ciclo verifica se há objetos, ou seja, modelos (para ser mais preciso$watch
) anexados para$scope
avaliar se seus valores mudaram e se ele detecta uma mudança, ele executa as etapas necessárias para atualizar a visualização.Agora, quando você usa,
$scope.$apply
encontra um erro "Já em andamento", então é bastante óbvio que um $ digest está sendo executado, mas o que o acionou?ans -> todas as
$http
chamadas, todos os ng-click, repetir, mostrar, ocultar etc acionam um$digest
ciclo E A PIOR PARTE FUNCIONA EM CADA $ ESCOPO.ou seja, digamos que sua página tenha 4 controladores ou diretivas A, B, C, D
Se você tiver 4
$scope
propriedades em cada um deles, terá um total de 16 $ propriedades de escopo em sua página.Se você disparar
$scope.$apply
no controlador D, um$digest
ciclo verificará todos os 16 valores !!! mais todas as propriedades $ rootScope.Resposta -> mas
$scope.$digest
aciona um$digest
filho e o mesmo escopo, portanto, verificará apenas 4 propriedades. Portanto, se você tiver certeza de que as mudanças em D não afetarão A, B, C, use$scope.$diges
t não$scope.$apply
.Portanto, um mero ng-click ou ng-show / hide pode disparar um
$digest
ciclo em mais de 100 propriedades, mesmo quando o usuário não disparou nenhum evento !fonte
Use
$timeout
, é a forma recomendada.Meu cenário é que preciso alterar itens na página com base nos dados que recebi de um WebSocket. E como está fora do Angular, sem o $ timeout, o único modelo será alterado, mas não a visualização. Porque o Angular não sabe que esse dado foi alterado.
$timeout
basicamente diz ao Angular para fazer a alteração na próxima rodada de $ digest.Tentei o seguinte também e funcionou. A diferença para mim é que $ timeout é mais claro.
fonte
$http
). Caso contrário, você terá que repetir este código em todo o lugar.$scope.$apply
se você estiver usandosetTimeout
ou$timeout
Achei uma solução muito legal:
injete onde você precisa:
fonte