AngularJS: Zapobieganie błędowi $ digest już w toku podczas wywoływania $ scope. $ Apply ()


838

Uważam, że muszę coraz bardziej ręcznie aktualizować moją stronę do zakresu, odkąd zbudowałem aplikację pod kątem.

Jedyny znany mi sposób to dzwonić $apply()z zakresu moich kontrolerów i dyrektyw. Problem polega na tym, że ciągle wyświetla błąd na konsoli, który brzmi:

Błąd: $ digest już trwa

Czy ktoś wie, jak uniknąć tego błędu lub osiągnąć to samo, ale w inny sposób?


34
To naprawdę frustrujące, że musimy używać $ stosować coraz więcej.
OZ_

Otrzymuję również ten błąd, mimo że dzwonię $ zastosuj w oddzwanianiu. Korzystam z biblioteki innej firmy, aby uzyskać dostęp do danych na ich serwerach, więc nie mogę skorzystać z $ http, ani też nie chcę, ponieważ musiałbym przepisać ich bibliotekę, aby użyć $ http.
Trevor

45
użycie$timeout()
Onur Yıldırım

6
użyj $ timeout (fn) + 1, może to rozwiązać problem! Zakres $. $$ Faza nie jest najlepszym rozwiązaniem.
Huei Tan

1
Zawiń tylko zakres kodu / wywołania. $ Stosuje się w ramach limitu czasu (nie $ limitu czasu) Funkcje AJAX (nie $ http) i zdarzeń (nie ng-*). Upewnij się, że jeśli wywołujesz go z funkcji (która jest wywoływana przez limit czasu / ajax / events), to nie jest ona również początkowo uruchamiana przy ładowaniu.
Patrick

Odpowiedzi:


660

Nie używaj tego wzorca - spowoduje to więcej błędów niż rozwiązuje. Nawet jeśli myślisz, że coś naprawiło, tak się nie stało.

Możesz sprawdzić, czy a $digestjest już w toku, sprawdzając $scope.$$phase.

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

$scope.$$phasepowróci "$digest"lub "$apply"jeśli a $digestlub $applyjest w toku. Wierzę, że różnica między tymi stanami polega na tym $digest, że będą przetwarzać zegarki obecnego zakresu i jego dzieci i $applybędą przetwarzać obserwatorów wszystkich zakresów.

Do punktu @ dnc253, jeśli często dzwonisz $digestlub $applyczęsto dzwonisz , być może robisz to źle. Generalnie uważam, że muszę podsumować, kiedy muszę zaktualizować stan zakresu w wyniku uruchomienia zdarzenia DOM poza zasięgiem Angulara. Na przykład, gdy modalny pasek ładowania Twittera zostaje ukryty. Czasami zdarzenie DOM jest uruchamiane, gdy $digesttrwa, a czasem nie. Dlatego używam tego czeku.

Chciałbym poznać lepszy sposób, jeśli ktoś go zna.


Z komentarzy: autor: @anddoutoi

angular.js Anti Patterns

  1. Nie rób if (!$scope.$$phase) $scope.$apply(), oznacza to, że Twój $scope.$apply()stos nie jest wystarczająco wysoki.

230
Wydaje mi się, że $ digest / $ Apply powinno to robić domyślnie
Roy Truelove

21
Zauważ, że w niektórych przypadkach muszę sprawdzić, ale aktualny zakres ORAZ zakres główny. Dostaję wartość fazy $$ w katalogu głównym, ale nie w moim zakresie. Sądzę, że ma to coś wspólnego z izolowanym zakresem dyrektywy, ale ...
Roy Truelove

106
„Przestań robić if (!$scope.$$phase) $scope.$apply()”, github.com/angular/angular.js/wiki/Anti-Patterns
anddoutoi

