apakah ada panggilan balik pasca render untuk arahan JS Angular?


139

Saya baru saja mendapatkan arahan saya untuk menarik template untuk menambahkan elemennya seperti ini:

# CoffeeScript
.directive 'dashboardTable', ->
  controller: lineItemIndexCtrl
  templateUrl: "<%= asset_path('angular/templates/line_items/dashboard_rows.html') %>"
  (scope, element, attrs) ->
    element.parent('table#line_items').dataTable()
    console.log 'Just to make sure this is run'

# HTML
<table id="line_items">
    <tbody dashboard-table>
    </tbody>
</table>

Saya juga menggunakan Plugin jQuery yang disebut DataTables. Penggunaan umumnya adalah seperti ini: $ ('table # some_id'). DataTable (). Anda dapat mengirimkan data JSON ke panggilan dataTable () untuk menyediakan data tabel ATAU Anda dapat memiliki data pada halaman dan akan melakukan sisanya .. Saya melakukan yang terakhir, memiliki baris sudah di halaman HTML .

Tetapi masalahnya adalah bahwa saya harus memanggil dataTable () pada tabel # line_items SETELAH DOM siap. Arahan saya di atas memanggil metode dataTable () SEBELUM template ditambahkan ke elemen direktif. Apakah ada cara agar saya dapat memanggil fungsi SETELAH append?

Terima kasih untuk bantuannya!

PEMBARUAN 1 setelah jawaban Andy:

Saya ingin memastikan bahwa metode tautan tidak hanya dipanggil SETELAH semuanya ada di laman, jadi saya mengubah arahan untuk sedikit tes:

# CoffeeScript
#angular.module(...)
.directive 'dashboardTable', ->
    {
      link: (scope,element,attrs) -> 
        console.log 'Just to make sure this gets run'
        element.find('#sayboo').html('boo')

      controller: lineItemIndexCtrl
      template: "<div id='sayboo'></div>"

    }

Dan saya memang melihat "boo" di div # sayboo.

Kemudian saya mencoba panggilan datatable jquery saya

.directive 'dashboardTable',  ->
    {
      link: (scope,element,attrs) -> 
        console.log 'Just to make sure this gets run'
        element.parent('table').dataTable() # NEW LINE

      controller: lineItemIndexCtrl
      templateUrl: "<%= asset_path('angular/templates/line_items/dashboard_rows.html') %>"
    }

Tidak beruntung di sana

Lalu saya mencoba menambahkan batas waktu:

.directive 'dashboardTable', ($timeout) ->
    {
      link: (scope,element,attrs) -> 
        console.log 'Just to make sure this gets run'
        $timeout -> # NEW LINE
          element.parent('table').dataTable()
        ,5000
      controller: lineItemIndexCtrl
      templateUrl: "<%= asset_path('angular/templates/line_items/dashboard_rows.html') %>"
    }

Dan itu berhasil. Jadi saya bertanya-tanya apa yang salah dalam versi kode non-timer?


1
@adardesign Tidak, saya tidak pernah melakukannya, saya harus menggunakan timer. Untuk beberapa alasan, panggilan balik sebenarnya bukan panggilan balik di sini. Saya punya tabel dengan 11 kolom dan 100-an baris, jadi secara alami sudut tampak seperti taruhan yang baik untuk digunakan untuk pengikatan data; tapi saya juga perlu menggunakan plugin jquery Datatables yang sesederhana $ ('table'). datatable (). Menggunakan direktif atau hanya memiliki objek json bodoh dengan semua baris dan menggunakan ng-repeat untuk iterate, saya tidak bisa mendapatkan $ (). Datatable () untuk menjalankan SETELAH elemen tabel html diberikan, jadi trik saya saat ini adalah untuk timer untuk memeriksa apakah $ ('tr'). Panjang> 3 (b / c dari header / footer)
Nik So

2
@adardesign Dan ya, saya mencoba semua metode kompilasi, kompilasi metode mengembalikan objek yang berisi metode postLink / preLink, metode kompilasi mengembalikan hanya fungsi (yaitu fungsi penghubung), metode penghubung (tanpa metode kompilasi karena sejauh yang saya tahu, jika Anda memiliki metode kompilasi yang mengembalikan metode penautan, fungsi penautan diabaikan) .. Tidak ada yang berhasil sehingga harus bergantung pada waktu tunggu $ lama yang baik. Akan memperbarui posting ini jika saya menemukan sesuatu yang berfungsi lebih baik atau hanya ketika saya menemukan bahwa panggilan balik benar-benar bertindak seperti panggilan balik
Nik So

