Tekrarlama bittiğinde bir işlevi çağırmak


249

Ne uygulamaya çalışıyorum temelde bir "on ng tekrar bitmiş işleme" işleyicisidir. Ne zaman yapıldığını tespit edebiliyorum ama ondan bir işlevi nasıl tetikleyeceğimi anlayamıyorum.

Kemanı kontrol edin: http://jsfiddle.net/paulocoelho/BsMqq/3/

JS

var module = angular.module('testApp', [])
    .directive('onFinishRender', function () {
    return {
        restrict: 'A',
        link: function (scope, element, attr) {
            if (scope.$last === true) {
                element.ready(function () {
                    console.log("calling:"+attr.onFinishRender);
                    // CALL TEST HERE!
                });
            }
        }
    }
});

function myC($scope) {
    $scope.ta = [1, 2, 3, 4, 5, 6];
    function test() {
        console.log("test executed");
    }
}

HTML

<div ng-app="testApp" ng-controller="myC">
    <p ng-repeat="t in ta" on-finish-render="test()">{{t}}</p>
</div>

Cevap : Finishingmove'dan çalışan keman: http://jsfiddle.net/paulocoelho/BsMqq/4/


Meraktan, element.ready()pasajın amacı nedir? Yani .. sahip olduğunuz bir çeşit jQuery eklentisi mi yoksa eleman hazır olduğunda tetiklenmeli mi?
gion_13

1
Biri ng-init gibi yerleşik yönergeleri kullanarak yapabilirdi
semiomant


stackoverflow.com/questions/13471129/… kopyası, orada cevabımı gör
bresleveloper

Yanıtlar:


400
var module = angular.module('testApp', [])
    .directive('onFinishRender', function ($timeout) {
    return {
        restrict: 'A',
        link: function (scope, element, attr) {
            if (scope.$last === true) {
                $timeout(function () {
                    scope.$emit(attr.onFinishRender);
                });
            }
        }
    }
});

Kullanmadım .ready()ama daha ziyade a $timeout. $timeoutng yinelenen öğelerin GERÇEKTEN oluşturma işlemini tamamladığında yürütüldüğünden emin olur (çünkü $timeoutmevcut özet döngüsünün sonunda yürütülür - ve bunun $applyaksine dahili olarak da çağıracaktırsetTimeout ). Bu yüzden bittikten sonra, dış kapsamlara (kardeş ve ebeveyn kapsamları) bir olay yayınlamak için ng-repeatkullanırız $emit.

Ve sonra kontrol cihazınızda şunları yakalayabilirsiniz $on:

$scope.$on('ngRepeatFinished', function(ngRepeatFinishedEvent) {
    //you also get the actual event object
    //do stuff, execute functions -- whatever...
});

Html ile böyle bir şey görünüyor:

<div ng-repeat="item in items" on-finish-render="ngRepeatFinished">
    <div>{{item.name}}}<div>
</div>

10
+1, ancak bir etkinlik kullanmadan önce $ eval kullanacağım. Daha fazla ayrıntı için cevabıma bakın.
Mark Rajcok

1
@PigalevPavel Sanırım kafa karıştırıcı $timeout(temelde setTimeout+ Angular's $scope.$apply()) setInterval. koşul $timeoutbaşına yalnızca bir kez yürütülür ifve bu bir sonraki $digestdöngünün başlangıcında olur . JavaScript zaman aşımları hakkında daha fazla bilgi için, bkz. Ejohn.org/blog/how-javascript-timers-work
-principle

1
@finishingmove Belki bir şey anlamıyorum :) Ama bakın başka bir ng-tekrarında tekrar ediyorum. Ve hepsinin ne zaman bittiğini bilmek istiyorum. Komut dosyanızı $ timeout ile kullandığımda sadece ebeveyn ng-tekrar için her şey iyi çalışıyor. Ancak $ timeout kullanmazsam, çocukların tekrarları bitmeden bir yanıt alırım. Nedenini bilmek istiyorum? Ve betiğinizi ebeveyn ng-tekrarı için $ timeout ile kullanırsam, tüm ng-tekrarı bittiğinde her zaman yanıt alacağımdan emin olabilir miyim?
Pigalev Pavel

1
Neden setTimeout()0 gecikmesi gibi bir şey kullanacaksınız başka bir soru olsa da, tarayıcının olay kuyruğu için hiçbir zaman gerçek bir spesifikasyona rastlamadım, sadece tek iş parçacıklılık ve varlığının ima ettiği şeyleri söylemem gerekir setTimeout().
rakslice

