Tylko przeczucie: dlaczego nie przyjrzeć się, jak to robi dyrektywa ngCloak? Oczywiście dyrektywa ngCloak zarządza wyświetlaniem zawartości po załadowaniu rzeczy. Założę się, że spojrzenie na ngCloak doprowadzi do dokładnej odpowiedzi ...
EDYCJA 1 godzinę później:
Ok, cóż, spojrzałem na ngCloak i jest naprawdę krótki. To oczywiście oznacza, że funkcja kompilująca nie zostanie wykonana, dopóki wyrażenia {{template}} nie zostaną ocenione (tj. Załadowany szablon), stąd fajna funkcjonalność dyrektywy ngCloak.
Moim świadomym przypuszczeniem byłoby po prostu utworzenie dyrektywy z taką samą prostotą jak ngCloak, a następnie w funkcji kompilującej robienie wszystkiego, co chcesz. :) Umieść dyrektywę w elemencie głównym swojej aplikacji. Możesz wywołać dyrektywę w stylu myOnload i użyć jej jako atrybutu my-onload. Funkcja kompilacji zostanie wykonana po skompilowaniu szablonu (oszacowaniu wyrażeń i załadowaniu pod-szablonów).
EDYCJA, 23 godziny później:
Ok, więc poszukałem informacji i zadałem własne pytanie . Pytanie, które zadałem, było pośrednio związane z tym pytaniem, ale przypadkowo doprowadziło mnie do odpowiedzi, która rozwiązuje to pytanie.
Odpowiedź jest taka, że możesz utworzyć prostą dyrektywę i umieścić swój kod w funkcji link dyrektywy, która (w większości przypadków użycia, wyjaśnione poniżej) będzie działać, gdy element będzie gotowy / załadowany. Na podstawie opisu kolejności wykonywania funkcji kompilacji i łączenia przez Josha ,
jeśli masz ten znacznik:
<div directive1>
<div directive2>
<!-- ... -->
</div>
</div>
Następnie AngularJS utworzy dyrektywy, uruchamiając funkcje dyrektyw w określonej kolejności:
directive1: compile
directive2: compile
directive1: controller
directive1: pre-link
directive2: controller
directive2: pre-link
directive2: post-link
directive1: post-link
Domyślnie prosta funkcja "link" jest post-linkem, więc funkcja linkująca z zewnętrznej dyrektywy1 nie będzie działać, dopóki nie zostanie uruchomiona funkcja linkująca wewnętrznej dyrektywy2. Dlatego mówimy, że manipulowanie DOMem jest bezpieczne tylko w post-link. Zatem w stosunku do pierwotnego pytania nie powinno być problemu z dostępem do wewnętrznego kodu HTML dyrektywy potomnej z funkcji link dyrektywy zewnętrznej, chociaż dynamicznie wstawiana zawartość musi zostać skompilowana, jak wspomniano powyżej.
Z tego możemy wywnioskować, że możemy po prostu stworzyć dyrektywę, aby wykonać nasz kod, gdy wszystko jest gotowe / skompilowane / połączone / załadowane:
app.directive('ngElementReady', [function() {
return {
priority: -1000, // a low number so this directive loads after all other directives have loaded.
restrict: "A", // attribute only
link: function($scope, $element, $attributes) {
console.log(" -- Element ready!");
// do what you want here.
}
};
}]);
Teraz możesz umieścić dyrektywę ngElementReady w głównym elemencie aplikacji, a console.log
będzie uruchomiona po załadowaniu:
<body data-ng-app="MyApp" data-ng-element-ready="">
...
...
</body>
To takie proste! Po prostu utwórz prostą dyrektywę i użyj jej. ;)
Możesz go dodatkowo dostosować, aby mógł wykonać wyrażenie (tj. Funkcję), dodając $scope.$eval($attributes.ngElementReady);
do niego:
app.directive('ngElementReady', [function() {
return {
priority: Number.MIN_SAFE_INTEGER, // execute last, after all other directives if any.
restrict: "A",
link: function($scope, $element, $attributes) {
$scope.$eval($attributes.ngElementReady); // execute the expression in the attribute.
}
};
}]);
Następnie możesz go użyć na dowolnym elemencie:
<body data-ng-app="MyApp" data-ng-controller="BodyCtrl" data-ng-element-ready="bodyIsReady()">
...
<div data-ng-element-ready="divIsReady()">...<div>
</body>
Po prostu upewnij się, że masz zdefiniowane funkcje (np. BodyIsReady i divIsReady) w zakresie (w kontrolerze), w którym żyje Twój element.
Ostrzeżenia: powiedziałem, że to zadziała w większości przypadków. Zachowaj ostrożność podczas korzystania z niektórych dyrektyw, takich jak ngRepeat i ngIf. Tworzą swój własny zakres, a Twoja dyrektywa może się nie uruchomić. Na przykład, jeśli umieścisz naszą nową dyrektywę ngElementReady na elemencie, który również ma ngIf, a stan ngIf zostanie oceniony jako false, wówczas nasza dyrektywa ngElementReady nie zostanie załadowana. Lub, na przykład, jeśli umieścisz naszą nową dyrektywę ngElementReady na elemencie, który również ma dyrektywę ngInclude, nasza dyrektywa nie zostanie załadowana, jeśli szablon dla ngInclude nie istnieje. Możesz obejść niektóre z tych problemów, upewniając się, że dyrektywy zostały zagnieżdżone zamiast umieszczać je wszystkie w tym samym elemencie. Na przykład robiąc to:
<div data-ng-element-ready="divIsReady()">
<div data-ng-include="non-existent-template.html"></div>
<div>
zamiast tego:
<div data-ng-element-ready="divIsReady()" data-ng-include="non-existent-template.html"></div>
Dyrektywa ngElementReady zostanie skompilowana w drugim przykładzie, ale jej funkcja link nie zostanie wykonana. Uwaga: dyrektywy są zawsze kompilowane, ale ich funkcje łączące nie zawsze są wykonywane w zależności od pewnych scenariuszy, takich jak powyższy.
EDYTUJ, kilka minut później:
Aha, i aby w pełni odpowiedzieć na pytanie, możesz teraz $emit
lub $broadcast
swoje zdarzenie z wyrażenia lub funkcji, która jest wykonywana w ng-element-ready
atrybucie. :) Np .:
<div data-ng-element-ready="$emit('someEvent')">
...
<div>
EDYTUJ, jeszcze więcej kilka minut później:
Odpowiedź @ satchmorun również działa, ale tylko podczas początkowego ładowania. Oto bardzo przydatne pytanie SO, które opisuje kolejność wykonywanych czynności, w tym funkcje linków app.run
i inne. Tak więc, w zależności od twojego przypadku użycia, app.run
może być dobre, ale nie dla określonych elementów, w takim przypadku funkcje linków są lepsze.
EDYCJA, pięć miesięcy później, 17 października o 8:11 czasu PST:
Nie działa to z podrzędnymi, które są ładowane asynchronicznie. Będziesz musiał dodać księgowość do swoich podrzędnych (np. Jednym ze sposobów jest śledzenie każdego fragmentu, kiedy jego zawartość jest ładowana, a następnie emitowanie zdarzenia, aby zakres nadrzędny mógł policzyć, ile części zostało załadowanych i na koniec zrobić to, co musi zrobić po załadowaniu wszystkich części).
EDYCJA, 23 października o 22:52 czasu PST:
Zrobiłem prostą dyrektywę do odpalania kodu, gdy ładowany jest obraz:
/*
* This img directive makes it so that if you put a loaded="" attribute on any
* img element in your app, the expression of that attribute will be evaluated
* after the images has finished loading. Use this to, for example, remove
* loading animations after images have finished loading.
*/
app.directive('img', function() {
return {
restrict: 'E',
link: function($scope, $element, $attributes) {
$element.bind('load', function() {
if ($attributes.loaded) {
$scope.$eval($attributes.loaded);
}
});
}
};
});
EDIT, 24 października o 00:48 czasu PST:
Poprawiłem moją pierwotną ngElementReady
dyrektywę i zmieniłem jej nazwę na whenReady
.
/*
* The whenReady directive allows you to execute the content of a when-ready
* attribute after the element is ready (i.e. done loading all sub directives and DOM
* content except for things that load asynchronously like partials and images).
*
* Execute multiple expressions by delimiting them with a semi-colon. If there
* is more than one expression, and the last expression evaluates to true, then
* all expressions prior will be evaluated after all text nodes in the element
* have been interpolated (i.e. {{placeholders}} replaced with actual values).
*
* Caveats: if other directives exists on the same element as this directive
* and destroy the element thus preventing other directives from loading, using
* this directive won't work. The optimal way to use this is to put this
* directive on an outer element.
*/
app.directive('whenReady', ['$interpolate', function($interpolate) {
return {
restrict: 'A',
priority: Number.MIN_SAFE_INTEGER, // execute last, after all other directives if any.
link: function($scope, $element, $attributes) {
var expressions = $attributes.whenReady.split(';');
var waitForInterpolation = false;
function evalExpressions(expressions) {
expressions.forEach(function(expression) {
$scope.$eval(expression);
});
}
if ($attributes.whenReady.trim().length == 0) { return; }
if (expressions.length > 1) {
if ($scope.$eval(expressions.pop())) {
waitForInterpolation = true;
}
}
if (waitForInterpolation) {
requestAnimationFrame(function checkIfInterpolated() {
if ($element.text().indexOf($interpolate.startSymbol()) >= 0) { // if the text still has {{placeholders}}
requestAnimationFrame(checkIfInterpolated);
}
else {
evalExpressions(expressions);
}
});
}
else {
evalExpressions(expressions);
}
}
}
}]);
Na przykład użyj tego w ten sposób, aby uruchomić, someFunction
gdy element jest załadowany i {{placeholders}}
jeszcze nie zastąpiony:
<div when-ready="someFunction()">
<span ng-repeat="item in items">{{item.property}}</span>
</div>
someFunction
zostanie wywołany przed wszystkimi item.property
zastąpieniem symboli zastępczych.
Oceń tyle wyrażeń, ile chcesz, i spraw, by ostatnie wyrażenie true
czekało na {{placeholders}}
ocenę w następujący sposób:
<div when-ready="someFunction(); anotherFunction(); true">
<span ng-repeat="item in items">{{item.property}}</span>
</div>
someFunction
i anotherFunction
zostanie zwolniony po {{placeholders}}
wymianie.
Działa to tylko przy pierwszym ładowaniu elementu, a nie przy przyszłych zmianach. Może nie działać zgodnie z oczekiwaniami, jeśli $digest
po początkowym zastąpieniu symboli zastępczych zachodzi ciągłość (podsumowanie $ może nastąpić do 10 razy, aż dane przestaną się zmieniać). Będzie odpowiedni w większości przypadków użycia.
EDYCJA, 31 października o 19:26 czasu PST:
W porządku, to prawdopodobnie moja ostatnia i ostatnia aktualizacja. Prawdopodobnie zadziała to dla 99,999 przypadków użycia:
/*
* The whenReady directive allows you to execute the content of a when-ready
* attribute after the element is ready (i.e. when it's done loading all sub directives and DOM
* content). See: /programming/14968690/sending-event-when-angular-js-finished-loading
*
* Execute multiple expressions in the when-ready attribute by delimiting them
* with a semi-colon. when-ready="doThis(); doThat()"
*
* Optional: If the value of a wait-for-interpolation attribute on the
* element evaluates to true, then the expressions in when-ready will be
* evaluated after all text nodes in the element have been interpolated (i.e.
* {{placeholders}} have been replaced with actual values).
*
* Optional: Use a ready-check attribute to write an expression that
* specifies what condition is true at any given moment in time when the
* element is ready. The expression will be evaluated repeatedly until the
* condition is finally true. The expression is executed with
* requestAnimationFrame so that it fires at a moment when it is least likely
* to block rendering of the page.
*
* If wait-for-interpolation and ready-check are both supplied, then the
* when-ready expressions will fire after interpolation is done *and* after
* the ready-check condition evaluates to true.
*
* Caveats: if other directives exists on the same element as this directive
* and destroy the element thus preventing other directives from loading, using
* this directive won't work. The optimal way to use this is to put this
* directive on an outer element.
*/
app.directive('whenReady', ['$interpolate', function($interpolate) {
return {
restrict: 'A',
priority: Number.MIN_SAFE_INTEGER, // execute last, after all other directives if any.
link: function($scope, $element, $attributes) {
var expressions = $attributes.whenReady.split(';');
var waitForInterpolation = false;
var hasReadyCheckExpression = false;
function evalExpressions(expressions) {
expressions.forEach(function(expression) {
$scope.$eval(expression);
});
}
if ($attributes.whenReady.trim().length === 0) { return; }
if ($attributes.waitForInterpolation && $scope.$eval($attributes.waitForInterpolation)) {
waitForInterpolation = true;
}
if ($attributes.readyCheck) {
hasReadyCheckExpression = true;
}
if (waitForInterpolation || hasReadyCheckExpression) {
requestAnimationFrame(function checkIfReady() {
var isInterpolated = false;
var isReadyCheckTrue = false;
if (waitForInterpolation && $element.text().indexOf($interpolate.startSymbol()) >= 0) { // if the text still has {{placeholders}}
isInterpolated = false;
}
else {
isInterpolated = true;
}
if (hasReadyCheckExpression && !$scope.$eval($attributes.readyCheck)) { // if the ready check expression returns false
isReadyCheckTrue = false;
}
else {
isReadyCheckTrue = true;
}
if (isInterpolated && isReadyCheckTrue) { evalExpressions(expressions); }
else { requestAnimationFrame(checkIfReady); }
});
}
else {
evalExpressions(expressions);
}
}
};
}]);
Użyj tego w ten sposób
<div when-ready="isReady()" ready-check="checkIfReady()" wait-for-interpolation="true">
isReady will fire when this {{placeholder}} has been evaluated
and when checkIfReady finally returns true. checkIfReady might
contain code like `$('.some-element').length`.
</div>
Oczywiście można to prawdopodobnie zoptymalizować, ale na tym poprzestanę. requestAnimationFrame jest fajne.
W dokumentacji dla
angular.Module
znajduje się wpis opisującyrun
funkcję:Więc jeśli masz jakiś moduł, który jest Twoją aplikacją:
Możesz uruchamiać rzeczy po załadowaniu modułów:
EDYCJA: Ręczna inicjalizacja na ratunek
Więc zostało powiedziane, że
run
nie jest wywoływany, gdy DOM jest gotowy i połączony. Jest wywoływana, gdy$injector
moduł, do którego się odwołujeng-app
, załadował wszystkie swoje zależności, co jest niezależne od kroku kompilacji DOM.Jeszcze raz przyjrzałem się ręcznej inicjalizacji i wydaje się, że to powinno załatwić sprawę.
Zrobiłem skrzypce, żeby zilustrować .
HTML jest prosty:
Zwróć uwagę na brak
ng-app
. I mam dyrektywę, która zrobi pewne manipulacje DOM, więc możemy upewnić się co do kolejności i czasu rzeczy.Jak zwykle tworzony jest moduł:
A oto dyrektywa:
Zamierzamy zamienić
test-directive
tag na adiv
of classtest-directive
i opakować jego zawartość wh1
.Dodałem funkcję kompilacji, która zwraca funkcje przed i po łączeniu, dzięki czemu możemy zobaczyć, kiedy te rzeczy działają.
Oto reszta kodu:
Zanim cokolwiek zrobimy,
test-directive
w DOM nie powinno być elementów z klasą , a po zakończeniu powinno być 1.To całkiem proste. Gdy dokument jest gotowy, wywołaj
angular.bootstrap
element główny aplikacji i tablicę nazw modułów.W rzeczywistości, jeśli dołączysz
run
funkcję doapp
modułu , zobaczysz, że zostanie ona uruchomiona przed jakąkolwiek kompilacją.Jeśli uruchomisz skrzypce i obejrzysz konsolę, zobaczysz:
źródło
run
odpala przed tą dyrektywą i po uruchomieniu odpala, html nie jest tam wszystko$timeout( initMyPlugins,0)
mojej dyrektywy, całyAngular nie dostarczył sposobu, aby zasygnalizować zakończenie ładowania strony, być może dlatego, że „ukończona” zależy od aplikacji . Na przykład, jeśli masz hierarchiczne drzewo części składowych, jedna ładuje inne. „Zakończ” oznaczałoby, że wszystkie zostały załadowane. Każdy framework miałby trudności z analizą twojego kodu i zrozumieniem, że wszystko jest zrobione lub nadal czeka. W tym celu należy zapewnić logikę specyficzną dla aplikacji, aby to sprawdzić i określić.
źródło
Wymyśliłem rozwiązanie, które jest stosunkowo dokładne w ocenie, kiedy inicjalizacja kątowa jest zakończona.
Dyrektywa jest następująca:
Można to po prostu dodać jako atrybut do
body
elementu, a następnie nasłuchiwać użycia$scope.$on('initialised', fn)
Działa przy założeniu, że aplikacja jest inicjowana, gdy nie ma więcej cykli $ digest. $ watch jest wywoływana każdym cyklem podsumowania, więc uruchamiany jest licznik czasu (setTimeout nie $ timeout, więc nowy cykl podsumowania nie jest wyzwalany). Jeśli cykl podsumowania nie nastąpi w określonym czasie, zakłada się, że aplikacja została zainicjowana.
To oczywiście nie jest tak dokładne jak rozwiązanie satchmoruns (ponieważ możliwe jest, że cykl podsumowania trwa dłużej niż limit czasu), ale moje rozwiązanie nie wymaga śledzenia modułów, co znacznie ułatwia zarządzanie (szczególnie w przypadku większych projektów ). W każdym razie wydaje się być wystarczająco dokładny dla moich wymagań. Mam nadzieję, że to pomoże.
źródło
Jeśli używasz Angular UI Router , możesz nasłuchiwać
$viewContentLoaded
zdarzenia.„$ viewContentLoaded - uruchamiane po załadowaniu widoku, po wyrenderowaniu modelu DOM . '$ scope' widoku emituje zdarzenie”. - Link
źródło
obserwuję manipulację DOM na kątach za pomocą JQuery i ustawiłem wykończenie mojej aplikacji (pewna predefiniowana i zadowalająca sytuacja, której potrzebuję dla mojej aplikacji), na przykład oczekuję, że mój repeater ng da wynik 7 i tam dla mnie w tym celu ustawi funkcję obserwacyjną z pomocą setInterval.
źródło
Jeśli nie używasz modułu ngRoute , czyli nie masz zdarzenia $ viewContentLoaded .
Możesz użyć innej metody dyrektywy:
Zgodnie z odpowiedzią Trusktr ma najniższy priorytet. Dodatkowo $ timeout spowoduje, że Angular przejdzie przez całą pętlę zdarzeń przed wykonaniem wywołania zwrotnego.
Zastosowano $ rootScope , ponieważ pozwala na umieszczenie dyrektywy w dowolnym zakresie aplikacji i powiadomienie tylko niezbędnych słuchaczy.
źródło
Według zespołu Angular i tego wydania Github :
Na tej podstawie wydaje się, że obecnie nie jest to możliwe do wykonania w wiarygodny sposób, w przeciwnym razie Angular dostarczyłby wydarzenie po wyjęciu z pudełka.
Uruchomienie aplikacji oznacza uruchomienie cyklu podsumowania w zakresie głównym, a także nie ma zdarzenia zakończenia cyklu podsumowania.
Zgodnie z dokumentacją projektową Angular 2 :
W związku z tym fakt, że nie jest to możliwe, jest jednym z powodów, dla których podjęto decyzję o przepisaniu w Angular 2.
źródło
Miałem fragment, który był ładowany po / przez główną część, która przyszła przez routing.
Musiałem uruchomić funkcję po załadowaniu tej częściowej i nie chciałem pisać nowej dyrektywy i doszedłem do wniosku, że możesz użyć bezczelnego
ngIf
Kontroler nadrzędnej części:
HTML z subpartial
źródło
Jeśli chcesz wygenerować JS z danymi po stronie serwera (JSP, PHP), możesz dodać swoją logikę do usługi, która zostanie załadowana automatycznie po załadowaniu kontrolera.
Dodatkowo, jeśli chcesz reagować, gdy wszystkie dyrektywy są już kompilowane / linkowane, możesz dodać odpowiednie proponowane rozwiązania powyżej w logice inicjalizacji.
źródło
To wszystko są świetne rozwiązania, jednak jeśli obecnie używasz routingu, to okazało się, że jest to najłatwiejsze i najmniej wymagające kodu. Korzystanie z właściwości „rozstrzygnij”, aby czekać na wypełnienie obietnicy przed uruchomieniem trasy. na przykład
})
Kliknij tutaj, aby zobaczyć pełną dokumentację - Credit to K. Scott Allen
źródło
być może mogę ci pomóc na tym przykładzie
W niestandardowym fancybox pokazuję zawartość z interpolowanymi wartościami.
w serwisie, w metodzie fancybox "open" tak
$ compile zwraca skompilowane dane. możesz sprawdzić skompilowane dane
źródło