Risposte:
Solo un'intuizione: perché non guardare come funziona la direttiva ngCloak? Chiaramente la direttiva ngCloak riesce a mostrare il contenuto dopo che le cose sono state caricate. Scommetto che guardare ngCloak porterà alla risposta esatta ...
EDIT 1 ora dopo: Ok, beh, ho guardato ngCloak ed è davvero breve. Ciò che questo ovviamente implica è che la funzione di compilazione non verrà eseguita fino a quando le espressioni {{template}} non saranno state valutate (cioè il modello che ha caricato), quindi la bella funzionalità della direttiva ngCloak.
La mia ipotesi plausibile sarebbe quella di creare una direttiva con la stessa semplicità di ngCloak, quindi nella tua funzione di compilazione fai quello che vuoi fare. :) Posiziona la direttiva sull'elemento radice della tua app. Puoi chiamare la direttiva qualcosa come myOnload e usarla come attributo my-onload. La funzione di compilazione verrà eseguita una volta che il modello è stato compilato (espressioni valutate e sotto-modelli caricati).
EDIT, 23 ore dopo: Ok, quindi ho fatto delle ricerche e ho anche posto la mia domanda . La domanda che ho posto era indirettamente correlata a questa domanda, ma casualmente mi ha portato alla risposta che risolve questa domanda.
La risposta è che puoi creare una semplice direttiva e inserire il tuo codice nella funzione di collegamento della direttiva, che (per la maggior parte dei casi d'uso, spiegata di seguito) verrà eseguita quando il tuo elemento è pronto / caricato. In base alla descrizione di Josh dell'ordine in cui vengono eseguite le funzioni di compilazione e collegamento ,
se hai questo markup:
<div directive1> <div directive2> <!-- ... --> </div> </div>
Quindi AngularJS creerà le direttive eseguendo le funzioni di direttiva in un certo ordine:
directive1: compile directive2: compile directive1: controller directive1: pre-link directive2: controller directive2: pre-link directive2: post-link directive1: post-link
Per impostazione predefinita, una funzione di "collegamento" diretta è un post-collegamento, quindi la funzione di collegamento della direttiva esterna1 non verrà eseguita fino a quando la funzione di collegamento della direttiva interna2 non sarà stata eseguita. Ecco perché diciamo che è sicuro solo manipolare DOM nel post-collegamento. Quindi, rispetto alla domanda originale, non dovrebbero esserci problemi di accesso all'html interno della direttiva figlia dalla funzione di collegamento della direttiva esterna, sebbene i contenuti inseriti dinamicamente debbano essere compilati, come detto sopra.
Da ciò possiamo concludere che possiamo semplicemente creare una direttiva per eseguire il nostro codice quando tutto è pronto / compilato / collegato / caricato:
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.
}
};
}]);
Ora quello che puoi fare è inserire la direttiva ngElementReady sull'elemento radice dell'app e si console.log
attiverà quando viene caricata:
<body data-ng-app="MyApp" data-ng-element-ready="">
...
...
</body>
È così semplice! Basta fare una semplice direttiva e usarla. ;)
Puoi personalizzarlo ulteriormente in modo che possa eseguire un'espressione (cioè una funzione) aggiungendovi $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.
}
};
}]);
Quindi puoi usarlo su qualsiasi elemento:
<body data-ng-app="MyApp" data-ng-controller="BodyCtrl" data-ng-element-ready="bodyIsReady()">
...
<div data-ng-element-ready="divIsReady()">...<div>
</body>
Assicurati solo di avere le tue funzioni (ad esempio bodyIsReady e divIsReady) definite nell'ambito (nel controller) in cui risiede il tuo elemento.
Avvertenze: ho detto che funzionerà per la maggior parte dei casi. Fai attenzione quando usi determinate direttive come ngRepeat e ngIf. Creano il proprio ambito e la tua direttiva potrebbe non essere attivata. Ad esempio, se metti la nostra nuova direttiva ngElementReady su un elemento che ha anche ngIf e la condizione di ngIf restituisce false, la nostra direttiva ngElementReady non verrà caricata. Oppure, ad esempio, se metti la nostra nuova direttiva ngElementReady su un elemento che ha anche una direttiva ngInclude, la nostra direttiva non verrà caricata se il modello per ngInclude non esiste. Puoi aggirare alcuni di questi problemi assicurandoti di annidare le direttive invece di metterle tutte sullo stesso elemento. Ad esempio, in questo modo:
<div data-ng-element-ready="divIsReady()">
<div data-ng-include="non-existent-template.html"></div>
<div>
Invece di questo:
<div data-ng-element-ready="divIsReady()" data-ng-include="non-existent-template.html"></div>
La direttiva ngElementReady verrà compilata nell'ultimo esempio, ma la sua funzione di collegamento non verrà eseguita. Nota: le direttive vengono sempre compilate, ma le loro funzioni di collegamento non vengono sempre eseguite a seconda di determinati scenari come sopra.
EDIT, pochi minuti dopo:
Oh, e per rispondere completamente alla domanda, puoi ora $emit
o il $broadcast
tuo evento dall'espressione o dalla funzione che viene eseguita ng-element-ready
nell'attributo. :) Per esempio:
<div data-ng-element-ready="$emit('someEvent')">
...
<div>
EDIT, ancora di più pochi minuti dopo:
Anche la risposta di @ satchmorun funziona, ma solo per il caricamento iniziale. Ecco una domanda SO molto utile che descrive l'ordine in cui le cose vengono eseguite, incluse le funzioni di collegamento app.run
e altre. Quindi, a seconda del tuo caso d'uso, app.run
potrebbe essere buono, ma non per elementi specifici, nel qual caso le funzioni di collegamento sono migliori.
EDIT, cinque mesi dopo, 17 ottobre alle 8:11 PST:
Questo non funziona con i parziali caricati in modo asincrono. Dovrai aggiungere la contabilità ai tuoi parziali (ad esempio, un modo è far sì che ogni parziale tenga traccia di quando il suo contenuto è terminato il caricamento, quindi emette un evento in modo che l'ambito genitore possa contare quanti parziali sono stati caricati e infine fare ciò di cui ha bisogno dopo che tutti i parziali sono stati caricati).
EDIT, 23 ottobre alle 22:52 PST:
Ho creato una semplice direttiva per attivare del codice quando viene caricata un'immagine:
/*
* 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 ottobre alle 00:48 PST:
Ho migliorato la mia ngElementReady
direttiva originale e l'ho rinominata 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);
}
}
}
}]);
Ad esempio, usalo in questo modo per attivare someFunction
quando un elemento viene caricato e {{placeholders}}
non ancora sostituito:
<div when-ready="someFunction()">
<span ng-repeat="item in items">{{item.property}}</span>
</div>
someFunction
verrà chiamato prima che tutti i item.property
segnaposto vengano sostituiti.
Valuta tutte le espressioni che desideri e crea l'ultima espressione true
da attendere per {{placeholders}}
essere valutata in questo modo:
<div when-ready="someFunction(); anotherFunction(); true">
<span ng-repeat="item in items">{{item.property}}</span>
</div>
someFunction
e anotherFunction
verrà licenziato dopo {{placeholders}}
essere stato sostituito.
Funziona solo la prima volta che un elemento viene caricato, non su modifiche future. Potrebbe non funzionare come desiderato se un messaggio $digest
continua a verificarsi dopo che i segnaposto sono stati inizialmente sostituiti (un $ digest può verificarsi fino a 10 volte fino a quando i dati non smettono di cambiare). Sarà adatto per la stragrande maggioranza dei casi d'uso.
EDIT, 31 ottobre alle 19:26 PST:
Va bene, questo è probabilmente il mio ultimo e ultimo aggiornamento. Questo probabilmente funzionerà per 99.999 dei casi d'uso là fuori:
/*
* 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);
}
}
};
}]);
Usalo in questo modo
<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>
Certo, probabilmente può essere ottimizzato, ma mi limiterò a farlo. requestAnimationFrame è carino.
Nella documentazione perangular.Module
, c'è una voce che descrive la run
funzione:
Utilizzare questo metodo per registrare il lavoro da eseguire quando l'iniettore ha terminato di caricare tutti i moduli.
Quindi, se hai un modulo che è la tua app:
var app = angular.module('app', [/* module dependencies */]);
Puoi eseguire cose dopo che i moduli sono stati caricati con:
app.run(function() {
// Do post-load initialization stuff here
});
Quindi è stato sottolineato che il run
non viene chiamato quando il DOM è pronto e collegato. Viene chiamato quando il $injector
per il modulo a cui fa riferimento ng-app
ha caricato tutte le sue dipendenze, che è separato dalla fase di compilazione DOM.
Ho dato un'altra occhiata all'inizializzazione manuale e sembra che questo dovrebbe fare il trucco.
Ho creato un violino per illustrare .
L'HTML è semplice:
<html>
<body>
<test-directive>This is a test</test-directive>
</body>
</html>
Notare la mancanza di un file ng-app
. E ho una direttiva che eseguirà alcune manipolazioni DOM, così possiamo assicurarci dell'ordine e della tempistica delle cose.
Come al solito, viene creato un modulo:
var app = angular.module('app', []);
Ed ecco la direttiva:
app.directive('testDirective', function() {
return {
restrict: 'E',
template: '<div class="test-directive"><h1><div ng-transclude></div></h1></div>',
replace: true,
transclude: true,
compile: function() {
console.log("Compiling test-directive");
return {
pre: function() { console.log("Prelink"); },
post: function() { console.log("Postlink"); }
};
}
};
});
Sostituiremo il test-directive
tag con un div
of class test-directive
e inseriremo il suo contenuto in un file h1
.
Ho aggiunto una funzione di compilazione che restituisce sia le funzioni pre che post link in modo da poter vedere quando queste cose vengono eseguite.
Ecco il resto del codice:
// The bootstrapping process
var body = document.getElementsByTagName('body')[0];
// Check that our directive hasn't been compiled
function howmany(classname) {
return document.getElementsByClassName(classname).length;
}
Prima di fare qualsiasi cosa, non dovrebbero esserci elementi con una classe di test-directive
nel DOM, e dopo aver finito dovrebbe esserci 1.
console.log('before (should be 0):', howmany('test-directive'));
angular.element(document).ready(function() {
// Bootstrap the body, which loades the specified modules
// and compiled the DOM.
angular.bootstrap(body, ['app']);
// Our app is loaded and the DOM is compiled
console.log('after (should be 1):', howmany('test-directive'));
});
È abbastanza semplice. Quando il documento è pronto, chiama angular.bootstrap
con l'elemento radice della tua app e un array di nomi di modulo.
In effetti, se colleghi una run
funzione al app
modulo , vedrai che viene eseguita prima che abbia luogo la compilazione.
Se esegui il violino e guardi la console, vedrai quanto segue:
before (should be 0): 0
Compiling test-directive
Prelink
Postlink
after (should be 1): 1 <--- success!
run
si attiva prima della direttiva e quando viene eseguita, html non è tutto lì
$timeout( initMyPlugins,0)
lavori all'interno della mia direttiva, tutto l'html di cui ho bisogno è lì
Angular non ha fornito un modo per segnalare quando una pagina ha terminato il caricamento, forse perché "finito" dipende dalla tua applicazione . Ad esempio, se hai un albero gerarchico dei parziali, uno carica gli altri. "Fine" significherebbe che sono stati caricati tutti. Qualsiasi framework farebbe fatica ad analizzare il tuo codice e a capire che tutto è fatto, o è ancora in attesa. Per questo, dovresti fornire una logica specifica per l'applicazione per verificarlo e determinarlo.
Ho trovato una soluzione che è relativamente accurata nel valutare quando l'inizializzazione angolare è completa.
La direttiva è:
.directive('initialisation',['$rootScope',function($rootScope) {
return {
restrict: 'A',
link: function($scope) {
var to;
var listener = $scope.$watch(function() {
clearTimeout(to);
to = setTimeout(function () {
console.log('initialised');
listener();
$rootScope.$broadcast('initialised');
}, 50);
});
}
};
}]);
Questo può quindi essere semplicemente aggiunto come attributo body
all'elemento e quindi ascoltato per l'utilizzo$scope.$on('initialised', fn)
Funziona presupponendo che l'applicazione venga inizializzata quando non ci sono più cicli $ digest. $ watch viene chiamato ogni ciclo digest e quindi viene avviato un timer (setTimeout non $ timeout, quindi un nuovo ciclo digest non viene attivato). Se un ciclo di digest non si verifica entro il timeout, si presume che l'applicazione sia stata inizializzata.
Ovviamente non è preciso come la soluzione satchmoruns (poiché è possibile che un ciclo di digest richieda più tempo del timeout) ma la mia soluzione non richiede che tu tenga traccia dei moduli, il che lo rende molto più facile da gestire (in particolare per progetti più grandi ). Ad ogni modo, sembra essere abbastanza preciso per le mie esigenze. Spero che sia d'aiuto.
Se stai usando Angular UI Router , puoi ascoltare l' $viewContentLoaded
evento.
"$ viewContentLoaded - attivato una volta caricata la visualizzazione, dopo il rendering del DOM ." $ scope "della visualizzazione genera l'evento." - Link
$scope.$on('$viewContentLoaded',
function(event){ ... });
osservo la manipolazione DOM di angular con JQuery e ho impostato una finitura per la mia app (una sorta di situazione predefinita e soddisfacente di cui ho bisogno per il mio abstract app), ad esempio mi aspetto che il mio ng-repeater produca 7 risultati e lì per i imposterà una funzione di osservazione con l'aiuto di setInterval per questo scopo.
$(document).ready(function(){
var interval = setInterval(function(){
if($("article").size() == 7){
myFunction();
clearInterval(interval);
}
},50);
});
Se non usi il modulo ngRoute , cioè non hai l' evento $ viewContentLoaded .
Puoi usare un altro metodo di direttiva:
angular.module('someModule')
.directive('someDirective', someDirective);
someDirective.$inject = ['$rootScope', '$timeout']; //Inject services
function someDirective($rootScope, $timeout){
return {
restrict: "A",
priority: Number.MIN_SAFE_INTEGER, //Lowest priority
link : function(scope, element, attr){
$timeout(
function(){
$rootScope.$emit("Some:event");
}
);
}
};
}
Di conseguenza, alla risposta di trusktr ha la priorità più bassa. Inoltre $ timeout farà sì che Angular esegua un intero ciclo di eventi prima dell'esecuzione del callback.
$ rootScope utilizzato, perché consente di posizionare la direttiva in qualsiasi ambito dell'applicazione e notificare solo i listener necessari.
$ rootScope. $ emit attiverà un evento per tutti i $ rootScope. $ solo sui listener. La parte interessante è che $ rootScope. $ Broadcast notificherà a tutti $ rootScope. $ On così come $ scope. $ On listener Fonte
Secondo il team di Angular e questo problema di Github :
ora abbiamo gli eventi $ viewContentLoaded e $ includeContentLoaded che vengono emessi rispettivamente in ng-view e ng-include. Penso che questo sia il più vicino possibile a sapere quando abbiamo finito con la compilation.
Sulla base di ciò, sembra che al momento non sia possibile farlo in modo affidabile, altrimenti Angular avrebbe fornito l'evento fuori dagli schemi.
Il bootstrap dell'app implica l'esecuzione del ciclo digest sull'ambito radice e inoltre non è presente un evento di ciclo digest completato.
Secondo i documenti di progettazione di Angular 2 :
A causa di più digest, è impossibile determinare e notificare al componente che il modello è stabile. Questo perché la notifica può modificare ulteriormente i dati, il che può riavviare il processo di associazione.
Secondo questo, il fatto che ciò non sia possibile è uno dei motivi per cui è stata presa la decisione di riscrivere in Angular 2.
Avevo un frammento che veniva caricato dopo / dal parziale principale arrivato tramite il routing.
Avevo bisogno di eseguire una funzione dopo che quel sottoparziale è stato caricato e non volevo scrivere una nuova direttiva e ho capito che potresti usare un ngIf
Titolare del genitore parziale:
$scope.subIsLoaded = function() { /*do stuff*/; return true; };
HTML di sottoparziale
<element ng-if="subIsLoaded()"><!-- more html --></element>
Se desideri generare JS con dati lato server (JSP, PHP) puoi aggiungere la tua logica a un servizio, che verrà caricato automaticamente quando il controller viene caricato.
Inoltre, se si desidera reagire quando tutte le direttive sono state compilate / collegate, è possibile aggiungere le soluzioni proposte appropriate sopra nella logica di inizializzazione.
module.factory('YourControllerInitService', function() {
// add your initialization logic here
// return empty service, because it will not be used
return {};
});
module.controller('YourController', function (YourControllerInitService) {
});
Queste sono tutte ottime soluzioni, tuttavia, se stai attualmente utilizzando Routing, ho trovato questa soluzione la più semplice e la minima quantità di codice necessaria. Utilizzo della proprietà "risoluzione" per attendere il completamento di una promessa prima di attivare il percorso. per esempio
$routeProvider
.when("/news", {
templateUrl: "newsView.html",
controller: "newsController",
resolve: {
message: function(messageService){
return messageService.getMessage();
}
}
})
Fare clic qui per la documentazione completa - Ringraziamo K. Scott Allen
forse posso aiutarti con questo esempio
Nella fancybox personalizzata mostro i contenuti con valori interpolati.
nel servizio, nel metodo fancybox "aperto", lo faccio
open: function(html, $compile) {
var el = angular.element(html);
var compiledEl = $compile(el);
$.fancybox.open(el);
}
$ compile restituisce dati compilati. puoi controllare i dati compilati