JS yönergesi için bir post render geri arama var mı?


139

Ben sadece bu gibi eleman eklemek için bir şablon çekme yönergesi aldım:

# 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>

Ayrıca DataTables adlı bir jQuery Eklentisi kullanıyorum. Genel kullanımı şöyledir: $ ('table # some_id'). DataTable (). Tablo verilerini sağlamak için JSON verileri dataTable () çağrısına iletebilirsiniz VEYA zaten sayfada veri olabilir ve gerisini yapacak .. Ben zaten HTML sayfasında satırları olan ikincisini yapıyorum .

Ama sorun DOM # sonra tablo # line_items dataTable () çağırmak zorunda olmasıdır. Yukarıdaki yönergem, şablon yönergenin öğesine eklenmeden ÖNCE dataTable () yöntemini çağırır. Ekten SONRA işlevleri çağırabileceğim bir yol var mı?

Yardımın için teşekkürler!

Andy'nin cevabından sonra GÜNCELLEME 1:

Ben küçük bir test için direktif değiştirdim bu yüzden link yöntemi sadece sayfadaki her şeyden SONRA çağrıldığından emin olmak istiyorum:

# 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>"

    }

Ve gerçekten div # sayboo "boo" görüyorum.

Sonra jquery datatable çağrı denemek

.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') %>"
    }

Orada şans yok

Sonra bir mola eklemeyi deneyin:

.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') %>"
    }

Ve bu işe yarıyor. Kodun zamanlayıcı olmayan sürümünde neyin yanlış gittiğini merak ediyorum?


1
@adardesign Hayır, hiç yapmadım, bir zamanlayıcı kullanmak zorunda kaldım. Nedense, geri arama burada gerçekten bir geri arama değildir. Ben 11 sütun ve 100 satır içeren bir tablo var, bu yüzden doğal açısal veri bağlama için kullanmak için iyi bir bahis gibi görünüyor; ama aynı zamanda $ ('table'). datatable () kadar basit olan jquery Datatables eklentisini kullanmam gerekiyor. Direktif kullanarak veya sadece tüm satırları ile aptal bir json nesnesi var ve yineleme için ng-repeat kullanın, tablo html öğesi işlendikten SONRA çalıştırmak için $ (). Datatable () elde edemiyorum, bu yüzden benim hile şu anda zamanlayıcı $ ('tr') uzunluğunu> 3 (üstbilgi / altbilgi b / c)
Nik So

2
@adardesign Ve evet, tüm derleme yöntemini denedim, postLink / preLink yöntemlerini içeren bir nesneyi döndüren derleme yöntemi, sadece bir işlevi döndüren derleme yöntemi (yani bağlantı işlevi), bağlama yöntemi (derleme yöntemi olmadan çünkü söyleyebildiğim kadarıyla, bir bağlama yöntemi döndüren bir derleme yöntemi varsa, bağlama işlevi yok sayılır) .. Hiçbiri çok iyi eski $ zaman aşımı güvenmek zorunda çalıştı. Daha iyi çalışan bir şey bulduğumda veya geri aramanın gerçekten geri arama gibi davrandığını bulduğumda bu yayını güncelleyecek
Nik So

Yanıtlar:


215

İkinci "delay" parametresi sağlanmazsa, varsayılan davranış, DOM oluşturmayı tamamladıktan sonra işlevi yürütmektir. Dolayısıyla setTimeout yerine $ timeout kullanın:

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

8
Neden dokümanlarda açıklanmıyor ?
Gaui

23
Haklısın, cevabım biraz yanıltıcı çünkü basitleştirmeye çalıştım. Tam cevap, bu etkinin Açısal değil, tarayıcının bir sonucudur. $timeout(fn)nihayetinde setTimeout(fn, 0), Javascript yürütmesini kesintiye uğratan ve tarayıcının o Javascript'in yürütülmesine devam etmeden önce içeriği oluşturmasına izin veren aramalar yapar .
parlamento

7
Tarayıcıyı "javascript yürütme" ve "DOM oluşturma" gibi belirli görevleri ayrı ayrı kuyruk olarak düşünün ve hangi setTimeout'un (fn, 0) şu anda çalışan "javascript yürütmesini" oluşturduktan sonra kuyruğun arkasına ittiğini düşünün .
parlamento

2
$ Timeout dışında aynı etkiye sahip olacak @GabLeRoux yup, $ scope. $ Application () çağrıldıktan sonra ek bir fayda sağlar. _.Defer () ile, myFunction kapsamdaki değişkenleri değiştirirse manuel olarak çağırmanız gerekir.
parlamento