Jawaban:


215

Jika parameter kedua, "delay" tidak disediakan, perilaku default adalah menjalankan fungsi setelah DOM menyelesaikan rendering. Jadi alih-alih setTimeout, gunakan $ timeout:

$timeout(function () {
    //DOM has finished rendering
});

8
Mengapa itu tidak dijelaskan dalam dokumen ?
Gaui

23
Anda benar, jawaban saya agak menyesatkan karena saya mencoba membuatnya sederhana. Jawaban lengkapnya adalah bahwa efek ini bukan hasil Angular melainkan browser. $timeout(fn)akhirnya panggilan setTimeout(fn, 0)yang memiliki efek mengganggu eksekusi Javascript dan memungkinkan browser untuk merender konten terlebih dahulu, sebelum melanjutkan eksekusi Javascript itu.
parlemen

7
Anggap browser sebagai antrian tugas-tugas tertentu seperti "eksekusi javascript" dan "rendering DOM" secara terpisah, dan apa yang disetTimeout (fn, 0) yang membuatnya mendorong "eksekusi javascript" yang sedang berjalan ke bagian belakang antrian, setelah rendering .
parlemen

2
@GabLeRoux yup, itu akan memiliki efek yang sama kecuali $ timeout memiliki manfaat tambahan memanggil $ scope. $ Apply () setelah berjalan. Dengan _.defer () Anda harus memanggilnya secara manual jika fungsi saya mengubah variabel pada ruang lingkup.
parlemen

2
Saya mengalami skenario di mana ini tidak membantu di mana pada page1 ng-repeat membuat banyak elemen, lalu saya pergi ke page2 dan kemudian saya kembali ke page1 dan saya mencoba untuk mendapatkan yang tinggi dari elemen ng-repeat ... mengembalikan ketinggian yang salah. Jika saya melakukan timeout seperti 1000ms, maka itu berfungsi.
yodalr

14

Saya memiliki masalah yang sama dan saya yakin jawabannya adalah tidak. Lihat komentar Miško dan beberapa diskusi dalam grup .

Angular dapat melacak bahwa semua fungsi yang dipanggil untuk memanipulasi DOM selesai, tetapi karena fungsi-fungsi itu dapat memicu logika async yang masih memperbarui DOM setelah mereka kembali, Angular tidak dapat diharapkan untuk mengetahuinya. Setiap panggilan balik yang diberikan Angular mungkin bekerja kadang-kadang, tetapi tidak akan aman untuk diandalkan.

Kami menyelesaikan ini secara heuristik dengan setTimeout, seperti yang Anda lakukan.

(Harap diingat bahwa tidak semua orang setuju dengan saya - Anda harus membaca komentar di tautan di atas dan melihat pendapat Anda.)


7

Anda dapat menggunakan fungsi 'tautan', juga dikenal sebagai postLink, yang berjalan setelah templat dimasukkan.

app.directive('myDirective', function() {
  return {
    link: function(scope, elm, attrs) { /*I run after template is put in */ },
    template: '<b>Hello</b>'
  }
});

Berikan ini bacaan jika Anda berencana membuat arahan, ini sangat membantu: http://docs.angularjs.org/guide/directive


Hai Andy, terima kasih banyak telah menjawab; Saya memang mencoba fungsi tautan tetapi saya tidak keberatan mencobanya lagi persis seperti kode Anda; Saya menghabiskan 1,5 hari terakhir membaca di halaman arahan itu; dan melihat contoh-contoh di situs sudut juga. Akan mencoba kode Anda sekarang.
Nik So

Ah, saya mengerti sekarang Anda mencoba melakukan tautan tetapi Anda salah melakukannya. Jika Anda baru saja mengembalikan fungsi, itu diasumsikan sebagai tautan. Jika Anda mengembalikan objek, Anda harus mengembalikannya dengan kunci sebagai 'tautan'. Anda juga dapat mengembalikan fungsi penautan dari fungsi kompilasi Anda.
Andrew Joslin

Hai Andy, dapatkan hasil saya kembali; Saya hampir kehilangan kewarasan saya, karena pada dasarnya saya benar-benar melakukan apa jawaban Anda di sini. Silakan lihat pembaruan saya
Nik So