34
@anddoutoi: Zgoda; Twój link wyjaśnia, że ​​to nie jest rozwiązanie; nie jestem jednak pewien, co należy rozumieć przez „nie jesteś wystarczająco wysoki na stosie wywołań”. Wiesz co to oznacza?
Trevor

13
@ threed: zobacz odpowiedź aaronfrost. Prawidłowym sposobem jest użycie opcji odroczenia, aby uruchomić podsumowanie w następnym cyklu. W przeciwnym razie zdarzenie zostanie utracone i w ogóle nie zostanie zaktualizowany zakres.
Marek

663

Z ostatniej dyskusji z Angularami na ten właśnie temat: Ze względów bezpieczeństwa na przyszłość nie powinieneś używać$$phase

Po naciśnięciu „właściwego” sposobu, odpowiedź jest obecnie

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

Ostatnio natknąłem się na to, pisząc usługi kątowe do zawijania interfejsów API Facebooka, Google'a i Twittera, które w różnym stopniu mają oddzwanianie.

Oto przykład z usługi. (Ze względu na zwięzłość, pozostała część usługi - która konfigurowała zmienne, wprowadziła limit czasu itp. - została pominięta).

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

Zauważ, że argument opóźnienia dla limitu czasu $ jest opcjonalny i domyślnie wynosi 0, jeśli nie jest ustawiony ( limit czasu wywołuje $ browser.defer, który domyślnie wynosi 0, jeśli opóźnienie nie jest ustawione )

Trochę nieintuicyjne, ale to odpowiedź od facetów piszących Angular, więc jest dla mnie wystarczająco dobra!


5
Wpadłem na to wiele razy w moich dyrektywach. Pisałem jeden dla redaktora i okazało się, że działa idealnie. Byłem na spotkaniu z Bradem Greenem i powiedział, że Angular 2.0 będzie ogromny bez cyklu trawienia, wykorzystując natywną zdolność JS do obserwacji i używając wielopełniacza w przeglądarkach, które tego nie mają. W tym momencie nie będziemy musieli już tego robić. :)
Michael J. Calkins,

Wczoraj widziałem problem polegający na tym, że wywołanie selectize.refreshItems () wewnątrz $ timeout spowodowało przerażający błąd rekursywnego podsumowania. Jakieś pomysły, jak to może być?
iwein

3
Jeśli używasz $timeoutzamiast natywnego setTimeout, dlaczego nie używasz $windowzamiast natywnego window?
LeeGee

2
@LeeGee: W $timeouttym przypadku warto użyć tego, aby $timeoutzapewnić poprawną aktualizację zakresu kątowego. Jeśli $ digest nie jest w toku, spowoduje uruchomienie nowego $ digest.
awe

2
@webicy To nie jest rzecz. Po uruchomieniu treści funkcji przekazanej do limitu czasu $ obietnica jest już rozwiązana! Nie ma absolutnie żadnego powodu cancel. Z dokumentów : „W wyniku tego obietnica zostanie rozwiązana z odrzuceniem”. Nie możesz rozwiązać rozwiązanej obietnicy. Anulowanie nie spowoduje żadnych błędów, ale nie przyniesie też nic pozytywnego.
daemonexmachina

324

Cykl skrótu jest połączeniem synchronicznym. Nie da kontroli nad pętlą zdarzeń przeglądarki, dopóki nie zostanie wykonana. Jest na to kilka sposobów. Najłatwiejszym sposobem na poradzenie sobie z tym jest skorzystanie z wbudowanego limitu czasu $, a drugim sposobem jest użycie podkreślenia lub lodash (i powinieneś być), zadzwoń:

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

lub jeśli masz lodash:

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

Wypróbowaliśmy kilka obejść i nienawidziliśmy wprowadzania $ rootScope do wszystkich naszych kontrolerów, dyrektyw, a nawet niektórych fabryk. Tak więc limit czasu $ i _.defer były naszymi ulubionymi do tej pory. Te metody z powodzeniem każą kątowi czekać na następną pętlę animacji, co zagwarantuje, że aktualny zakres.