2
Bu sayfa1 ng-repeat elemanlar demet oluşturur nerede yardımcı olmaz bir senaryo yaşıyorum, sonra ben page2 gidin ve sonra page1 sayfasına geri dönün ve ng-repeat elemanları üst yüksek almaya çalışın ... yanlış yüksekliği döndürür. 1000 ms gibi bir zaman aşımı yaparsam çalışır.
yodalr

14

Aynı problemi yaşadım ve cevabın gerçekten hayır olduğuna inanıyorum. Bkz Misko yorumuna ve bazı grubunda tartışma .

Açısal, DOM'yi manipüle etmek için yaptığı tüm işlev çağrılarının tamamlandığını izleyebilir, ancak bu işlevler döndükten sonra DOM'u güncelleyen asenkron mantığı tetikleyebildiğinden, Angular'ın bunu bilmesi beklenemezdi. Eğik verir Herhangi geri arama olabilir bazen çalışır, ancak güvenmek güvenli olmaz.

Bunu sizin gibi sezgisel olarak bir setTimeout ile çözdük.

(Lütfen herkesin benimle aynı fikirde olmadığını unutmayın - yukarıdaki bağlantılardaki yorumları okumalı ve ne düşündüğünüzü görmelisiniz.)


7

Şablon yerleştirildikten sonra çalışan postLink olarak da bilinen 'link' işlevini kullanabilirsiniz.

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

Direktifler oluşturmayı planlıyorsanız, bunu okuyun, büyük bir yardım: http://docs.angularjs.org/guide/directive


Merhaba Andy, cevapladığınız için çok teşekkür ederim; Bağlantı işlevini denediniz mi ama tam olarak kodladığınız gibi tekrar denemeye aldım; Son 1,5 günü bu direktif sayfasını okuyarak geçirdim; ve açısal sitedeki örneklere de bakıyoruz. Şimdi kodunuzu deneyeceğim.
Nik So

Ah, şimdi görüyorum ki bağlantı kurmaya çalışıyorsun ama yanlış yapıyordun. Bir işlevi geri döndürürseniz, bağlantı olduğu varsayılır. Bir nesneyi döndürürseniz, nesneyi anahtarla 'link' olarak döndürmeniz gerekir. Derleme işlevinizden bir bağlantı işlevi de döndürebilirsiniz.
Andrew Joslin

Merhaba Andy, sonuçlarımı geri aldım; Akıl sağlığımı neredeyse kaybettim, çünkü burada cevabınızı gerçekten temel olarak yaptım. Lütfen güncellememe bakın
Nik So

Humm, şöyle bir şey deneyin: <table id = "bob"> <tbody dashboard-table = "# bob"> </tbody> </table> Ardından bağlantınızda $ (attrs.dashboardTable) .dataTable () doğru seçildiğinden emin olun. Ya da sanırım zaten denediniz .. Bağlantının çalışıp çalışmadığından gerçekten emin değilim.
Andrew Joslin

Bu benim için çalıştı, bağlantı gereksinimi için şablonun dom post render içinde öğeleri taşımak istedim, bağlantı fonksiyonunda
yaptı.Teşekkürler

7

Cevabım veri tabloları ile ilgili olmasa da, DOM manipülasyonu ve örneğin içerikleri zaman uyumsuz olarak güncellenen elemanlarda kullanılan direktifler için jQuery eklentisi başlatma sorununu ele alıyor.

Zaman aşımı uygulamak yerine, içerik değişikliklerini (veya ek harici tetikleyicileri) dinleyecek bir saat eklenebilir.

Benim durumumda, iç DOM'mizi oluşturan ng tekrarı yapıldıktan sonra bir jQuery eklentisini başlatmak için bu geçici çözümü kullandım - başka bir durumda, bu, kapsam özelliği denetleyicide değiştirildikten sonra DOM'yi değiştirmek için kullandım. İşte böyle yaptım ...

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 ...
                        }
                    });

                }
            }
        }
    }
}]);

Not: myContent değişkenini my-directive-watch özniteliğinde boole yapmak yerine, orada herhangi bir keyfi ifade düşünülebilir.

Not: Yukarıdaki örnekteki gibi kapsamı ayırmak, öğe başına yalnızca bir kez yapılabilir - bunu aynı öğede birden çok yönerge ile yapmaya çalışmak $ derlemesine neden olur: multidir Hata - bkz: https://docs.angularjs.org / hata / $ derleme / multidir


7

Bu soruya cevap vermek için geç olabilirim. Ama yine de birisi cevabımdan faydalanabilir.

Ben de benzer bir sorun vardı ve benim durumumda, bu bir kütüphane ve kütüphanenin kodunu değiştirmek iyi bir uygulama değildir çünkü direktif değiştiremiyorum. Yani yaptığım şey, sayfa yüklemesini beklemek için bir değişken kullanmak ve belirli öğeyi işlemek için benim html içinde ng-if kullanmaktı.

