Czasami muszę użyć $scope.$apply
kodu w moim kodzie, a czasami wyświetla błąd „podsumowanie już w toku”. Zacząłem więc szukać sposobu obejścia tego problemu i znalazłem to pytanie: AngularJS: Zapobiegaj błędom $ Digest już w toku podczas wywoływania $ scope. $ Apply () . Jednak w komentarzach (i na kątowej wiki) możesz przeczytać:
Nie rób tego, jeśli (! $ Scope. $$ phase) $ scope. $ Apply (), oznacza to, że twój $ scope. $ Apply () nie jest wystarczająco wysoko na stosie wywołań.
Więc teraz mam dwa pytania:
- Dlaczego dokładnie jest to anty-wzór?
- Jak bezpiecznie używać $ scope. $ Apply?
Wydaje się, że innym „rozwiązaniem” błędu „podsumowanie już w toku” jest użycie $ timeout:
$timeout(function() {
//...
});
Czy to jest droga? Czy to jest bezpieczniejsze? Oto więc prawdziwe pytanie: jak mogę całkowicie wyeliminować możliwość wystąpienia błędu „podsumowanie już w toku”?
PS: Używam tylko $ scope. $ Stosuje się w wywołaniach zwrotnych innych niż angularjs, które nie są synchroniczne. (o ile wiem, są to sytuacje, w których musisz użyć $ scope. $ zastosuj, jeśli chcesz, aby zmiany zostały zastosowane)
źródło
scope
od wewnątrz, czy z zewnątrz kątowo. Dlatego zawsze wiesz, czy musisz zadzwonić,scope.$apply
czy nie. A jeśli używasz tego samego kodu zarówno doscope
manipulacji kątowych , jak i nie-kątowych , robisz to źle, zawsze powinien być oddzielony ... więc w zasadzie, jeśli napotkasz przypadek, w którym musisz sprawdzićscope.$$phase
, twój kod nie jest zaprojektowane w prawidłowy sposób i zawsze jest sposób, aby to zrobićdigest already in progress
Odpowiedzi:
Po dłuższych poszukiwaniach udało mi się rozwiązać pytanie, czy zawsze można go bezpiecznie używać
$scope.$apply
. Krótka odpowiedź brzmi: tak.Długa odpowiedź:
Ze względu na to, jak Twoja przeglądarka wykonuje Javascript, nie jest możliwe, że dwa strawienia rozmowy zderzają się przypadkowo .
W związku z tym błąd „podsumowanie już w toku” może wystąpić tylko w jednej sytuacji: gdy $ Apply jest wystawiane w innym $ Apply, np .:
$scope.apply(function() { // some code... $scope.apply(function() { ... }); });
Taka sytuacja nie może wystąpić, jeśli użyjemy $ scope.apply w czystym wywołaniu zwrotnym innym niż angularjs, takim jak na przykład wywołanie zwrotne z
setTimeout
. Więc poniższy kod jest w 100% kuloodporny i nie ma potrzeby wykonywania plikuif (!$scope.$$phase) $scope.$apply()
setTimeout(function () { $scope.$apply(function () { $scope.message = "Timeout called!"; }); }, 2000);
nawet ten jest bezpieczny:
$scope.$apply(function () { setTimeout(function () { $scope.$apply(function () { $scope.message = "Timeout called!"; }); }, 2000); });
Co NIE jest bezpieczne (ponieważ $ timeout - podobnie jak wszyscy pomocnicy angularjs - już
$scope.$apply
cię wzywa ):$timeout(function () { $scope.$apply(function () { $scope.message = "Timeout called!"; }); }, 2000);
To wyjaśnia również, dlaczego użycie
if (!$scope.$$phase) $scope.$apply()
jest anty-wzorcem. Po prostu nie potrzebujesz go, jeśli używasz$scope.$apply
we właściwy sposób: w czystym wywołaniu zwrotnym js, takim jaksetTimeout
na przykład.Przeczytaj http://jimhoskins.com/2012/12/17/angularjs-and-apply.html, aby uzyskać bardziej szczegółowe wyjaśnienie.
źródło
$document.bind('keydown', function(e) { $rootScope.$apply(function() { // a passed through function from the controller gets executed here }); });
naprawdę nie wiem, dlaczego muszę tutaj złożyć wniosek o $, ponieważ używam $ document.bind ..function $DocumentProvider(){ this.$get = ['$window', function(window){ return jqLite(window.document); }]; }
Nie ma tam zastosowania.$timeout
semantycznie oznacza uruchamianie kodu po opóźnieniu. Może to być funkcjonalnie bezpieczne, ale jest to hack. Powinien istnieć bezpieczny sposób korzystania z $ apply, gdy nie możesz wiedzieć, czy$digest
cykl jest w toku lub czy jesteś już w środku$apply
.Obecnie jest to zdecydowanie anty-wzór. Widziałem podsumowanie, nawet jeśli sprawdzisz fazę $$. Po prostu nie powinieneś mieć dostępu do wewnętrznego interfejsu API oznaczonego
$$
prefiksami.Powinieneś użyć
$scope.$evalAsync();
ponieważ jest to preferowana metoda w Angular ^ 1.4 i jest specjalnie udostępniona jako API dla warstwy aplikacji.
źródło
W każdym przypadku, gdy twoje podsumowanie jest w toku i naciskasz na inną usługę, po prostu wyświetla błąd, tj. Przegląd już trwa. więc aby to wyleczyć, masz dwie możliwości. możesz sprawdzić inne trwające podsumowanie, na przykład odpytywanie.
Pierwszy
if ($scope.$root.$$phase != '$apply' && $scope.$root.$$phase != '$digest') { $scope.$apply(); }
jeśli powyższy warunek jest spełniony, możesz zastosować swój $ scope. $ zastosuj w inny sposób nie i
Drugim rozwiązaniem jest użycie $ timeout
$timeout(function() { //... })
nie pozwoli na rozpoczęcie drugiego skrótu, dopóki $ timeout nie zakończy jego wykonania.
źródło
$scope.$apply();
.$timeout
jest kluczem! to działa, a później okazało się, że jest również zalecane.scope.$apply
wyzwala$digest
cykl, który jest podstawą dwukierunkowego wiązania danychZA
$digest
sprawdza rowerowe dla obiektów tj modeli (dokładniej$watch
) dołączonych do$scope
oceny, czy ich wartości uległy zmianie i jeżeli wykryje zmianę wtedy podejmuje niezbędne kroki, aby zaktualizować widok.Teraz, gdy używasz
$scope.$apply
, napotykasz błąd „Już w toku”, więc jest całkiem oczywiste, że podsumowanie $ działa, ale co go spowodowało?ans -> co
$http
połączenie, wszystkie ng-klik, powtórz, pokaż, ukryj itp. uruchamiają$digest
cykl I NAJGORSZA CZĘŚĆ DZIAŁA KAŻDEGO ZAKRESUtzn. powiedz, że twoja strona ma 4 kontrolery lub dyrektywy A, B, C, D
Jeśli masz 4
$scope
właściwości w każdej z nich, na swojej stronie masz łącznie 16 właściwości zakresu $.Jeśli wyzwolisz
$scope.$apply
w kontrolerze D,$digest
cykl sprawdzi wszystkie 16 wartości !!! plus wszystkie właściwości $ rootScope.Odpowiedź -> ale
$scope.$digest
wyzwala$digest
on element podrzędny i ten sam zakres, więc sprawdzi tylko 4 właściwości. Więc jeśli jesteś pewien, że zmiany w D nie wpłyną na A, B, C, użyj$scope.$diges
t nie$scope.$apply
.Zatem zwykłe naciśnięcie klawisza ng-click lub ng-show / hide może wywołać
$digest
cykl w ponad 100+ właściwościach, nawet jeśli użytkownik nie uruchomił żadnego zdarzenia !źródło
Użyj
$timeout
, jest to sposób zalecany.Mój scenariusz jest taki, że muszę zmienić elementy na stronie na podstawie danych, które otrzymałem z WebSocket. A ponieważ jest poza Angularem, bez limitu czasu $ timeout, jedyny model zostanie zmieniony, ale nie widok. Ponieważ Angular nie wie, że część danych została zmieniona.
$timeout
po prostu mówi Angularowi, aby dokonał zmiany w następnej rundzie podsumowania $.Wypróbowałem również następujące i działa. Dla mnie różnica polega na tym, że $ timeout jest wyraźniejszy.
setTimeout(function(){ $scope.$apply(function(){ // changes }); },0)
źródło
$http
.). W przeciwnym razie będziesz musiał powtarzać ten kod w każdym miejscu.$scope.$apply
czy używaszsetTimeout
lub$timeout
Znalazłem bardzo fajne rozwiązanie:
.factory('safeApply', [function($rootScope) { return function($scope, fn) { var phase = $scope.$root.$$phase; if (phase == '$apply' || phase == '$digest') { if (fn) { $scope.$eval(fn); } } else { if (fn) { $scope.$apply(fn); } else { $scope.$apply(); } } } }])
wstrzyknij tam, gdzie potrzebujesz:
.controller('MyCtrl', ['$scope', 'safeApply', function($scope, safeApply) { safeApply($scope); // no function passed in safeApply($scope, function() { // passing a function in }); } ])
źródło