AngularJS: Bagaimana menjalankan kode tambahan setelah AngularJS telah membuat template?

182

Saya memiliki template Sudut di DOM. Ketika pengontrol saya mendapatkan data baru dari layanan, itu memperbarui model di $ scope, dan merender ulang templat. Semuanya baik sejauh ini.

Masalahnya adalah bahwa saya juga perlu melakukan beberapa pekerjaan tambahan setelah template telah dirender ulang dan berada di DOM (dalam hal ini plugin jQuery).

Sepertinya harus ada acara untuk didengarkan, seperti AfterRender, tetapi saya tidak dapat menemukan hal seperti itu. Mungkin arahan akan menjadi cara untuk pergi, tetapi sepertinya api terlalu dini juga.

Ini adalah jsFiddle yang menguraikan masalah saya: Fiddle-AngularIssue

== UPDATE ==

Berdasarkan komentar yang bermanfaat, saya telah beralih ke arahan untuk menangani manipulasi DOM, dan menerapkan arloji $ model di dalam arahan. Namun, saya masih memiliki masalah dasar yang sama; kode di dalam $ watch event diaktifkan sebelum templat dikompilasi dan dimasukkan ke dalam DOM, oleh karena itu, plugin jquery selalu mengevaluasi tabel kosong.

Menariknya, jika saya menghapus panggilan async semuanya bekerja dengan baik, jadi itu adalah langkah ke arah yang benar.

Ini Fiddle saya yang diperbarui untuk mencerminkan perubahan ini: http://jsfiddle.net/uNREn/12/

gorebash
sumber
Ini sepertinya mirip dengan pertanyaan yang saya miliki. stackoverflow.com/questions/11444494/… . Mungkin sesuatu di sana dapat membantu.
dnc253

Jawaban:

39

Posting ini sudah lama, tetapi saya mengubah kode Anda ke:

scope.$watch("assignments", function (value) {//I change here
  var val = value || null;            
  if (val)
    element.dataTable({"bDestroy": true});
  });
}

lihat jsfiddle .

Saya harap ini membantu Anda

dipold
sumber
Terima kasih, itu sangat membantu. Ini adalah solusi terbaik bagi saya karena tidak memerlukan manipulasi dom dari controller, dan akan tetap berfungsi ketika data diperbarui dari respons layanan yang benar-benar tidak sinkron (dan saya tidak perlu menggunakan $ evalAsync di controller saya).
gorebash
9
Apakah ini sudah usang? Ketika saya mencoba ini sebenarnya panggilan sebelum DOM diberikan. Apakah itu tergantung pada dom bayangan atau sesuatu?
Shayne
Saya relatif baru untuk Angular dan saya tidak bisa melihat bagaimana menerapkan jawaban ini dengan kasus khusus saya. Saya telah melihat berbagai jawaban dan menebak, tetapi itu tidak berhasil. Saya tidak yakin apa fungsi 'arloji' seharusnya menonton. Aplikasi sudut kami memiliki berbagai petunjuk berbeda, yang akan bervariasi dari halaman ke halaman, jadi bagaimana saya tahu apa yang harus 'ditonton'? Tentunya ada cara sederhana untuk menjalankan beberapa kode ketika halaman telah selesai dibuat?
moosefetcher
63

Pertama, tempat yang tepat untuk mengacaukan rendering adalah arahan. Saran saya adalah membungkus DOM memanipulasi plugin jQuery dengan arahan seperti ini.

Saya memiliki masalah yang sama dan muncul dengan potongan ini. Itu menggunakan $watchdan $evalAsyncuntuk memastikan kode Anda berjalan setelah arahan seperti ng-repeattelah diselesaikan dan template seperti {{ value }}diberikan.

app.directive('name', function() {
    return {
        link: function($scope, element, attrs) {
            // Trigger when number of children changes,
            // including by directives like ng-repeat
            var watch = $scope.$watch(function() {
                return element.children().length;
            }, function() {
                // Wait for templates to render
                $scope.$evalAsync(function() {
                    // Finally, directives are evaluated
                    // and templates are renderer here
                    var children = element.children();
                    console.log(children);
                });
            });
        },
    };
});

Semoga ini bisa membantu Anda mencegah beberapa perjuangan.

danijar
sumber
Ini mungkin menyatakan yang sudah jelas, tetapi jika ini tidak berhasil untuk Anda, periksa operasi asinkron di fungsi tautan / pengontrol Anda. Saya tidak berpikir $ evalAsync dapat memperhitungkannya.
LOAS
Perlu dicatat bahwa Anda juga dapat menggabungkan linkfungsi dengan a templateUrl.
Wtower
35

Mengikuti saran Misko, jika Anda ingin operasi async, maka alih-alih $ timeout () (yang tidak berfungsi)

$timeout(function () { $scope.assignmentsLoaded(data); }, 1000);

gunakan $ evalAsync () (yang berfungsi)

$scope.$evalAsync(function() { $scope.assignmentsLoaded(data); } );

