Bagaimana saya bisa menjalankan direktif setelah dom selesai rendering?

115

Saya punya masalah yang tampaknya sederhana tanpa solusi yang jelas (dengan membaca dokumen Angular JS) .

Saya mendapat arahan Angular JS yang melakukan beberapa kalkulasi berdasarkan tinggi elemen DOM lainnya untuk menentukan ketinggian wadah di DOM.

Sesuatu yang mirip dengan ini terjadi di dalam arahan:

return function(scope, element, attrs) {
    $('.main').height( $('.site-header').height() -  $('.site-footer').height() );
}

Masalahnya adalah ketika arahan berjalan, $('site-header')tidak dapat ditemukan, mengembalikan array kosong sebagai ganti elemen DOM yang dibungkus jQuery yang saya butuhkan.

Apakah ada callback yang dapat saya gunakan dalam direktif saya yang hanya berjalan setelah DOM dimuat dan saya dapat mengakses elemen DOM lainnya melalui kueri gaya pemilih jQuery biasa?

Jannis
sumber
1
Anda bisa menggunakan scope. $ On () dan scope. $ Emit () untuk menggunakan kejadian khusus. Tidak yakin apakah ini pendekatan yang benar / direkomendasikan.
Tosh

Jawaban:

137

Itu tergantung pada bagaimana $ ('site-header') Anda dibuat.

Anda dapat mencoba menggunakan $ timeout dengan 0 penundaan. Sesuatu seperti:

return function(scope, element, attrs) {
    $timeout(function(){
        $('.main').height( $('.site-header').height() -  $('.site-footer').height() );
    });        
}

Penjelasan cara kerjanya: satu , dua .

Jangan lupa untuk memasukkan $timeoutarahan Anda:

.directive('sticky', function($timeout)
Artem Andreev
sumber
5
Terima kasih, saya mencoba membuat ini bekerja selama berabad-abad sampai saya menyadari bahwa saya belum $timeoutmemenuhi arahan. Doh. Semuanya bekerja sekarang, tepuk tangan.
Jannis
5
Ya, Anda harus meneruskan $timeoutke arahan seperti ini:.directive('sticky', function($timeout) { return function (scope, element, attrs, controller) { $timeout(function(){ }); }); };
Vladimir Starkov
19
Penjelasan Anda yang ditautkan menjelaskan mengapa trik batas waktu berfungsi di JavaScript, tetapi tidak dalam konteks AngularJS. Dari dokumentasi resmi : " [...] 4. Antrean $ evalAsync digunakan untuk menjadwalkan pekerjaan yang perlu dilakukan di luar bingkai tumpukan saat ini, tetapi sebelum render tampilan browser. Ini biasanya dilakukan dengan setTimeout (0) , tetapi pendekatan setTimeout (0) mengalami kelambatan dan dapat menyebabkan tampilan berkedip karena browser merender tampilan setelah setiap peristiwa. [...] "(penekanan saya)
Alberto
12
Saya menghadapi masalah yang sama dan menemukan bahwa saya membutuhkan sekitar 300ms untuk memungkinkan DOM memuat sebelum menjalankan arahan saya. Saya benar-benar tidak suka memasukkan angka yang tampaknya sewenang-wenang seperti itu. Saya yakin kecepatan memuat DOM akan bervariasi tergantung pada pengguna. Jadi bagaimana saya bisa yakin 300ms akan bekerja untuk siapa saja yang menggunakan aplikasi saya?
keepitreal
5
tidak terlalu senang dengan jawaban ini .. meskipun tampaknya menjawab pertanyaan OP .. ini sangat spesifik untuk kasus mereka dan relevansinya dengan bentuk masalah yang lebih umum (yaitu menjalankan perintah setelah dom dimuat) tidak jelas + itu terlalu hacky .. tidak ada yang spesifik tentang sudut sama sekali
abbood
44

Inilah cara saya melakukannya:

app.directive('example', function() {

    return function(scope, element, attrs) {
        angular.element(document).ready(function() {
                //MANIPULATE THE DOM
        });
    };

});
rjm226
sumber
1
Bahkan tidak perlu angular.element karena elemen sudah tersedia di sana:element.ready(function(){
timhc22
1
Elemen @ timhc22 adalah referensi ke DOMElement yang memicu direktif, rekomendasi Anda tidak akan menghasilkan referensi DOMElement ke objek Dokumen halaman.
tobius
itu tidak bekerja dengan baik. Saya mendapatkan offsetWidth = 0 melalui pendekatan ini
Alexey Sh.
37

Mungkin penulis tidak membutuhkan jawaban saya lagi. Namun, demi kelengkapan, saya rasa pengguna lain mungkin menganggapnya berguna. Solusi terbaik dan paling sederhana adalah menggunakan $(window).load()di dalam tubuh fungsi yang dikembalikan. (Alternatifnya, Anda dapat menggunakan document.ready. Itu sangat tergantung apakah Anda membutuhkan semua gambar atau tidak).

Menggunakan $timeoutmenurut pendapat saya adalah opsi yang sangat lemah dan mungkin gagal dalam beberapa kasus.

Ini kode lengkap yang akan saya gunakan:

.directive('directiveExample', function(){
   return {
       restrict: 'A',
       link: function($scope, $elem, attrs){

           $(window).load(function() {
               //...JS here...
           });
       }
   }
});
jnardiello.dll
sumber
1
Dapatkah Anda menjelaskan mengapa "mungkin gagal dalam beberapa kasus"? Kasus apa yang Anda maksud?
rryter
6
Anda berasumsi jQuery tersedia di sini.
Jonathan Cremin
3
@JonathanCremin jQuery memilih adalah masalah yang dihadapi sesuai OP
Nick Devereaux
1
Ini berfungsi dengan baik namun jika ada posting yang membangun item baru dengan direktif maka pemuatan jendela tidak akan aktif setelah pemuatan awal dan oleh karena itu tidak akan berfungsi dengan benar.
Brian Scott
@ BrianScott - Saya menggunakan kombinasi $ (window) .load untuk rendering halaman awal (kasus penggunaan saya menunggu file font yang disematkan) dan kemudian element.ready untuk menangani peralihan tampilan.
aaaaaa
8

ada ngcontentloadedacara, saya pikir Anda bisa menggunakannya

.directive('directiveExample', function(){
   return {
       restrict: 'A',
       link: function(scope, elem, attrs){

                $$window = $ $window


                init = function(){
                    contentHeight = elem.outerHeight()
                    //do the things
                }

                $$window.on('ngcontentloaded',init)

       }
   }
});
sunderls
sumber
21
Bisakah Anda menjelaskan apa yang $ $windowsedang dilakukannya?
Catfish
2
Sepertinya beberapa coffeescript, mungkin itu dimaksudkan untuk menjadi $ ($ window) dan $ window disuntikkan ke dalam direktif
mdob
5

Jika Anda tidak dapat menggunakan $ timeout karena sumber daya eksternal dan tidak dapat menggunakan perintah karena masalah waktu tertentu, gunakan siaran.

Tambahkan $scope.$broadcast("variable_name_here");setelah sumber daya eksternal yang diinginkan atau pengontrol / direktif yang berjalan lama selesai.

Kemudian tambahkan di bawah ini setelah sumber daya eksternal Anda dimuat.

$scope.$on("variable_name_here", function(){ 
   // DOM manipulation here
   jQuery('selector').height(); 
}

Misalnya dalam janji permintaan HTTP yang ditangguhkan.

MyHttpService.then(function(data){
   $scope.MyHttpReturnedImage = data.image;
   $scope.$broadcast("imageLoaded");
});

$scope.$on("imageLoaded", function(){ 
   jQuery('img').height(80).width(80); 
}
JSV
sumber
2
Ini tidak akan menyelesaikan masalah, karena data yang dimuat tidak berarti, bahwa mereka sudah dirender di DOM, bahkan jika mereka berada dalam variabel cakupan yang tepat yang terikat ke elemen DOM. Ada rentang waktu antara saat mereka dimuat di scope dan output yang diberikan di dom.
René Stalder
1

Saya memiliki masalah serupa dan ingin membagikan solusi saya di sini.

Saya memiliki HTML berikut:

<div data-my-directive>
  <div id='sub' ng-include='includedFile.htm'></div>
</div>

Masalah: Dalam fungsi tautan direktif div induk saya ingin membuat kueri div # sub anak. Tapi itu hanya memberi saya objek kosong karena ng-include belum selesai ketika fungsi tautan direktif dijalankan. Jadi pertama-tama saya membuat solusi kotor dengan $ timeout, yang berfungsi tetapi parameter penundaan bergantung pada kecepatan klien (tidak ada yang suka itu).

Bekerja tapi kotor:

app.directive('myDirective', [function () {
    var directive = {};
    directive.link = function (scope, element, attrs) {
        $timeout(function() {
            //very dirty cause of client-depending varying delay time 
            $('#sub').css(/*whatever*/);
        }, 350);
    };
    return directive;
}]);

Inilah solusi bersihnya:

app.directive('myDirective', [function () {
    var directive = {};
    directive.link = function (scope, element, attrs) {
        scope.$on('$includeContentLoaded', function() {
            //just happens in the moment when ng-included finished
            $('#sub').css(/*whatever*/);
        };
    };
    return directive;
}]);

Mungkin itu membantu seseorang.

jaheraho
sumber