1
Bu cevap için teşekkürler. Bir sorum var, neden atamalıyız on-finish-render="ngRepeatFinished"? Atadığımda on-finish-render="helloworld"aynı şekilde çalışıyor.
Arnaud

89

Geri aramanızın (ör. Test ()) DOM oluşturulduktan sonra ancak tarayıcı oluşturulmadan önce yürütülmesini istiyorsanız $ evalAsync kullanın . Bu titremeyi önleyecektir - ref .

if (scope.$last) {
   scope.$evalAsync(attr.onFinishRender);
}

Keman .

Oluşturduktan sonra geri aramanızı gerçekten aramak istiyorsanız, $ timeout kullanın:

if (scope.$last) {
   $timeout(function() { 
      scope.$eval(attr.onFinishRender);
   });
}

Etkinlik yerine $ eval'i tercih ederim. Bir olayla, olayın adını bilmemiz ve o olay için denetleyicimize kod eklememiz gerekir. $ Eval ile kontrolör ve direktif arasında daha az bağlantı vardır.


Kontrol ne $lastişe yarar? Dokümanlarda bulamıyorum. Kaldırıldı mı?
Erik Aigner

2
@ErikAigner, öğede etkin $lastbir ng-repeatan varsa kapsamda tanımlanır .
Mark Rajcok

Görünüşe göre Fiddle'ınız gereksiz oluyor $timeout.
bbodenmiller

1
Harika. Bu, yayma / yayınlamanın performans açısından daha pahalı olduğunu gören kabul edilen cevap olmalıdır.
Florin Vistig

29

Bugüne kadar verilmiş cevaplar sadece ilk kez çalışacak ng-repeathale alır , ancak bir dinamik varsa ng-repeatöğeleri filtreleme / ekleme / silme olacak yani ve sen her zaman olduğu bildirilir gerekmektedir ng-repeatrender olur, bu çözümler sizin için işe yaramaz.

Yani, HER ZAMAN,ng-repeat sadece ilk kez değil , yeniden oluşturulduğu konusunda bilgilendirilmeniz gerekiyorsa , bunu yapmanın bir yolunu buldum, oldukça 'çirkin', ama ne olduğunuzu biliyorsanız iyi çalışır yapıyor. Bunu kullanın $filterSepetinde ng-repeat başka kullanmadan önce$filter :

.filter('ngRepeatFinish', function($timeout){
    return function(data){
        var me = this;
        var flagProperty = '__finishedRendering__';
        if(!data[flagProperty]){
            Object.defineProperty(
                data, 
                flagProperty, 
                {enumerable:false, configurable:true, writable: false, value:{}});
            $timeout(function(){
                    delete data[flagProperty];                        
                    me.$emit('ngRepeatFinished');
                },0,false);                
        }
        return data;
    };
})

Bu işlem, her oluşturulduğunda $emitçağrılacak bir etkinlik olacaktır .ngRepeatFinishedng-repeat

Bu nasıl kullanılır:

<li ng-repeat="item in (items|ngRepeatFinish) | filter:{name:namedFiltered}" >

ngRepeatFinishFiltre ihtiyaçları bir doğrudan uygulanacak Arrayveya Objecttanımlanan senin$scope , sen sonra diğer filtreler de uygulayabilirsiniz.

Nasıl KULLANILMAZ:

<li ng-repeat="item in (items | filter:{name:namedFiltered}) | ngRepeatFinish" >

Önce diğer filtreleri, ardından ngRepeatFinishfiltreyi uygulamayın .

Bunu ne zaman kullanmalıyım?

Liste oluşturulduktan sonra belirli css stillerini DOM'a uygulamak istiyorsanız, çünkü DOM öğelerinin yeniden oluşturulmuş yeni DOM öğelerinin boyutlarını ng-repeat . (BTW: bu tür işlemler bir direktif dahilinde yapılmalıdır)

ngRepeatFinishedOlayı işleyen işlevde YAPILMAMASI GEREKENLER :

  • $scope.$applyBu işlevde bir işlem yapmayın, yoksa Angular'ı Angular'ın algılayamayacağı sonsuz bir döngüye koyacaksınız.

  • Bunu $scopeözelliklerde değişiklik yapmak için kullanmayın , çünkü bu değişiklikler bir sonraki $digestdöngüye kadar görünümünüze yansıtılmaz ve gerçekleştiremediğiniz için $scope.$applyherhangi bir faydası olmaz.