Humm, coba sesuatu seperti: <table id = "bob"> <tbody dashboard-table = "# bob"> </tbody> </table> Kemudian di tautan Anda, lakukan $ (attrs.dashboardTable) .dataTable () untuk pastikan itu dipilih dengan benar. Atau saya kira Anda sudah mencobanya .. Saya benar-benar tidak yakin apakah tautannya tidak berfungsi.
Andrew Joslin

Yang ini berfungsi untuk saya, saya ingin memindahkan elemen dalam rendering postingan dom untuk kebutuhan saya, melakukannya dalam fungsi tautan. Terima kasih
abhi

7

Meskipun jawaban saya tidak terkait dengan datatables itu membahas masalah manipulasi DOM dan misalnya inisialisasi plugin jQuery untuk arahan yang digunakan pada elemen yang kontennya diperbarui secara async.

Alih-alih menerapkan batas waktu, Anda bisa menambahkan arloji yang akan mendengarkan perubahan konten (atau bahkan pemicu eksternal tambahan).

Dalam kasus saya, saya menggunakan solusi ini untuk menginisialisasi plugin jQuery setelah ng-repeat dilakukan yang menciptakan DOM batin saya - dalam kasus lain saya menggunakannya untuk hanya memanipulasi DOM setelah properti lingkup diubah pada controller. Inilah yang saya lakukan ...

HTML:

<div my-directive my-directive-watch="!!myContent">{{myContent}}</div>

JS:

app.directive('myDirective', [ function(){
    return {
        restrict : 'A',
        scope : {
            myDirectiveWatch : '='
        },
        compile : function(){
            return {
                post : function(scope, element, attributes){

                    scope.$watch('myDirectiveWatch', function(newVal, oldVal){
                        if (newVal !== oldVal) {
                            // Do stuff ...
                        }
                    });

                }
            }
        }
    }
}]);

Catatan: Daripada hanya casting variabel myContent ke bool pada atribut my-directive-watch, kita bisa membayangkan ekspresi sewenang-wenang di sana.

Catatan: Mengisolasi ruang lingkup seperti pada contoh di atas hanya dapat dilakukan sekali per elemen - mencoba melakukan ini dengan banyak arahan pada elemen yang sama akan menghasilkan $ compile: multidir Error - lihat: https://docs.angularjs.org / error / $ compile / multidir


7

Mungkin saya terlambat menjawab pertanyaan ini. Tapi tetap saja seseorang bisa mendapat manfaat dari jawaban saya.

Saya memiliki masalah serupa dan dalam kasus saya, saya tidak dapat mengubah arahan karena, itu adalah perpustakaan dan mengubah kode perpustakaan bukan praktik yang baik. Jadi yang saya lakukan adalah menggunakan variabel untuk menunggu memuat halaman dan menggunakan ng-if di dalam html saya untuk menunggu render elemen tertentu.

Di controller saya:

$scope.render=false;

//this will fire after load the the page

angular.element(document).ready(function() {
    $scope.render=true;
});

Dalam html saya (dalam kasus saya komponen html adalah kanvas)

<canvas ng-if="render"> </canvas>

3

Saya memiliki masalah yang sama, tetapi menggunakan sudut + DataTable dengan fnDrawCallback+ baris pengelompokan + $ disusun arahan bersarang. Saya menempatkan $ timeout pada fnDrawCallbackfungsi saya untuk memperbaiki render pagination.

Sebelum contoh, berdasarkan sumber row_grouping:

var myDrawCallback = function myDrawCallbackFn(oSettings){
  var nTrs = $('table#result>tbody>tr');
  for(var i=0; i<nTrs.length; i++){
     //1. group rows per row_grouping example
     //2. $compile html templates to hook datatable into Angular lifecycle
  }
}

Setelah contoh:

var myDrawCallback = function myDrawCallbackFn(oSettings){
  var nTrs = $('table#result>tbody>tr');
  $timeout(function requiredRenderTimeoutDelay(){
    for(var i=0; i<nTrs.length; i++){
       //1. group rows per row_grouping example
       //2. $compile html templates to hook datatable into Angular lifecycle
    }
  ,50); //end $timeout
}

Bahkan penundaan waktu singkat sudah cukup untuk memungkinkan Angular memberikan arahan Angular terkompilasi saya.


Hanya ingin tahu, apakah Anda memiliki meja yang agak besar dengan banyak kolom? karena saya memang menemukan bahwa saya memerlukan banyak milidetik (> 100) yang mengganggu sehingga tidak membiarkan dataTable () dipanggil untuk tersedak
Nik So

Saya menemukan masalah terjadi pada navigasi halaman DataTable untuk kumpulan hasil dari 2 baris hingga lebih dari 150 baris. Jadi, tidak - saya tidak berpikir ukuran tabel adalah masalahnya, tapi mungkin DataTable menambahkan render overhead yang cukup untuk mengunyah beberapa milidetik itu. Fokus saya adalah membuat pengelompokan baris berfungsi di DataTable dengan integrasi AngularJS minimal.
JJ Zabkar


0

Berikut adalah arahan untuk memiliki tindakan yang diprogram setelah render yang dangkal. Maksud saya dangkal itu akan mengevaluasi setelah elemen yang diberikan dan yang tidak akan terkait ketika kontennya diberikan. Jadi jika Anda memerlukan beberapa sub elemen yang melakukan tindakan post render, Anda harus mempertimbangkan untuk menggunakannya di sana:

define(['angular'], function (angular) {
  'use strict';
  return angular.module('app.common.after-render', [])
    .directive('afterRender', [ '$timeout', function($timeout) {
    var def = {
        restrict : 'A', 
        terminal : true,
        transclude : false,
        link : function(scope, element, attrs) {
            if (attrs) { scope.$eval(attrs.afterRender) }
            scope.$emit('onAfterRender')
        }
    };
    return def;
    }]);
});

maka kamu bisa melakukan:

<div after-render></div>

atau dengan ungkapan yang bermanfaat seperti:

<div after-render="$emit='onAfterThisConcreteThingRendered'"></div>


Ini tidak benar-benar setelah konten diberikan. Jika saya memiliki ekspresi di dalam elemen <div after-render> {{blah}} </div> pada saat ini ekspresi belum dievaluasi. Isi div masih {{blah}} di dalam fungsi tautan. Jadi secara teknis Anda memecat acara sebelum konten diberikan.
Edward Olamisan

Ini adalah tindakan dangkal setelah render, saya tidak pernah mengakuinya mendalam
Sebastian Sastre

0

Saya mendapatkan ini bekerja dengan arahan berikut:

app.directive('datatableSetup', function () {
    return { link: function (scope, elm, attrs) { elm.dataTable(); } }
});

Dan dalam HTML:

<table class="table table-hover dataTable dataTable-columnfilter " datatable-setup="">

kesulitan memotret jika hal di atas tidak berhasil untuk Anda.

1) perhatikan bahwa 'datatableSetup' adalah setara dengan 'datatable-setup'. Angular mengubah format menjadi case unta.

2) pastikan aplikasi didefinisikan sebelum arahan. misalnya definisi dan arahan aplikasi sederhana.

var app = angular.module('app', []);
app.directive('datatableSetup', function () {
    return { link: function (scope, elm, attrs) { elm.dataTable(); } }
});

0

Mengikuti fakta bahwa urutan beban tidak dapat diantisipasi, solusi sederhana dapat digunakan.

Mari kita lihat hubungan directive-'user of directive '. Biasanya pengguna arahan akan memasok beberapa data ke arahan atau menggunakan beberapa fungsionalitas (fungsi) persediaan arahan. Arahan di sisi lain mengharapkan beberapa variabel akan didefinisikan pada ruang lingkupnya.

Jika kita dapat memastikan bahwa semua pemain telah memenuhi semua persyaratan tindakan mereka sebelum mereka mencoba melakukan tindakan tersebut - semuanya harus baik-baik saja.

Dan sekarang arahannya:

app.directive('aDirective', function () {
    return {
        scope: {
            input: '=',
            control: '='
        },
        link: function (scope, element) {
            function functionThatNeedsInput(){
                //use scope.input here
            }
            if ( scope.input){ //We already have input 
                functionThatNeedsInput();
            } else {
                scope.control.init = functionThatNeedsInput;
            }
          }

        };
})

dan sekarang pengguna direktif html

<a-directive control="control" input="input"></a-directive>

dan di suatu tempat di pengontrol komponen yang menggunakan arahan:

$scope.control = {};
...
$scope.input = 'some data could be async';
if ( $scope.control.functionThatNeedsInput){
    $scope.control.functionThatNeedsInput();
}

Itu saja. Ada banyak overhead tetapi Anda bisa kehilangan $ timeout. Kami juga mengasumsikan bahwa komponen yang menggunakan arahan adalah instantiated sebelum arahan karena kita bergantung pada variabel kontrol yang ada ketika direktif tersebut dipakai.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.