2
Czy jest to porównywalne z użyciem $ timeout (...)? Użyłem $ timeout w kilku przypadkach, aby przejść do następnego cyklu zdarzeń i wydaje się, że działa dobrze - ktoś wie, czy istnieje powód, aby nie używać $ timeout?
Trevor,

9
To naprawdę powinno być używane tylko wtedy, gdy już używasz underscore.js. To rozwiązanie nie jest warte importowania całej biblioteki podkreślenia tylko po to, aby użyć jej deferfunkcji. Zdecydowanie wolę to $timeoutrozwiązanie, ponieważ każdy ma już dostęp $timeoutpoprzez angular, bez żadnych zależności od innych bibliotek.
tenisista

10
To prawda ... ale jeśli nie używasz podkreślenia lub kasku ... musisz ponownie ocenić to, co robisz. Te dwie biblioteki zmieniły wygląd kodu.
mroźny

2
Mamy zależność od Restangular (zamierzamy wkrótce wyeliminować Restangular na korzyść ng-route). Myślę, że to dobra odpowiedź, ale nie jest dobrze zakładać, że ludzie chcą używać podkreślenia / lodash. Wszystkie te biblioteki są w porządku ... jeśli wykorzystasz je wystarczająco ... obecnie używam metod ES5, które usuwają 98% powodów, dla których użyłem podkreślenia.
BradGreens

2
Masz rację @SgtPooki. Zmodyfikowałem odpowiedź, aby uwzględnić opcję użycia limitu czasu $. Limit czasu $ i _.defer będą czekać do następnej pętli animacji, co zapewni zakończenie bieżącego zakresu. Dziękuję za to, że jestem uczciwy i zmusza mnie do zaktualizowania odpowiedzi tutaj.
mroźny

267

Wiele odpowiedzi tutaj zawiera dobre porady, ale może również prowadzić do zamieszania. Samo użycie nie$timeout jest najlepszym ani właściwym rozwiązaniem. Pamiętaj również, aby przeczytać tę informację, jeśli niepokoi Cię wydajność lub skalowalność.

Rzeczy, które powinieneś wiedzieć

  • $$phase jest prywatny dla ram i istnieją ku temu dobre powody.

  • $timeout(callback)poczeka, aż zakończy się bieżący cykl podsumowania (jeśli istnieje), a następnie wykona wywołanie zwrotne, a następnie uruchom na końcu pełny $apply.

  • $timeout(callback, delay, false)zrobi to samo (z opcjonalnym opóźnieniem przed wykonaniem wywołania zwrotnego), ale nie uruchomi $apply(trzeciego argumentu), który zapisuje wydajność, jeśli nie zmodyfikujesz modelu Angular ($ scope).

  • $scope.$apply(callback)wywołuje, między innymi $rootScope.$digest, co oznacza, że ​​ponownie przekopiuje zakres główny aplikacji i wszystkich jej potomków, nawet jeśli jesteś w zakresie izolowanym.

  • $scope.$digest()po prostu zsynchronizuje swój model z widokiem, ale nie przetrawi zakresu nadrzędnego, co może zaoszczędzić wiele wydajności podczas pracy nad izolowaną częścią twojego HTML z izolowanym zakresem (głównie z dyrektywy). $ digest nie odbiera oddzwonienia: wykonujesz kod, a następnie skrót.

  • $scope.$evalAsync(callback)został wprowadzony z angularjs 1.2 i prawdopodobnie rozwiąże większość twoich problemów. Więcej informacji na ten temat można znaleźć w ostatnim akapicie.

  • jeśli dostaniesz $digest already in progress error, oznacza to, że twoja architektura jest zła: albo nie musisz przekierowywać swojego zakresu, albo nie powinieneś być za to odpowiedzialny (patrz poniżej).

Jak zbudować swój kod

