Ingin tahu apa cara terbaik untuk mendeteksi selesainya pemuatan / bootstrap halaman, ketika semua arahan selesai kompilasi / penautan.
Adakah acara yang sudah ada? Haruskah saya membebani fungsi bootstrap secara berlebihan?
Ingin tahu apa cara terbaik untuk mendeteksi selesainya pemuatan / bootstrap halaman, ketika semua arahan selesai kompilasi / penautan.
Adakah acara yang sudah ada? Haruskah saya membebani fungsi bootstrap secara berlebihan?
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 untuk
angular.Module
, ada entri yang menjelaskanrun
fungsinya:Jadi, jika Anda memiliki beberapa modul yang merupakan aplikasi Anda:
Anda dapat menjalankan sesuatu setelah modul dimuat dengan:
EDIT: Inisialisasi Manual untuk penyelamatan
Jadi telah ditunjukkan bahwa
run
tidak dipanggil ketika DOM sudah siap dan ditautkan. Ini dipanggil ketika$injector
untuk modul yang dirujuk olehng-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:
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:
Dan inilah arahannya:
Kami akan mengganti
test-directive
tag dengandiv
kelastest-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:
Sebelum kita melakukan apa pun, seharusnya tidak ada elemen dengan kelas
test-directive
di DOM, dan setelah kita selesai harus ada 1.Ini sangat mudah. Jika dokumen sudah siap, panggil
angular.bootstrap
dengan elemen root aplikasi Anda dan larik nama modul.Faktanya, jika Anda melampirkan
run
fungsi keapp
modul , Anda akan melihatnya dijalankan sebelum kompilasi apa pun dilakukan.Jika Anda menjalankan biola dan menonton konsol, Anda akan melihat yang berikut ini:
sumber
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 sanaAngular 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.
sumber
Saya telah menemukan solusi yang relatif akurat dalam mengevaluasi ketika inisialisasi sudut selesai.
Arahannya adalah:
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.
sumber
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
sumber
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.
sumber
Jika Anda tidak menggunakan modul ngRoute , misalnya Anda tidak memiliki $ viewContentLoaded acara .
Anda dapat menggunakan metode direktif lain:
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.
sumber
Menurut tim Angular dan masalah Github ini :
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 :
Menurut ini, fakta bahwa ini tidak mungkin adalah salah satu alasan mengapa keputusan diambil untuk menulis ulang di Angular 2.
sumber
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:
HTML subpihak
sumber
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.
sumber
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
})
Klik di sini untuk dokumen lengkap - Penghargaan untuk K. Scott Allen
sumber
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
$ compile mengembalikan data yang dikompilasi. Anda dapat memeriksa data yang dikompilasi
sumber