"Ancak filtreler bu şekilde kullanılmaz!"

Hayır, değiller, bu bir hack, eğer beğenmezseniz kullanmayın. Aynı şeyi başarmanın daha iyi bir yolunu biliyorsanız lütfen bana bildirin.

Özetleme

Bu bir hack'tir ve yanlış şekilde kullanmak tehlikelidir, bunu yalnızca ng-repeatrender işlemi bittikten sonra stil uygulamak için kullanın ve herhangi bir sorun yaşamamalısınız.


Teşekkürler 4 ipucu. Her neyse, çalışan bir örneği olan bir plunkr çok takdir edilecektir.
holographix

2
Ne yazık ki bu benim için en azından en son AngularJS 1.3.7 için işe yaramadı. Bu yüzden en güzel olanı değil, başka bir geçici çözüm keşfettim, ama bu sizin için önemliyse işe yarayacak. Ne zaman eklediğimde / öğeleri değiştirdiğimde / sildiğimde her zaman listenin sonuna başka bir kukla öğe ekliyorum, bu nedenle $lastöğe de değiştiğinden, şimdi ngRepeatFinishkontrol ederek basit direktif $lastçalışacaktır. NgRepeatFinish çağrıldığında kukla öğeyi kaldırırım. (Ben de CSS ile saklıyorum, bu yüzden kısaca görünmüyor)
darklow

Bu kesmek güzel ama mükemmel değil; etkinlikte filtre her başladığında beş kez gönderilir: - /
Sjeiti

Aşağıdaki Mark Rajcokseçenek, ng tekrarının her yeniden sunumunda mükemmel bir şekilde çalışır (örneğin, model değişikliği nedeniyle). Yani bu hileli çözüm gerekli değil.
Dzhu

1
@ Haklı olun - öğeler eklenmiş / kaydırılmamışsa (yani, değiştirilen dizi), çalışmaz. Benim önceki test muhtemelen yeniden atanmış dizi (sugarjs kullanarak) oldu bu nedenle her zaman çalıştı ... Bunu işaret
ettiğiniz

10

Aynı denetleyicide farklı ng tekrarları için farklı işlevler çağırmanız gerekirse, şöyle bir şey deneyebilirsiniz:

Yönerge:

var module = angular.module('testApp', [])
    .directive('onFinishRender', function ($timeout) {
    return {
        restrict: 'A',
        link: function (scope, element, attr) {
            if (scope.$last === true) {
            $timeout(function () {
                scope.$emit(attr.broadcasteventname ? attr.broadcasteventname : 'ngRepeatFinished');
            });
            }
        }
    }
});

Oyun kumandanızda, $ on ile etkinlikleri yakalayın:

$scope.$on('ngRepeatBroadcast1', function(ngRepeatFinishedEvent) {
// Do something
});

$scope.$on('ngRepeatBroadcast2', function(ngRepeatFinishedEvent) {
// Do something
});

Şablonunuzda birden çok ng-tekrarlı

<div ng-repeat="item in collection1" on-finish-render broadcasteventname="ngRepeatBroadcast1">
    <div>{{item.name}}}<div>
</div>

<div ng-repeat="item in collection2" on-finish-render broadcasteventname="ngRepeatBroadcast2">
    <div>{{item.name}}}<div>
</div>

5

Diğer çözümler ilk sayfa yüklemesinde iyi çalışır, ancak model değiştiğinde işlevinizin çağrılmasını sağlamanın tek yolu denetleyiciden $ timeout çağırmaktır. İşte $ timeout kullanan çalışan bir keman . Örneğin, şöyle olur:

.controller('myC', function ($scope, $timeout) {
$scope.$watch("ta", function (newValue, oldValue) {
    $timeout(function () {
       test();
    });
});

ngRepeat, yalnızca satır içeriği yeni olduğunda bir yönergeyi değerlendirir; bu nedenle, listenizden öğeleri kaldırırsanız, onFinishRender tetiklenmez. Örneğin, bu kemanlar filtre değerlerini girmeyi deneyin yayarlar .


Benzer şekilde, model keman
John Harley

2
ne tade $scope.$watch("ta",...?
Muhammad Adeel Zahid

4

Çift dolarlık kapsamı kullanmaktan kaçınmıyorsanız ve tek içeriği tekrarlanan bir direktif yazıyorsanız, oldukça basit bir çözüm var (sadece ilk render'ı önemsediğiniz varsayılarak). Link fonksiyonunda:

const dereg = scope.$watch('$$childTail.$last', last => {
    if (last) {
        dereg();
        // do yr stuff -- you may still need a $timeout here
    }
});

Bu, oluşturulmuş bir listenin üyelerinin genişliklerine veya yüksekliklerine göre DOM manipülasyonu yapmanız gereken bir yönerge olduğu durumlarda yararlıdır (bence bu soruyu sormanın en muhtemel nedeni olduğunu düşünüyorum), ancak genel olarak değil önerilen diğer çözümler gibi.


1

Filtrelenmiş bir ngRepeat ile bu soruna yönelik bir çözüm Mutasyon olaylarında olabilir, ancak kullanımdan kaldırılmıştır (hemen değiştirilmeden).

Sonra kolay bir tane daha düşündüm:

app.directive('filtered',function($timeout) {
    return {
        restrict: 'A',link: function (scope,element,attr) {
            var elm = element[0]
                ,nodePrototype = Node.prototype
                ,timeout
                ,slice = Array.prototype.slice
            ;

            elm.insertBefore = alt.bind(null,nodePrototype.insertBefore);
            elm.removeChild = alt.bind(null,nodePrototype.removeChild);

            function alt(fn){
                fn.apply(elm,slice.call(arguments,1));
                timeout&&$timeout.cancel(timeout);
                timeout = $timeout(altDone);
            }

            function altDone(){
                timeout = null;
                console.log('Filtered! ...fire an event or something');
            }
        }
    };
});

Bu, üst öğenin Node.prototype yöntemlerini, art arda yapılan değişiklikleri izlemek için tek bir $ zaman aşımı ile birleştirir.

Çoğunlukla doğru çalışıyor ama altDone'un iki kez çağrılacağı bazı durumlar aldım.

Yine ... bu yönergeyi ngRepeat öğesinin üst öğesine ekleyin.


Bu şimdiye kadarki en iyi sonucu veriyor, ancak mükemmel değil. Mesela 70 öğeden 68'e geçiyorum ama ateş etmiyor.
Gaui

1
Ne işe yarayabilir de ekledi appendChild(çünkü sadece kullanılan insertBeforeve removeChildyukarıdaki kod).
Sjeiti

1

Çok kolay, işte böyle yaptım.

.directive('blockOnRender', function ($blockUI) {
    return {
        restrict: 'A',
        link: function (scope, element, attrs) {
            
            if (scope.$first) {
                $blockUI.blockElement($(element).parent());
            }
            if (scope.$last) {
                $blockUI.unblockElement($(element).parent());
            }
        }
    };
})


Bu kod elbette kendi $ blockUI hizmetiniz varsa çalışır.
CPhelefu

0

Lütfen kemanı inceleyin, http://jsfiddle.net/yNXS2/ . Oluşturduğunuz direktif yeni bir kapsam oluşturmadığından bu şekilde devam ettim.

$scope.test = function(){... bunu gerçekleştirdi.


Yanlış keman? Sorudaki ile aynı.
James M

Humm, kemanı güncelledin mi? Şu anda orijinal kodumun bir kopyasını gösteriyor.
PCoelho

0

Bu sorunun cevapları arasında en basit çözümü görmemeye çok şaşırdım. Yapmak istediğiniz şey, ngInittekrarlanan elemanınıza ( ngRepeatdirektifli eleman ) kontrol etmek için bir yönerge eklemektir ( $lastkapsamda ayarlanmış ngRepeat, tekrarlanan elemanın listede sonuncu olduğunu gösteren özel bir değişken ). Eğer $lastdoğruysa, biz son öğe render ediyoruz ve istediğimiz işlevi çağırabilir.

ng-init="$last && test()"

HTML işaretlemenizin tam kodu:

<div ng-app="testApp" ng-controller="myC">
    <p ng-repeat="t in ta" ng-init="$last && test()">{{t}}</p>
</div>

Angular.js tarafından sağlandığından , aramak istediğiniz kapsam işlevinin yanı sıra (bu durumda test) uygulamanızda fazladan JS koduna ihtiyacınız ngInityoktur. testŞablondan erişilebilmesi için işlevinizin kapsamda olduğundan emin olun :

$scope.test = function test() {
    console.log("test executed");
}
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.