Gdy pojawia się ten błąd, próbujesz przetrawić swój zakres, gdy jest już w toku: ponieważ nie znasz stanu swojego zakresu w tym momencie, nie jesteś odpowiedzialny za jego trawienie.

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

A jeśli wiesz, co robisz i pracujesz nad izolowaną małą dyrektywą, będąc częścią dużej aplikacji Angular, możesz zamiast $ zastosować zamiast skrótu zastosować, aby zapisać wyniki.

Aktualizacja od Angularjs 1.2

Nowa, skuteczna metoda została dodana do jakiejkolwiek $ zakresie: $evalAsync. Zasadniczo wykona on wywołanie zwrotne w ramach bieżącego cyklu podsumowania, jeśli taki wystąpi, w przeciwnym razie nowy cykl podsumowania rozpocznie wykonywanie wywołania zwrotnego.

To nadal nie jest tak dobre, jak $scope.$digestjeśli naprawdę wiesz, że musisz zsynchronizować tylko izolowaną część kodu HTML (ponieważ nowa $applyzostanie uruchomiona, jeśli żadna nie jest w toku), ale jest to najlepsze rozwiązanie, gdy wykonujesz funkcję którego nie możesz wiedzieć, czy będzie on wykonywany synchronicznie, czy nie , na przykład po pobraniu zasobu potencjalnie buforowanego: czasami będzie to wymagać asynchronicznego połączenia z serwerem, w przeciwnym razie zasób zostanie lokalnie pobrany synchronicznie.

W tych przypadkach i we wszystkich innych, w których miałeś !$scope.$$phase, koniecznie użyj$scope.$evalAsync( callback )


4
$timeoutjest krytykowany przelotnie. Czy możesz podać więcej powodów do uniknięcia $timeout?
mlhDev

88

Przydatna metoda małego pomocnika, aby utrzymać ten proces na sucho

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

6
Twoja aplikacja SafeApply pomogła mi zrozumieć, co się dzieje o wiele bardziej niż cokolwiek innego. Dzięki za opublikowanie tego.
Jason More

4
Chciałem zrobić to samo, ale czy to nie oznacza, że ​​istnieje szansa, że ​​zmiany wprowadzone w fn () nie zostaną zauważone przez $ digest? Czy nie lepiej byłoby opóźnić funkcję, przyjmując zakres. $$ phase === '$ digest'?
Spencer Alger,

Zgadzam się, czasami $ Apply () jest używane do uruchomienia skrótu, po prostu wywołuje fn sam ... czy to nie spowoduje problemu?
CMCDragonkai 17.09.13

1
Wydaje mi się, że scope.$apply(fn);powinno być, scope.$apply(fn());ponieważ fn () wykona funkcję, a nie fn.
Pomóżcie

1
@ZenOut Wywołanie $ Apply obsługuje wiele różnych argumentów, w tym funkcje. Jeśli przekazana funkcja, ocenia funkcję.
boxmein,

33

Miałem ten sam problem ze skryptami stron trzecich, takimi jak na przykład CodeMirror i Krpano, a nawet użycie wymienionych tutaj metod safeApply nie rozwiązało błędu.

Ale to, co rozwiązało, to skorzystanie z usługi $ timeout (nie zapomnij najpierw wstrzyknąć).

Zatem coś takiego:

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

a jeśli w swoim kodzie używasz

to

być może dlatego, że znajduje się w kontrolerze dyrektywy fabrycznej lub po prostu potrzebuje jakiegoś wiązania, to zrobiłbyś coś takiego:

.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

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

Problem pojawia się, gdy masz wywołanie, $applyktóre czasami jest uruchamiane asynchronicznie poza kodem Angular (kiedy należy zastosować $ Apply), a czasem synchronicznie wewnątrz kodu Angular (co powoduje $digest already in progressbłąd).