Kontrol cihazımda:

$scope.render=false;

//this will fire after load the the page

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

Html'de (benim durumumda html bileşeni bir tuvaldir)

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

3

Aynı sorunu vardı, ancak Angular + DataTable kullanarak fnDrawCallback+ satır gruplama + $ derlenmiş iç içe yönergeleri. Sayfa fnDrawCallbackgörüntüleme oluşturmayı düzeltmek için $ zaman aşımı işlevime yerleştirdim .

Örnekten önce, row_grouping kaynağına göre:

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
  }
}

Örnek sonra:

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
}

Angular'ın derlenmiş Angular direktiflerimi oluşturmasına izin vermek için kısa bir zaman aşımı gecikmesi bile yeterliydi.


Sadece merak ediyorum, çok sütunlu oldukça büyük bir masanız var mı? çünkü dataTable () boğulma çağrısına izin vermemek için can sıkıcı bir milisaniye (> 100) çok ihtiyacım olduğunu buldum
Nik So

Sorunun DataTable sayfa gezintisinde 2 satırdan 150 satıra kadar sonuç kümeleri için olduğunu gördüm . Yani, hayır - Ben tablonun boyutu sorun olduğunu sanmıyorum, ama belki de DataTable milisaniye uzakta bazı çiğnemek için yeterli render ek yükü ekledi. Odak noktam, minimum AngularJS entegrasyonu ile DataTable'da satır gruplandırmayı sağlamaktı.
JJ Zabkar

2

Benim için çalışan çözümlerin hiçbiri zaman aşımı kullanımını kabul etmiyor. Çünkü postLink sırasında dinamik olarak oluşturulan bir şablon kullanıyordum.

Ancak, zaman aşımı çağrılan işlevi zaten sırada olduğu için açısal oluşturma motorundan sonra gerçekleşecek olan tarayıcı kuyruğuna eklediğinden '0' zaman aşımı olabilir.

Şuna bakın: http://blog.brunoscopelliti.com/run-a-directive-after-the-dom-has-finished-rendering


0

İşte sığ bir render işleminden sonra eylemleri programlamak için bir direktif. Sığ derken, render edilen bu elementten sonra değerlendirilecek ve içeriği ne zaman render edileceği ile ilgisiz olacaktır . Bu nedenle, bir post render işlemi yapan bazı alt öğelere ihtiyacınız varsa, bunu orada kullanmayı düşünmelisiniz:

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;
    }]);
});

o zaman şunları yapabilirsiniz:

<div after-render></div>

veya aşağıdaki gibi herhangi bir yararlı ifade ile:

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


Bu, içerik oluşturulduktan sonra değil. Bu noktada <div after-render> {{blah}} </div> öğesi içinde bir ifadem olsaydı, ifade henüz değerlendirilmez. Div içeriği hala link fonksiyonunun içinde {{blah}}. Teknik olarak, içerik oluşturulmadan önce etkinliği tetiklersiniz.
Edward Olamisan

Bu, render işleminden sonra sığ, asla derin olduğunu iddia etmedim
Sebastian Sastre

0

Aşağıdaki yönerge ile bu çalışma var:

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

Ve HTML'de:

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

yukarıdaki sizin için işe yaramazsa çekim sorun.

1) "datatableSetup" ifadesinin "datatable-setup" ile eşdeğer olduğunu unutmayın. Açısal formatı deve durumuna dönüştürür.

2) uygulamanın direktiften önce tanımlandığından emin olun. örneğin, basit uygulama tanımı ve yönergesi.

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

0

Yükleme sırasının beklenemeyeceği gerçeğinin ardından basit bir çözüm kullanılabilir.

'Direktif kullanıcısı' ilişkisinin direktifine bakalım. Genellikle direktifin kullanıcısı direktife bazı veriler sağlar veya direktifin sağladığı bazı işlevleri (işlevleri) kullanır. Yönerge ise, bazı değişkenlerin kendi kapsamında tanımlanmasını beklemektedir.

Tüm oyuncuların bu eylemleri gerçekleştirmeye çalışmadan önce tüm eylem gereksinimlerinin karşılandığından emin olabilirsek - her şey iyi olmalıdır.

Ve şimdi direktif:

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;
            }
          }

        };
})

ve şimdi html direktifinin kullanıcısı

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

ve yönergeyi kullanan bileşenin denetleyicisinin bir yerinde:

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

Bu kadar. Çok fazla ek yük var ama $ zaman aşımını kaybedebilirsiniz. Ayrıca yönergeyi kullanan bileşenin yönergeden önce somutlaştırıldığını varsayıyoruz çünkü yönergeyi somutlaştırıldığında var olan kontrol değişkenine bağlıyız.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.