Ich habe mich gefragt, was der beste Weg ist, um das Ende des Ladens / Bootstrappens von Seiten zu erkennen, wenn alle Anweisungen kompiliert / verknüpft sind.
Gibt es schon eine Veranstaltung? Sollte ich die Bootstrap-Funktion überladen?
Ich habe mich gefragt, was der beste Weg ist, um das Ende des Ladens / Bootstrappens von Seiten zu erkennen, wenn alle Anweisungen kompiliert / verknüpft sind.
Gibt es schon eine Veranstaltung? Sollte ich die Bootstrap-Funktion überladen?
Nur eine Vermutung: Warum nicht schauen, wie die ngCloak-Direktive das macht? Es ist klar, dass die ngCloak-Direktive es schafft, Inhalte anzuzeigen, nachdem Dinge geladen wurden. Ich wette, ein Blick auf ngCloak wird zur genauen Antwort führen ...
EDIT 1 Stunde später: Ok, nun, ich habe mir ngCloak angesehen und es ist wirklich kurz. Dies impliziert offensichtlich, dass die Kompilierungsfunktion erst ausgeführt wird, wenn {{template}} -Ausdrücke ausgewertet wurden (dh die geladene Vorlage), was die nette Funktionalität der ngCloak-Direktive darstellt.
Meine Vermutung wäre, einfach eine Direktive mit der gleichen Einfachheit wie ngCloak zu erstellen und dann in Ihrer Kompilierungsfunktion zu tun, was Sie wollen. :) Platziere die Direktive auf dem Root-Element deiner App. Sie können die Direktive so etwas wie myOnload aufrufen und als Attribut my-onload verwenden. Die Kompilierungsfunktion wird ausgeführt, sobald die Vorlage kompiliert wurde (Ausdrücke ausgewertet und Untervorlagen geladen).
EDIT, 23 Stunden später: Ok, also habe ich einige Nachforschungen angestellt und auch meine eigene Frage gestellt . Die Frage, die ich gestellt habe, war indirekt mit dieser Frage verbunden, führte mich aber zufällig zu der Antwort, die diese Frage löst.
Die Antwort ist, dass Sie eine einfache Direktive erstellen und Ihren Code in die Verknüpfungsfunktion der Direktive einfügen können, die (für die meisten unten erläuterten Anwendungsfälle) ausgeführt wird, wenn Ihr Element bereit / geladen ist. Basierend auf Joshs Beschreibung der Reihenfolge, in der Kompilierungs- und Verknüpfungsfunktionen ausgeführt werden ,
Wenn Sie dieses Markup haben:
<div directive1> <div directive2> <!-- ... --> </div> </div>
Dann erstellt AngularJS die Direktiven, indem Direktivenfunktionen in einer bestimmten Reihenfolge ausgeführt werden:
directive1: compile directive2: compile directive1: controller directive1: pre-link directive2: controller directive2: pre-link directive2: post-link directive1: post-link
Standardmäßig ist eine gerade "Link" -Funktion eine Post-Link-Funktion, sodass die Link-Funktion Ihrer äußeren Direktive1 erst ausgeführt wird, nachdem die Link-Funktion der inneren Direktive2 ausgeführt wurde. Deshalb sagen wir, dass es nur sicher ist, DOM-Manipulationen im Post-Link durchzuführen. In Bezug auf die ursprüngliche Frage sollte es also kein Problem geben, über die Link-Funktion der äußeren Direktive auf das innere HTML der untergeordneten Direktive zuzugreifen, obwohl dynamisch eingefügte Inhalte wie oben beschrieben kompiliert werden müssen.
Daraus können wir schließen, dass wir einfach eine Anweisung zur Ausführung unseres Codes erstellen können, wenn alles fertig / kompiliert / verknüpft / geladen ist:
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.
}
};
}]);
Jetzt können Sie die Direktive ngElementReady auf das Stammelement der App setzen, und der console.log
Wille wird ausgelöst, wenn sie geladen wird:
<body data-ng-app="MyApp" data-ng-element-ready="">
...
...
</body>
So einfach ist das! Machen Sie einfach eine einfache Anweisung und verwenden Sie sie. ;)
Sie können es weiter anpassen, damit es einen Ausdruck (dh eine Funktion) ausführen kann, indem Sie Folgendes hinzufügen $scope.$eval($attributes.ngElementReady);
:
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.
}
};
}]);
Dann können Sie es für jedes Element verwenden:
<body data-ng-app="MyApp" data-ng-controller="BodyCtrl" data-ng-element-ready="bodyIsReady()">
...
<div data-ng-element-ready="divIsReady()">...<div>
</body>
Stellen Sie einfach sicher, dass Ihre Funktionen (z. B. bodyIsReady und divIsReady) in dem Bereich (im Controller) definiert sind, unter dem Ihr Element lebt.
Vorsichtsmaßnahmen: Ich sagte, dass dies in den meisten Fällen funktionieren wird. Seien Sie vorsichtig, wenn Sie bestimmte Anweisungen wie ngRepeat und ngIf verwenden. Sie erstellen ihren eigenen Bereich, und Ihre Direktive wird möglicherweise nicht ausgelöst. Wenn Sie beispielsweise unsere neue ngElementReady-Direktive auf ein Element setzen, das auch ngIf enthält, und die Bedingung der ngIf als false ausgewertet wird, wird unsere ngElementReady-Direktive nicht geladen. Wenn Sie beispielsweise unsere neue ngElementReady-Direktive auf ein Element setzen, das auch eine ngInclude-Direktive enthält, wird unsere Direktive nicht geladen, wenn die Vorlage für ngInclude nicht vorhanden ist. Sie können einige dieser Probleme umgehen, indem Sie sicherstellen, dass Sie die Anweisungen verschachteln, anstatt sie alle auf dasselbe Element zu setzen. Zum Beispiel auf diese Weise:
<div data-ng-element-ready="divIsReady()">
<div data-ng-include="non-existent-template.html"></div>
<div>
an Stelle von:
<div data-ng-element-ready="divIsReady()" data-ng-include="non-existent-template.html"></div>
Die Anweisung ngElementReady wird im letzteren Beispiel kompiliert, die Verknüpfungsfunktion wird jedoch nicht ausgeführt. Hinweis: Direktiven werden immer kompiliert, aber ihre Verknüpfungsfunktionen werden abhängig von bestimmten Szenarien wie den oben genannten nicht immer ausgeführt.
BEARBEITEN, einige Minuten später:
Oh, und um die Frage vollständig zu beantworten, können Sie jetzt $emit
oder $broadcast
Ihr Ereignis aus dem Ausdruck oder der Funktion, die im ng-element-ready
Attribut ausgeführt wird. :) Z.B:
<div data-ng-element-ready="$emit('someEvent')">
...
<div>
EDIT, noch einige Minuten später:
Die Antwort von @ satchmorun funktioniert auch, aber nur für das anfängliche Laden. Hier ist eine sehr nützliche SO-Frage , die die Reihenfolge beschreibt, in der Dinge ausgeführt werden, einschließlich Verknüpfungsfunktionen app.run
und anderer. Abhängig von Ihrem Anwendungsfall ist dies app.run
möglicherweise gut, jedoch nicht für bestimmte Elemente. In diesem Fall sind Verknüpfungsfunktionen besser.
EDIT, fünf Monate später, 17. Oktober um 8:11 PST:
Dies funktioniert nicht mit Partials, die asynchron geladen werden. Sie müssen Ihren Partials Buchhaltung hinzufügen (z. B. besteht eine Möglichkeit darin, dass jeder Teil nachverfolgt wird, wann der Inhalt geladen ist, und dann ein Ereignis ausgibt, damit der übergeordnete Bereich zählen kann, wie viele Partials geladen wurden, und schließlich das tut, was er benötigt tun, nachdem alle Partials geladen wurden).
EDIT, 23. Oktober um 22.52 Uhr PST:
Ich habe eine einfache Anweisung zum Auslösen von Code beim Laden eines Bildes erstellt:
/*
* 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. Oktober um 00:48 Uhr PST:
Ich habe meine ursprüngliche ngElementReady
Richtlinie verbessert und in umbenannt 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);
}
}
}
}]);
Verwenden Sie es beispielsweise so, um zu feuern, someFunction
wenn ein Element geladen und {{placeholders}}
noch nicht ersetzt wird:
<div when-ready="someFunction()">
<span ng-repeat="item in items">{{item.property}}</span>
</div>
someFunction
wird aufgerufen, bevor alle item.property
Platzhalter ersetzt werden.
Bewerten Sie so viele Ausdrücke, wie Sie möchten, und lassen Sie den letzten Ausdruck, true
auf den Sie warten, {{placeholders}}
wie folgt ausgewertet werden:
<div when-ready="someFunction(); anotherFunction(); true">
<span ng-repeat="item in items">{{item.property}}</span>
</div>
someFunction
und anotherFunction
wird nach dem {{placeholders}}
Austausch abgefeuert .
Dies funktioniert nur beim ersten Laden eines Elements, nicht bei zukünftigen Änderungen. Es funktioniert möglicherweise nicht wie gewünscht, wenn $digest
nach dem ersten Ersetzen von Platzhaltern immer wieder ein Fehler auftritt (ein $ Digest kann bis zu 10 Mal auftreten, bis sich die Daten nicht mehr ändern). Es ist für die meisten Anwendungsfälle geeignet.
EDIT, 31. Oktober um 19:26 Uhr PST:
Okay, dies ist wahrscheinlich mein letztes und letztes Update. Dies wird wahrscheinlich für 99,999 der Anwendungsfälle funktionieren:
/*
* 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);
}
}
};
}]);
Verwenden Sie es so
<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>
Natürlich kann es wahrscheinlich optimiert werden, aber ich werde es dabei belassen. requestAnimationFrame ist nett.
In den Dokumenten für
angular.Module
gibt es einen Eintrag, der dierun
Funktion beschreibt:Wenn Sie also ein Modul haben, das Ihre App ist:
Sie können Dinge ausführen, nachdem die Module geladen wurden mit:
EDIT: Manuelle Initialisierung zur Rettung
Es wurde also darauf hingewiesen, dass das
run
nicht aufgerufen wird, wenn das DOM bereit und verbunden ist. Es wird aufgerufen, wenn das$injector
für das Modul, auf das verwiesenng-app
wird, alle seine Abhängigkeiten geladen hat, was vom DOM-Kompilierungsschritt getrennt ist.Ich habe mir die manuelle Initialisierung noch einmal angesehen , und es scheint, dass dies den Trick machen sollte.
Ich habe eine Geige gemacht, um zu veranschaulichen .
Der HTML-Code ist einfach:
Beachten Sie das Fehlen eines
ng-app
. Und ich habe eine Anweisung, die einige DOM-Manipulationen vornimmt, damit wir die Reihenfolge und das Timing der Dinge sicherstellen können.Wie üblich wird ein Modul erstellt:
Und hier ist die Richtlinie:
Wir werden das
test-directive
Tag durch eindiv
of-Klasse ersetzentest-directive
und seinen Inhalt in ein verpackenh1
.Ich habe eine Kompilierungsfunktion hinzugefügt, die sowohl Pre- als auch Post-Link-Funktionen zurückgibt, damit wir sehen können, wann diese Dinge ausgeführt werden.
Hier ist der Rest des Codes:
Bevor wir etwas getan haben, sollte es keine Elemente mit einer Klasse von
test-directive
im DOM geben, und nachdem wir fertig sind, sollte es 1 geben.Es ist ziemlich einfach. Wenn das Dokument fertig ist, rufen Sie
angular.bootstrap
mit dem Stammelement Ihrer App und einer Reihe von Modulnamen auf.Wenn Sie
run
demapp
Modul eine Funktion hinzufügen , wird diese tatsächlich ausgeführt, bevor die Kompilierung stattfindet.Wenn Sie die Geige laufen lassen und die Konsole beobachten, sehen Sie Folgendes:
quelle
run
die vor der Direktive$timeout( initMyPlugins,0)
Arbeiten innerhalb meiner Direktive alles HTML, das ich brauche, da istAngular hat keine Möglichkeit bereitgestellt, zu signalisieren, wann eine Seite vollständig geladen wurde, möglicherweise weil "fertig" von Ihrer Anwendung abhängt . Wenn Sie beispielsweise einen hierarchischen Teilbaum haben, lädt einer den anderen. "Fertig stellen" würde bedeuten, dass alle geladen wurden. Jedes Framework würde es schwer haben, Ihren Code zu analysieren und zu verstehen, dass alles erledigt ist oder noch gewartet wird. Dazu müssten Sie eine anwendungsspezifische Logik bereitstellen, um dies zu überprüfen und festzustellen.
quelle
Ich habe eine Lösung gefunden, die relativ genau bewertet, wann die Winkelinitialisierung abgeschlossen ist.
Die Richtlinie lautet:
Das kann dann einfach als Attribut zum
body
Element hinzugefügt und dann auf Verwendung abgehört werden$scope.$on('initialised', fn)
Es wird davon ausgegangen, dass die Anwendung initialisiert wird, wenn keine $ Digest-Zyklen mehr vorhanden sind. $ watch wird bei jedem Digest-Zyklus aufgerufen und daher wird ein Timer gestartet (setTimeout nicht $ timeout, damit kein neuer Digest-Zyklus ausgelöst wird). Wenn innerhalb des Timeouts kein Digest-Zyklus auftritt, wird davon ausgegangen, dass die Anwendung initialisiert wurde.
Es ist offensichtlich nicht so genau wie die Satchmoruns-Lösung (da ein Digest-Zyklus möglicherweise länger dauert als das Timeout), aber bei meiner Lösung müssen Sie nicht die Module im Auge behalten, was die Verwaltung erheblich vereinfacht (insbesondere bei größeren Projekten) ). Wie auch immer, scheint genau genug für meine Anforderungen zu sein. Ich hoffe es hilft.
quelle
Wenn Sie Angular UI Router verwenden , können Sie auf das
$viewContentLoaded
Ereignis warten."$ viewContentLoaded - wird ausgelöst, sobald die Ansicht geladen wurde, nachdem das DOM gerendert wurde . Der '$ scope' der Ansicht gibt das Ereignis aus." - Link
quelle
Ich beobachte die DOM-Manipulation des Winkels mit JQuery und habe ein Finish für meine App festgelegt (eine vordefinierte und zufriedenstellende Situation, die ich für meine App-Zusammenfassung benötige). Ich erwarte beispielsweise, dass mein ng-Repeater 7 Ergebnisse liefert und dort für i wird zu diesem Zweck mit Hilfe von setInterval eine Beobachtungsfunktion einstellen.
quelle
Wenn Sie das ngRoute- Modul nicht verwenden , haben Sie kein $ viewContentLoaded- Ereignis.
Sie können eine andere Direktivenmethode verwenden:
Entsprechend der Antwort von trusktr hat es die niedrigste Priorität. Plus $ timeout bewirkt, dass Angular vor der Ausführung des Rückrufs eine gesamte Ereignisschleife durchläuft.
$ rootScope wird verwendet, da damit Direktiven in einen beliebigen Bereich der Anwendung eingefügt und nur die erforderlichen Listener benachrichtigt werden können.
quelle
Laut dem Angular-Team und dieser Github-Ausgabe :
Auf dieser Grundlage scheint dies derzeit nicht zuverlässig möglich zu sein, da Angular sonst das Ereignis sofort bereitgestellt hätte.
Das Bootstrapping der App impliziert das Ausführen des Digest-Zyklus im Root-Bereich, und es gibt auch kein Ereignis, bei dem der Digest-Zyklus beendet ist.
Gemäß den Angular 2- Designdokumenten :
Demnach ist die Tatsache, dass dies nicht möglich ist, einer der Gründe, warum die Entscheidung getroffen wurde, in Angular 2 eine Neufassung vorzunehmen.
quelle
Ich hatte ein Fragment, das nach / durch den Hauptteil geladen wurde, der über das Routing hereinkam.
Ich musste eine Funktion ausführen, nachdem dieses Subpartial geladen wurde, und ich wollte keine neue Direktive schreiben und fand heraus, dass Sie eine freche verwenden könnten
ngIf
Controller des übergeordneten Teils:
HTML von subpartial
quelle
Wenn Sie JS mit serverseitigen Daten (JSP, PHP) generieren möchten, können Sie Ihre Logik einem Dienst hinzufügen, der automatisch geladen wird, wenn Ihr Controller geladen wird.
Wenn Sie reagieren möchten, wenn alle Anweisungen fertig kompiliert / verknüpft sind, können Sie die entsprechenden Lösungsvorschläge oben in der Initialisierungslogik hinzufügen.
quelle
Dies sind alles großartige Lösungen. Wenn Sie jedoch derzeit Routing verwenden, habe ich festgestellt, dass diese Lösung die einfachste und am wenigsten benötigte Codemenge ist. Verwenden Sie die Eigenschaft "Auflösen", um auf das Ausfüllen eines Versprechens zu warten, bevor Sie die Route auslösen. z.B
})
Klicken Sie hier für die vollständigen Dokumente - Dank an K. Scott Allen
quelle
Vielleicht kann ich Ihnen anhand dieses Beispiels helfen
In der benutzerdefinierten Fancybox zeige ich Inhalte mit interpolierten Werten.
im Service, in der "offenen" Fancybox-Methode, mache ich
Die $ compile gibt kompilierte Daten zurück. Sie können die kompilierten Daten überprüfen
quelle