Może się tak zdarzyć na przykład, gdy masz bibliotekę, która asynchronicznie pobiera elementy z serwera i buforuje je. Przy pierwszym żądaniu element zostanie pobrany asynchronicznie, aby nie blokować wykonywania kodu. Jednak po raz drugi element znajduje się już w pamięci podręcznej, dzięki czemu można go odzyskać synchronicznie.

Aby zapobiec temu błędowi, należy zapewnić, aby wywoływany kod $applybył uruchamiany asynchronicznie. Można to zrobić, uruchamiając kod w wywołaniu $timeoutz opóźnieniem ustawionym na 0(co jest ustawieniem domyślnym). Jednak wywołanie kodu w środku $timeouteliminuje konieczność dzwonienia $apply, ponieważ limit czasu $ sam w sobie uruchomi inny $digestcykl, który z kolei dokona wszystkich niezbędnych aktualizacji itp.

Rozwiązanie

Krótko mówiąc, zamiast tego:

... your controller code...

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

... more of your controller code...

Zrób to:

... your controller code...

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

... more of your controller code...

Zadzwoń tylko $applywtedy, gdy wiesz, że kod jest uruchomiony, zawsze będzie uruchamiany poza kodem Angular (np. Twoje wezwanie do $ Apply nastąpi w ramach wywołania zwrotnego, które jest wywoływane przez kod poza twoim kodem Angular).

O ile ktoś nie zda sobie sprawy z jakiejś znaczącej wady korzystania $timeoutz niego $apply, nie rozumiem, dlaczego nie zawsze możesz użyć $timeout(z zerowym opóźnieniem) zamiast $apply, ponieważ spowoduje to w przybliżeniu to samo.


Dzięki, to zadziałało w mojej sprawie, w której nie dzwonię do $applysiebie, ale wciąż pojawia się błąd.
ariscris

5
Główną różnicą jest to, że $applyjest synchroniczny (jego wywołanie zwrotne jest wykonywane, a następnie stosuje się kod następujący po $), a $timeoutnie jest: wykonywany jest bieżący kod po przekroczeniu limitu czasu, a następnie nowy stos zaczyna się od wywołania zwrotnego, tak jakbyś go używał setTimeout. Może to prowadzić do usterki graficznej, jeśli aktualizujesz dwa razy ten sam model: $timeoutzaczeka na odświeżenie widoku przed ponowną aktualizacją.
floribon

Rzeczywiście dzięki, trzask. Miałem metodę wywoływaną w wyniku aktywności $ watch i próbowałem zaktualizować interfejs użytkownika, zanim mój filtr zewnętrzny skończył się uruchamiać. Umieszczenie tego w funkcji $ timeout działało dla mnie.
djmarquette

28

Gdy pojawi się ten błąd, oznacza to po prostu, że jest już w trakcie aktualizacji widoku. Naprawdę nie powinieneś dzwonić $apply()w swoim kontrolerze. Jeśli widok nie jest aktualizowany zgodnie z oczekiwaniami, a po wywołaniu pojawia się ten błąd $apply(), najprawdopodobniej oznacza to, że nie aktualizujesz poprawnie modelu. Jeśli opublikujesz kilka szczegółów, możemy dowiedzieć się, jaki jest podstawowy problem.


heh, spędziłem cały dzień, aby dowiedzieć się, że AngularJS po prostu nie może oglądać wiązań „magicznie” i czasami powinienem naciskać na niego za pomocą $ apply ().
OZ_

co na wszelkie sposoby you're not updating the the model correctly? $scope.err_message = 'err message';nieprawidłowa aktualizacja?
OZ_

2
Musisz tylko zadzwonić, $apply()gdy zaktualizujesz model „poza” kątem (np. Z wtyczki jQuery). Łatwo wpaść w pułapkę widoku, który nie wygląda dobrze, więc rzucasz $apply()wszędzie wiązką s, co kończy się błędem widocznym w PO. Kiedy mówię, you're not updating the the model correctlymam na myśli całą logikę biznesową niepoprawnie wypełniającą wszystko, co może być w zakresie, co prowadzi do tego, że widok nie wygląda zgodnie z oczekiwaniami.
dnc253

