Jawaban:
Hanya firasat: mengapa tidak melihat bagaimana arahan ngCloak melakukannya? Jelas direktif ngCloak berhasil menampilkan konten setelah banyak hal dimuat. Saya yakin melihat ngCloak akan mengarah ke jawaban yang tepat ...
EDIT 1 jam kemudian: Ok, saya melihat ngCloak dan itu sangat singkat. Hal ini secara jelas menyiratkan bahwa fungsi kompilasi tidak akan dijalankan hingga ekspresi {{template}} telah dievaluasi (yaitu template yang dimuatnya), dengan demikian fungsionalitas yang bagus dari direktif ngCloak.
Tebakan saya yang terpelajar adalah hanya membuat arahan dengan kesederhanaan ngCloak yang sama, lalu dalam fungsi kompilasi Anda lakukan apa pun yang ingin Anda lakukan. :) Tempatkan direktif pada elemen root aplikasi Anda. Anda dapat memanggil direktif seperti myOnload dan menggunakannya sebagai atribut my-onload. Fungsi kompilasi akan dijalankan setelah template dikompilasi (ekspresi dievaluasi dan sub-template dimuat).
EDIT, 23 jam kemudian: Oke, jadi saya melakukan riset, dan saya juga mengajukan pertanyaan saya sendiri . Pertanyaan yang saya ajukan secara tidak langsung terkait dengan pertanyaan ini, tetapi secara kebetulan membawa saya ke jawaban yang memecahkan pertanyaan ini.
Jawabannya adalah Anda dapat membuat direktif sederhana dan meletakkan kode Anda di fungsi tautan direktif, yang (untuk sebagian besar kasus penggunaan, dijelaskan di bawah) akan berjalan ketika elemen Anda siap / dimuat. Berdasarkan deskripsi Josh tentang urutan di mana fungsi kompilasi dan tautan dijalankan ,
jika Anda memiliki markup ini:
<div directive1> <div directive2> <!-- ... --> </div> </div>
Kemudian AngularJS akan membuat direktif dengan menjalankan fungsi direktif dalam urutan tertentu:
directive1: compile directive2: compile directive1: controller directive1: pre-link directive2: controller directive2: pre-link directive2: post-link directive1: post-link
Secara default, fungsi "link" lurus adalah post-link, sehingga fungsi link outer directive1 Anda tidak akan berjalan hingga fungsi link inner directive2 dijalankan. Itulah mengapa kami mengatakan bahwa manipulasi DOM hanya aman dilakukan di tautan pos. Jadi terhadap pertanyaan awal, seharusnya tidak ada masalah mengakses html bagian dalam direktif anak dari fungsi tautan direktif luar, meskipun konten yang dimasukkan secara dinamis harus dikompilasi, seperti yang dikatakan di atas.
Dari sini kita dapat menyimpulkan bahwa kita dapat membuat arahan untuk mengeksekusi kode kita ketika semuanya sudah siap / dikompilasi / ditautkan / dimuat:
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.
}
};
}]);
Sekarang yang dapat Anda lakukan adalah meletakkan direktif ngElementReady ke elemen root aplikasi, dan console.log
akan aktif saat dimuat:
<body data-ng-app="MyApp" data-ng-element-ready="">
...
...
</body>
Sesederhana itu! Buat saja petunjuk sederhana dan gunakan itu. ;)
Anda selanjutnya dapat menyesuaikannya sehingga dapat mengeksekusi ekspresi (yaitu fungsi) dengan menambahkannya $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.
}
};
}]);
Kemudian Anda dapat menggunakannya di elemen apa pun:
<body data-ng-app="MyApp" data-ng-controller="BodyCtrl" data-ng-element-ready="bodyIsReady()">
...
<div data-ng-element-ready="divIsReady()">...<div>
</body>
Pastikan Anda memiliki fungsi Anda (misalnya bodyIsReady dan divIsReady) yang ditentukan dalam cakupan (di pengontrol) tempat elemen Anda berada.
Peringatan: Saya mengatakan ini akan berhasil untuk banyak kasus. Hati-hati saat menggunakan arahan tertentu seperti ngRepeat dan ngIf. Mereka membuat ruang lingkupnya sendiri, dan arahan Anda tidak boleh menyala. Misalnya jika Anda meletakkan direktif ngElementReady baru kami pada elemen yang juga memiliki ngIf, dan kondisi ngIf bernilai false, maka direktif ngElementReady kami tidak akan dimuat. Atau, misalnya, jika Anda meletakkan direktif ngElementReady baru kami pada elemen yang juga memiliki direktif ngInclude, direktif kami tidak akan dimuat jika template untuk ngInclude tidak ada. Anda dapat mengatasi beberapa masalah ini dengan memastikan Anda menyusun arahan daripada menempatkan semuanya pada elemen yang sama. Misalnya, dengan melakukan ini:
<div data-ng-element-ready="divIsReady()">
<div data-ng-include="non-existent-template.html"></div>
<div>
alih-alih ini:
<div data-ng-element-ready="divIsReady()" data-ng-include="non-existent-template.html"></div>
Direktif ngElementReady akan dikompilasi dalam contoh terakhir, tetapi fungsi tautannya tidak akan dijalankan. Catatan: direktif selalu dikompilasi, tetapi fungsi tautannya tidak selalu dijalankan tergantung pada skenario tertentu seperti di atas.
EDIT, beberapa menit kemudian:
Oh, dan untuk sepenuhnya menjawab pertanyaan itu, Anda sekarang dapat $emit
atau $broadcast
acara Anda dari ekspresi atau fungsi yang dijalankan di ng-element-ready
atribut. :) Misalnya:
<div data-ng-element-ready="$emit('someEvent')">
...
<div>
EDIT, bahkan beberapa menit kemudian:
Jawaban @ satchmorun juga berfungsi, tetapi hanya untuk pemuatan awal. Berikut adalah pertanyaan SO yang sangat berguna yang menjelaskan urutan hal-hal yang dijalankan termasuk fungsi tautan app.run
,, dan lainnya. Jadi, bergantung pada kasus penggunaan Anda, app.run
mungkin bagus, tetapi tidak untuk elemen tertentu, dalam hal ini fungsi tautan lebih baik.
EDIT, lima bulan kemudian, 17 Okt pukul 8:11 PST:
Ini tidak berfungsi dengan parsial yang dimuat secara asinkron. Anda harus menambahkan pembukuan ke dalam parsial Anda (misalnya salah satu cara adalah membuat setiap pelacakan parsial ketika isinya selesai memuat, lalu keluarkan acara sehingga cakupan induk dapat menghitung berapa banyak parsial yang telah dimuat dan akhirnya melakukan apa yang diperlukan lakukan setelah semua parsial dimuat).
EDIT, 23 Okt pukul 10:52 malam PST:
Saya membuat arahan sederhana untuk mengaktifkan beberapa kode saat gambar dimuat:
/*
* 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 Okt pukul 12:48 PST:
Saya meningkatkan ngElementReady
arahan asli saya dan menamainya kembali menjadi 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);
}
}
}
}]);
Misalnya, gunakan seperti ini untuk mengaktifkan someFunction
saat elemen dimuat dan {{placeholders}}
belum diganti:
<div when-ready="someFunction()">
<span ng-repeat="item in items">{{item.property}}</span>
</div>
someFunction
akan dipanggil sebelum semua item.property
placeholder diganti.
Evaluasi ekspresi sebanyak yang Anda inginkan, dan buat ekspresi terakhir true
menunggu untuk {{placeholders}}
dievaluasi seperti ini:
<div when-ready="someFunction(); anotherFunction(); true">
<span ng-repeat="item in items">{{item.property}}</span>
</div>
someFunction
dan anotherFunction
akan dipecat setelah {{placeholders}}
diganti.
Ini hanya berfungsi saat pertama kali elemen dimuat, bukan pada perubahan di masa mendatang. Ini mungkin tidak berfungsi seperti yang diinginkan jika $digest
terus terjadi setelah placeholder awalnya diganti (intisari $ dapat terjadi hingga 10 kali hingga data berhenti berubah). Ini akan cocok untuk sebagian besar kasus penggunaan.
EDIT, 31 Okt pukul 19:26 PST:
Baiklah, ini mungkin pembaruan terakhir dan terakhir saya. Ini mungkin akan berfungsi untuk 99,999 kasus penggunaan di luar sana:
/*
* 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);
}
}
};
}]);
Gunakan seperti ini
<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>
Tentu saja, ini mungkin dapat dioptimalkan, tetapi saya akan berhenti di situ. requestAnimationFrame bagus.
Di dokumen untukangular.Module
, ada entri yang menjelaskan run
fungsinya:
Gunakan metode ini untuk mendaftarkan pekerjaan yang harus dilakukan ketika injektor selesai memuat semua modul.
Jadi, jika Anda memiliki beberapa modul yang merupakan aplikasi Anda:
var app = angular.module('app', [/* module dependencies */]);
Anda dapat menjalankan sesuatu setelah modul dimuat dengan:
app.run(function() {
// Do post-load initialization stuff here
});
Jadi telah ditunjukkan bahwa run
tidak dipanggil ketika DOM sudah siap dan ditautkan. Ini dipanggil ketika $injector
untuk modul yang dirujuk oleh ng-app
telah memuat semua dependensinya, yang terpisah dari langkah kompilasi DOM.
Saya melihat lagi inisialisasi manual , dan tampaknya ini akan berhasil.
Saya telah membuat biola untuk diilustrasikan .
HTMLnya sederhana:
<html>
<body>
<test-directive>This is a test</test-directive>
</body>
</html>
Perhatikan kekurangan file ng-app
. Dan saya memiliki arahan yang akan melakukan beberapa manipulasi DOM, sehingga kami dapat memastikan urutan dan waktu segala sesuatunya.
Seperti biasa, modul dibuat:
var app = angular.module('app', []);
Dan inilah arahannya:
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"); }
};
}
};
});
Kami akan mengganti test-directive
tag dengan div
kelas test-directive
, dan membungkus isinya dalamh1
.
Saya telah menambahkan fungsi kompilasi yang mengembalikan fungsi tautan pra dan pasca sehingga kita dapat melihat kapan hal-hal ini berjalan.
Berikut kode lainnya:
// 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;
}
Sebelum kita melakukan apa pun, seharusnya tidak ada elemen dengan kelas test-directive
di DOM, dan setelah kita selesai harus ada 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'));
});
Ini sangat mudah. Jika dokumen sudah siap, panggilangular.bootstrap
dengan elemen root aplikasi Anda dan larik nama modul.
Faktanya, jika Anda melampirkan run
fungsi ke app
modul , Anda akan melihatnya dijalankan sebelum kompilasi apa pun dilakukan.
Jika Anda menjalankan biola dan menonton konsol, Anda akan melihat yang berikut ini:
before (should be 0): 0
Compiling test-directive
Prelink
Postlink
after (should be 1): 1 <--- success!
run
diaktifkan sebelum arahan dan ketika dijalankan, html tidak semuanya ada
$timeout( initMyPlugins,0)
karya dalam arahan saya, semua html yang saya butuhkan ada di sana
Angular belum menyediakan cara untuk memberi sinyal ketika halaman selesai dimuat, mungkin karena "selesai" bergantung pada aplikasi Anda . Misalnya, jika Anda memiliki pohon hierarki parsial, satu memuat yang lain. "Selesai" berarti semuanya telah dimuat. Kerangka kerja apa pun akan kesulitan menganalisis kode Anda dan memahami bahwa semuanya telah selesai, atau masih menunggu. Untuk itu, Anda harus menyediakan logika khusus aplikasi untuk memeriksa dan menentukannya.
Saya telah menemukan solusi yang relatif akurat dalam mengevaluasi ketika inisialisasi sudut selesai.
Arahannya adalah:
.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);
});
}
};
}]);
Itu kemudian dapat ditambahkan sebagai atribut ke body
elemen dan kemudian didengarkan untuk digunakan$scope.$on('initialised', fn)
Ia bekerja dengan mengasumsikan bahwa aplikasi dijalankan ketika tidak ada lagi siklus $ digest. $ watch dipanggil setiap siklus intisari dan timer dimulai (setTimeout bukan $ timeout sehingga siklus intisari baru tidak dipicu). Jika siklus intisari tidak terjadi dalam waktu tunggu maka aplikasi dianggap telah diinisialisasi.
Ini jelas tidak seakurat solusi satchmoruns (karena mungkin saja siklus intisari membutuhkan waktu lebih lama dari batas waktu) tetapi solusi saya tidak mengharuskan Anda untuk melacak modul yang membuatnya lebih mudah untuk dikelola (terutama untuk proyek yang lebih besar ). Bagaimanapun, tampaknya cukup akurat untuk kebutuhan saya. Semoga membantu.
Jika Anda menggunakan Angular UI Router , Anda dapat mendengarkan $viewContentLoaded
acara tersebut.
"$ viewContentLoaded - diaktifkan setelah tampilan dimuat, setelah DOM dirender . '$ scope' dari tampilan memancarkan acara." - Tautan
$scope.$on('$viewContentLoaded',
function(event){ ... });
saya mengamati manipulasi DOM sudut dengan JQuery dan saya memang menetapkan penyelesaian untuk aplikasi saya (semacam situasi yang telah ditentukan dan memuaskan yang saya perlukan untuk abstrak aplikasi saya) misalnya saya berharap ng-repeater saya menghasilkan 7 hasil dan di sana untuk saya akan mengatur fungsi observasi dengan bantuan setInterval untuk tujuan ini.
$(document).ready(function(){
var interval = setInterval(function(){
if($("article").size() == 7){
myFunction();
clearInterval(interval);
}
},50);
});
Jika Anda tidak menggunakan modul ngRoute , misalnya Anda tidak memiliki $ viewContentLoaded acara .
Anda dapat menggunakan metode direktif lain:
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");
}
);
}
};
}
Sesuai dengan jawaban trusktr itu memiliki prioritas terendah. Ditambah $ timeout akan menyebabkan Angular dijalankan melalui seluruh event loop sebelum eksekusi callback.
$ rootScope digunakan, karena memungkinkan untuk menempatkan direktif dalam lingkup aplikasi apa pun dan hanya memberi tahu pendengar yang diperlukan.
$ rootScope. $ emit akan mengaktifkan peristiwa untuk semua $ rootScope. $ hanya pada pendengar. Bagian yang menarik adalah bahwa $ rootScope. $ Broadcast akan memberi tahu semua $ rootScope. $ Pada serta $ scope. $ Pada Sumber pendengar
Menurut tim Angular dan masalah Github ini :
kita sekarang memiliki peristiwa $ viewContentLoaded dan $ includeContentLoaded yang masing-masing dipancarkan dalam ng-view dan ng-include. Saya pikir ini sedekat yang bisa diketahui orang ketika kita selesai dengan kompilasi.
Berdasarkan hal ini, tampaknya saat ini hal ini tidak mungkin dilakukan dengan cara yang andal, jika tidak Angular akan menyediakan acara tersebut di luar kotak.
Bootstrap aplikasi menyiratkan menjalankan siklus intisari pada cakupan root, dan juga tidak ada peristiwa siklus intisari yang selesai.
Menurut dokumen desain Angular 2 :
Karena multi digest, tidak mungkin untuk menentukan dan memberi tahu komponen bahwa modelnya stabil. Ini karena notifikasi selanjutnya dapat mengubah data, yang dapat memulai kembali proses pengikatan.
Menurut ini, fakta bahwa ini tidak mungkin adalah salah satu alasan mengapa keputusan diambil untuk menulis ulang di Angular 2.
Saya memiliki fragmen yang dimuat-in setelah / oleh sebagian utama yang masuk melalui perutean.
Saya perlu menjalankan fungsi setelah sub-parsial itu dimuat dan saya tidak ingin menulis arahan baru dan menemukan Anda bisa menggunakan cheeky ngIf
Pengontrol parsial induk:
$scope.subIsLoaded = function() { /*do stuff*/; return true; };
HTML subpihak
<element ng-if="subIsLoaded()"><!-- more html --></element>
Jika Anda ingin membuat JS dengan data sisi server (JSP, PHP), Anda dapat menambahkan logika Anda ke layanan, yang akan dimuat secara otomatis saat pengontrol Anda dimuat.
Selain itu, jika Anda ingin bereaksi ketika semua arahan selesai dikompilasi / menghubungkan, Anda dapat menambahkan solusi yang diusulkan sesuai di atas dalam logika inisialisasi.
module.factory('YourControllerInitService', function() {
// add your initialization logic here
// return empty service, because it will not be used
return {};
});
module.controller('YourController', function (YourControllerInitService) {
});
Ini semua adalah solusi hebat, Namun, jika Anda saat ini menggunakan Routing maka saya menemukan solusi ini menjadi yang termudah dan paling sedikit kode yang diperlukan. Menggunakan properti 'Resol' untuk menunggu janji diselesaikan sebelum memicu rute. misalnya
$routeProvider
.when("/news", {
templateUrl: "newsView.html",
controller: "newsController",
resolve: {
message: function(messageService){
return messageService.getMessage();
}
}
})
Klik di sini untuk dokumen lengkap - Penghargaan untuk K. Scott Allen
mungkin saya dapat membantu Anda dengan contoh ini
Di fancybox khusus saya menampilkan konten dengan nilai interpolasi.
dalam layanan, dalam metode fancybox "terbuka", saya lakukan
open: function(html, $compile) {
var el = angular.element(html);
var compiledEl = $compile(el);
$.fancybox.open(el);
}
$ compile mengembalikan data yang dikompilasi. Anda dapat memeriksa data yang dikompilasi