Biola . Saya juga menambahkan tautan "hapus baris data" yang akan mengubah $ scope.assignments, mensimulasikan perubahan pada data / model - untuk menunjukkan bahwa mengubah data berfungsi.

Bagian Runtime dari halaman Tinjauan Konseptual menjelaskan bahwa evalAsync harus digunakan ketika Anda membutuhkan sesuatu terjadi di luar bingkai tumpukan saat ini, tetapi sebelum browser merender. (Tebak di sini ... "frame stack saat ini" mungkin termasuk pembaruan DOM Angular.) Gunakan $ timeout jika Anda memerlukan sesuatu untuk terjadi setelah browser merender.

Namun, seperti yang sudah Anda ketahui, saya rasa tidak perlu operasi async di sini.

Mark Rajcok
sumber
4
$scope.$evalAsync($scope.assignmentsLoaded(data));tidak masuk akal IMO. Argumen untuk $evalAsync()harus berupa fungsi. Dalam contoh Anda, Anda memanggil fungsi pada saat fungsi harus didaftarkan untuk dieksekusi nanti.
hgoebl
@ hgoebl, argumen ke $ evalAsync dapat berupa fungsi atau ekspresi. Dalam hal ini saya menggunakan ekspresi. Tapi Anda benar, ekspresi dieksekusi segera. Saya memodifikasi jawaban untuk membungkusnya dalam fungsi untuk eksekusi nanti. Terima kasih sudah menangkapnya.
Mark Rajcok
26

Saya telah menemukan solusi paling sederhana (murah dan ceria) adalah dengan menambahkan rentang kosong dengan ng-show = "someFunctionThatAlwaysReturnsZeroOrNothing ()" ke akhir elemen terakhir yang diberikan. Fungsi ini akan dijalankan kapan untuk memeriksa apakah elemen span harus ditampilkan. Jalankan kode lain dalam fungsi ini.

Saya menyadari ini bukan cara paling elegan untuk melakukan sesuatu, namun, itu berhasil untuk saya ...

