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?
angularjs
angularjs-scope
angularjs-digest
Żarówka 1
źródło
źródło
$timeout()
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.Odpowiedzi:
Możesz sprawdzić, czy a
$digest
jest już w toku, sprawdzając$scope.$$phase
.$scope.$$phase
powróci"$digest"
lub"$apply"
jeśli a$digest
lub$apply
jest 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$apply
będą przetwarzać obserwatorów wszystkich zakresów.Do punktu @ dnc253, jeśli często dzwonisz
$digest
lub$apply
czę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$digest
trwa, 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
źródło
if (!$scope.$$phase) $scope.$apply()
”, github.com/angular/angular.js/wiki/Anti-PatternsZ 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
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).
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!
źródło
$timeout
zamiast natywnegosetTimeout
, dlaczego nie używasz$window
zamiast natywnegowindow
?$timeout
tym przypadku warto użyć tego, aby$timeout
zapewnić poprawną aktualizację zakresu kątowego. Jeśli $ digest nie jest w toku, spowoduje uruchomienie nowego $ digest.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.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ń:
lub jeśli masz lodash:
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.
źródło
underscore.js
. To rozwiązanie nie jest warte importowania całej biblioteki podkreślenia tylko po to, aby użyć jejdefer
funkcji. Zdecydowanie wolę to$timeout
rozwiązanie, ponieważ każdy ma już dostęp$timeout
poprzez angular, bez żadnych zależności od innych bibliotek.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.
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.$digest
jeśli naprawdę wiesz, że musisz zsynchronizować tylko izolowaną część kodu HTML (ponieważ nowa$apply
zostanie 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 )
źródło
$timeout
jest krytykowany przelotnie. Czy możesz podać więcej powodów do uniknięcia$timeout
?Przydatna metoda małego pomocnika, aby utrzymać ten proces na sucho
źródło
scope.$apply(fn);
powinno być,scope.$apply(fn());
ponieważ fn () wykona funkcję, a nie fn.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:
a jeśli w swoim kodzie używasz
być może dlatego, że znajduje się w kontrolerze dyrektywy fabrycznej lub po prostu potrzebuje jakiegoś wiązania, to zrobiłbyś coś takiego:
źródło
Zobacz http://docs.angularjs.org/error/$rootScope:inprog
Problem pojawia się, gdy masz wywołanie,
$apply
któ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 progress
błą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
$apply
był uruchamiany asynchronicznie. Można to zrobić, uruchamiając kod w wywołaniu$timeout
z opóźnieniem ustawionym na0
(co jest ustawieniem domyślnym). Jednak wywołanie kodu w środku$timeout
eliminuje konieczność dzwonienia$apply
, ponieważ limit czasu $ sam w sobie uruchomi inny$digest
cykl, który z kolei dokona wszystkich niezbędnych aktualizacji itp.Rozwiązanie
Krótko mówiąc, zamiast tego:
Zrób to:
Zadzwoń tylko
$apply
wtedy, 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
$timeout
z 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.źródło
$apply
siebie, ale wciąż pojawia się błąd.$apply
jest synchroniczny (jego wywołanie zwrotne jest wykonywane, a następnie stosuje się kod następujący po $), a$timeout
nie 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:$timeout
zaczeka na odświeżenie widoku przed ponowną aktualizacją.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.źródło
you're not updating the the model correctly
?$scope.err_message = 'err message';
nieprawidłowa aktualizacja?$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 correctly
mam 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.Najkrótsza forma sejfu
$apply
to:źródło
Możesz także użyć evalAsync. Będzie działał jakiś czas po zakończeniu podsumowania!
źródło
Po pierwsze, nie naprawiaj tego w ten sposób
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
Jeśli używasz podkreślenia lub lodash, możesz użyć defer ():
źródło
Czasami nadal będziesz otrzymywać błędy, jeśli skorzystasz z tej metody ( https://stackoverflow.com/a/12859093/801426 ).
Spróbuj tego:
źródło
$rootScope
ianyScope.$root
są tym samym facetem.$rootScope.$root
jest zbędny.Powinieneś użyć $ evalAsync lub $ timeout zgodnie z kontekstem.
To jest link z dobrym wyjaśnieniem:
źródło
spróbuj użyć
zamiast
$ 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:
Bibliografia:
1. Zakres. $ ApplyAsync () vs. Zakres. $ EvalAsync () w AngularJS 1.3
źródło
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.
źródło
yearofmoo wykonał świetną robotę, tworząc dla nas funkcję $ SafeApply:
Stosowanie :
źródło
Byłem w stanie rozwiązać ten problem, dzwoniąc
$eval
zamiast$apply
w miejscach, w których wiem, że$digest
funkcja będzie działać.Zgodnie z docs ,
$apply
w zasadzie robi to:W moim przypadku
ng-click
zmiana 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
$apply
z$eval
wewną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,
$eval
wszystko, co musisz zrobić.źródło
użyj
$scope.$$phase || $scope.$apply();
zamiast tegoźródło
Rozumiejąc, że kątowa dokumenty nazywamy sprawdzając
$$phase
się anty-wzorzec , starałem się dostać$timeout
i_.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:
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ż
$$phase
metody inspekcji.źródło
To jest moja usługa utils:
i to jest przykład jego użycia:
źródło
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.źródło
Kiedy wyłączyłem debugger, błąd już się nie pojawia. W moim przypadku było to spowodowane tym, że debugger zatrzymał wykonywanie kodu.
źródło
podobne do powyższych odpowiedzi, ale działało to dla mnie wiernie ... w serwisie dodaj:
źródło
Możesz użyć
aby zapobiec błędowi.
źródło
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 () {
}); }
źródło
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
a sam kod nasłuchuje jakiegoś zdarzenia i wywołuje $ digest tylko w zakresie $ $, którego potrzebujesz
źródło
Znalazłem to: https://coderwall.com/p/ngisma, gdzie Nathan Walker (u dołu strony) sugeruje dekoratorowi w $ rootScope, aby stworzył func „safeApply”, kod:
źródło
To rozwiąże twój problem:
źródło