@ dnc253 Zgadzam się i napisałem odpowiedź. Wiedząc, co teraz wiem, użyłbym $ timeout (function () {...}); Robi to samo, co robi _.defer. Obaj przechodzą do następnej pętli animacji.
mroźny


11

Możesz także użyć evalAsync. Będzie działał jakiś czas po zakończeniu podsumowania!

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

10

Po pierwsze, nie naprawiaj tego w ten sposób

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

Nie ma to sensu, ponieważ $ faza jest po prostu flagą logiczną dla cyklu $ digest, więc twoja $ apply () czasami nie działa. I pamiętaj, że to zła praktyka.

Zamiast tego użyj $timeout

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

Jeśli używasz podkreślenia lub lodash, możesz użyć defer ():

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

9

Czasami nadal będziesz otrzymywać błędy, jeśli skorzystasz z tej metody ( https://stackoverflow.com/a/12859093/801426 ).

Spróbuj tego:

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

5
zarówno faza $ $. $$ faza jak i! $ zakres. $ root. $$ faza (nie! $ rootScope. $ root. $$ faza) działa dla mnie. +1
asprotte

2
$rootScopei anyScope.$rootsą tym samym facetem. $rootScope.$rootjest zbędny.
floribon


5

spróbuj użyć

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

zamiast

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

$ ApplyAsync Zaplanuj wywołanie $ Apply, aby nastąpiło później. Można tego użyć do umieszczenia w kolejce wielu wyrażeń, które muszą zostać ocenione w tym samym skrócie.

UWAGA: W ramach $ digest $ ApplyAsync () będzie opróżniać tylko jeśli bieżącym zakresem jest $ rootScope. Oznacza to, że jeśli wywołasz $ digest w zakresie potomnym, nie będzie on domyślnie opróżniał kolejki $ applyAsync ().

Przykład:

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

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

Bibliografia:

1. Zakres. $ ApplyAsync () vs. Zakres. $ EvalAsync () w AngularJS 1.3

  1. AngularJs Docs

4

Radziłbym użyć niestandardowego zdarzenia zamiast wywoływać cykl podsumowania.

Przekonałem się, że nadawanie niestandardowych zdarzeń i rejestrowanie słuchaczy dla tych zdarzeń jest dobrym rozwiązaniem do uruchomienia akcji, którą chcesz wykonać, niezależnie od tego, czy jesteś w cyklu podsumowania.

Tworząc niestandardowe zdarzenie, zwiększasz również efektywność kodu, ponieważ wywołujesz tylko detektory zasubskrybowane do tego zdarzenia i NIE wywołujesz wszystkich zegarków powiązanych z zakresem, tak jak gdybyś wywołał zakres.

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


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

3

yearofmoo wykonał świetną robotę, tworząc dla nas funkcję $ SafeApply:

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

Stosowanie :

//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

Byłem w stanie rozwiązać ten problem, dzwoniąc $evalzamiast $applyw miejscach, w których wiem, że $digestfunkcja będzie działać.

Zgodnie z docs , $applyw zasadzie robi to:

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

W moim przypadku ng-clickzmiana zmiennej w zakresie, a $ watch na tej zmiennej zmienia inne zmienne, które muszą być $applied. Ten ostatni krok powoduje błąd „podsumowanie już w toku”.

Zastępując $applyz $evalwewnątrz wyrażenia obserwować zmienne zakres aktualizowane zgodnie z oczekiwaniami.

Dlatego wydaje się, że jeśli skrót i tak będzie działał z powodu jakiejś innej zmiany w Angular, $evalwszystko, co musisz zrobić.



1

Rozumiejąc, że kątowa dokumenty nazywamy sprawdzając $$phasesię anty-wzorzec , starałem się dostać $timeouti_.defer do pracy.

Metody limitu czasu i odroczone tworzą błysk nieprzetworzonej {{myVar}}zawartości w domenie jak FOUT . Dla mnie było to nie do przyjęcia. Nie pozostawia mi wiele do powiedzenia dogmatycznie, że coś jest włamaniem i nie ma odpowiedniej alternatywy.

Jedyne, co działa za każdym razem, to:

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

Nie rozumiem niebezpieczeństwa tej metody ani dlaczego jest ona opisywana jako hack przez ludzi w komentarzach i zgranym zespole. Polecenie wydaje się precyzyjne i łatwe do odczytania:

„Przeprowadź podsumowanie, chyba że się już dzieje”

W CoffeeScript jest jeszcze ładniejszy:

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

W czym problem? Czy istnieje alternatywa, która nie stworzy FOUT? $ safeApply wygląda dobrze, ale używa również $$phasemetody inspekcji.


1
Chciałbym zobaczyć świadomą odpowiedź na to pytanie!
Ben Wheeler,

Jest to hack, ponieważ oznacza to, że brakuje Ci kontekstu lub nie rozumiesz kodu w tym momencie: albo jesteś w kanale cyklicznego podsumowania i nie potrzebujesz tego, albo jesteś asynchronicznie poza tym, a następnie potrzebujesz go. Jeśli nie możesz tego wiedzieć w tym punkcie kodu, nie jesteś odpowiedzialny za jego strawienie
floribon

1

To jest moja usługa 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);
        }
    };
});