Saya memiliki situasi yang serupa, meskipun sedikit terbalik di mana saya perlu menghapus indikator pemuatan saat animasi dimulai, pada perangkat seluler angular menginisialisasi jauh lebih cepat daripada animasi yang akan ditampilkan, dan menggunakan ng-jubah tidak cukup karena indikator memuat dihapus jauh sebelum data nyata ditampilkan. Dalam hal ini saya baru saja menambahkan fungsi kembali 0 saya ke elemen yang diberikan pertama, dan dalam fungsi itu membalik var yang menyembunyikan indikator memuat. (tentu saja saya menambahkan ng-hide ke indikator memuat yang dipicu oleh fungsi ini.

fuzion9
sumber
3
Ini adalah peretasan pasti tapi itu persis apa yang saya butuhkan. Memaksa kode saya untuk berjalan tepat setelah render (atau sangat dekat dengan persis). Di dunia yang ideal saya tidak akan melakukan ini tetapi kita di sini ...
Andrew
Saya membutuhkan layanan built in $anchorScrolluntuk dipanggil setelah DOM diberikan dan sepertinya tidak ada yang berhasil selain ini. Meretas tapi berhasil.
Pat Migliaccio
ng-init adalah alternatif yang lebih baik
Flying Gambit
1
ng-init mungkin tidak selalu berhasil. Sebagai contoh, saya memiliki ng-repeat yang menambahkan sejumlah gambar ke dom. Jika saya ingin memanggil fungsi setelah setiap gambar ditambahkan di ng-repeat saya harus menggunakan ng-show.
Michael Bremerkamp
7

Saya pikir Anda sedang mencari $ evalAsync http://docs.angularjs.org/api/ng.$rootScope.Scope#$evalAsync

Misko Hevery
sumber
2
Terima kasih atas komentarnya. Mungkin saya terlalu baru untuk Angular tetapi saya tidak melihat cara mengerjakan ini ke dalam kode saya. Apakah ada contoh yang bisa Anda tunjukkan kepada saya?
gorebash
4

Akhirnya saya menemukan solusinya, saya menggunakan layanan REST untuk memperbarui koleksi saya. Untuk mengonversi jquery yang dapat datatable adalah kode berikut:

$scope.$watchCollection( 'conferences', function( old, nuew ) {
        if( old === nuew ) return;
        $( '#dataTablex' ).dataTable().fnDestroy();
        $timeout(function () {
                $( '#dataTablex' ).dataTable();
        });
    });
Sergio Ceron
sumber
3

Saya harus sering melakukan ini. saya punya arahan dan perlu melakukan beberapa hal jquery setelah barang model sepenuhnya dimuat ke DOM. jadi saya meletakkan logika saya di tautan: fungsi direktif dan membungkus kode dalam setTimeout (function () {.....}, 1); setTimout akan diaktifkan setelah DOM dimuat dan 1 milidetik adalah jumlah waktu tersingkat setelah DOM dimuat sebelum kode dijalankan. ini tampaknya bekerja untuk saya tetapi saya berharap sudut mengangkat suatu peristiwa setelah template selesai memuat sehingga arahan yang digunakan oleh template itu bisa melakukan hal jquery dan mengakses elemen DOM. semoga ini membantu.

mgrimmenator
sumber
2

Anda juga dapat membuat arahan yang menjalankan kode Anda di fungsi tautan.

Lihat balasan stackoverflow itu .

Anthony O.
sumber
2

$ Scope. $ EvalAsync () atau $ timeout (fn, 0) tidak bekerja dengan baik untuk saya.

Saya harus menggabungkan keduanya. Saya membuat arahan dan juga menempatkan prioritas lebih tinggi dari nilai default untuk ukuran yang baik. Ini adalah arahan untuk itu (Catatan saya menggunakan ngInject untuk menyuntikkan dependensi):

app.directive('postrenderAction', postrenderAction);

/* @ngInject */
function postrenderAction($timeout) {
    // ### Directive Interface
    // Defines base properties for the directive.
    var directive = {
        restrict: 'A',
        priority: 101,
        link: link
    };
    return directive;

    // ### Link Function
    // Provides functionality for the directive during the DOM building/data binding stage.
    function link(scope, element, attrs) {
        $timeout(function() {
            scope.$evalAsync(attrs.postrenderAction);
        }, 0);
    }
}

Untuk memanggil arahan, Anda akan melakukan ini:

<div postrender-action="functionToRun()"></div>

Jika Anda ingin memanggilnya setelah ng-repeat selesai berjalan, saya menambahkan rentang kosong di ng-repeat dan ng-if = "$ last":

<li ng-repeat="item in list">
    <!-- Do stuff with list -->
    ...

    <!-- Fire function after the last element is rendered -->
    <span ng-if="$last" postrender-action="$ctrl.postRender()"></span>
</li>
Xchai
sumber
1

Dalam beberapa skenario di mana Anda memperbarui layanan dan mengarahkan ke tampilan (halaman) baru dan kemudian arahan Anda dimuat sebelum layanan Anda diperbarui maka Anda dapat menggunakan $ rootScope. $ Broadcast jika $ watch atau $ timeout Anda gagal

Melihat

<service-history log="log" data-ng-repeat="log in requiedData"></service-history>

Pengendali

app.controller("MyController",['$scope','$rootScope', function($scope, $rootScope) {

   $scope.$on('$viewContentLoaded', function () {
       SomeSerive.getHistory().then(function(data) {
           $scope.requiedData = data;
           $rootScope.$broadcast("history-updation");
       });
  });

}]);

Pengarahan

app.directive("serviceHistory", function() {
    return {
        restrict: 'E',
        replace: true,
        scope: {
           log: '='
        },
        link: function($scope, element, attrs) {
            function updateHistory() {
               if(log) {
                   //do something
               }
            }
            $rootScope.$on("history-updation", updateHistory);
        }
   };
});
Sudharshan
sumber
1

Saya datang dengan solusi yang cukup sederhana. Saya tidak yakin apakah itu cara yang benar untuk melakukannya tetapi itu bekerja secara praktis. Mari kita langsung menonton apa yang ingin kita render. Sebagai contoh dalam arahan yang mencakup beberapa ng-repeats, saya akan berhati-hati untuk panjang teks (Anda mungkin memiliki hal-hal lain!) Dari paragraf atau keseluruhan html. Arahannya akan seperti ini:

.directive('myDirective', [function () {
    'use strict';
    return {

        link: function (scope, element, attrs) {
            scope.$watch(function(){
               var whole_p_length = 0;
               var ps = element.find('p');
                for (var i=0;i<ps.length;i++){
                    if (ps[i].innerHTML == undefined){
                        continue
                    }
                    whole_p_length+= ps[i].innerHTML.length;
                }
                //it could be this too:  whole_p_length = element[0].innerHTML.length; but my test showed that the above method is a bit faster
                console.log(whole_p_length);
                return whole_p_length;
            }, function (value) {   
                //Code you want to be run after rendering changes
            });
        }
}]);

Perhatikan bahwa kode benar-benar berjalan setelah rendering perubahan rendering agak lengkap. Tapi saya kira dalam banyak kasus Anda dapat menangani situasi setiap kali rendering perubahan terjadi. Anda juga dapat mempertimbangkan membandingkan ppanjang ini (atau ukuran lain) dengan model Anda jika Anda ingin menjalankan kode Anda hanya sekali setelah rendering selesai. Saya menghargai setiap pemikiran / komentar tentang ini.

Ehsan88
sumber
0

Anda dapat menggunakan modul 'jQuery Passthrough' pada utular -ui utils . Saya berhasil mengikat plugin carousel touch jQuery ke beberapa gambar yang saya ambil async dari layanan web dan membuatnya dengan ng-repeat.

Michael Kork.
sumber