i to jest przykład jego użycia:

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

Korzystam z tej metody i wydaje się, że działa ona doskonale. To tylko czeka na zakończenie cyklu, a następnie uruchamia się apply(). Wystarczy wywołać funkcję apply(<your scope>)z dowolnego miejsca.

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

Kiedy wyłączyłem debugger, błąd już się nie pojawia. W moim przypadku było to spowodowane tym, że debugger zatrzymał wykonywanie kodu.


0

podobne do powyższych odpowiedzi, ale działało to dla mnie wiernie ... w serwisie dodaj:

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

0

Możesz użyć

$timeout

aby zapobiec błędowi.

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

Co jeśli nie chcę używać $ timeout
rahim.nagori

0

Problem pojawia się w zasadzie, gdy zwracamy się do Angulara z prośbą o uruchomienie cyklu podsumowania, nawet jeśli jest on w trakcie tworzenia, co stanowi problem dla Angulara do zrozumienia. wyjątek konsekwencji w konsoli.
1. Wywołanie zakresu nie ma sensu. $ Apply () wewnątrz funkcji $ timeout, ponieważ wewnętrznie robi to samo.
2. Kod współpracuje z waniliową funkcją JavaScript, ponieważ jego natywny, nie kątowy zdefiniowano, tj. SetTimeout
3. W tym celu można skorzystać z

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

}); }


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

Oto dobre rozwiązanie, aby uniknąć tego błędu i uniknąć zastosowania $

możesz połączyć to z debounce (0), jeśli dzwonisz na podstawie zdarzenia zewnętrznego. Powyżej używamy „debounce” i pełnego przykładu kodu

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

a sam kod nasłuchuje jakiegoś zdarzenia i wywołuje $ digest tylko w zakresie $ $, którego potrzebujesz

        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

Znalazłem to: https://coderwall.com/p/ngisma, gdzie Nathan Walker (u dołu strony) sugeruje dekoratorowi w $ rootScope, aby stworzył func „safeApply”, kod:

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

To rozwiąże twój problem:

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

Nie rób, jeśli (! $ Scope. $$ faza) $ scope. $ Apply (), oznacza to, że twój $ scope. $ Apply () nie jest wystarczająco wysoki na stosie wywołań.
MGot90
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.