एनजी-रिपीट खत्म होने पर एक फंक्शन को कॉल करना


249

मैं जो लागू करने की कोशिश कर रहा हूं वह मूल रूप से "एनजी रिपीट समाप्त रेंडरिंग" हैंडलर है। मैं यह पता लगाने में सक्षम हूं कि यह कब किया गया है, लेकिन मैं यह पता नहीं लगा सकता हूं कि इससे कोई फ़ंक्शन कैसे ट्रिगर किया जाए।

फिडेल की जाँच करें: http://jsfiddle.net/paulocoelho/BsMqq/3/

जे एस

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

एचटीएमएल

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

उत्तर : फिनिशिंगोव से वर्किंग फिडेल: http://jsfiddle.net/paulocoelho/BsMqq/4/


जिज्ञासा से बाहर, element.ready()स्निपेट का उद्देश्य क्या है ? मेरा मतलब है .. क्या यह किसी प्रकार का jQuery प्लगइन है जो आपके पास है, या तत्व तैयार होने पर इसे चालू किया जाना चाहिए?
gion_13

1
कोई भी इसका उपयोग कर सकता है जैसे कि एनजी-
इनिट


stackoverflow.com/questions/13471129/… का डुप्लिकेट , वहाँ मेरा जवाब देखें
bresleveloper

जवाबों:


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

ध्यान दें कि मैंने उपयोग नहीं किया .ready(), बल्कि इसे एक में लपेटा $timeout$timeoutसुनिश्चित करें कि यह तब निष्पादित किया जाता है जब एनजी-दोहराया तत्वों ने वास्तव में प्रतिपादन समाप्त कर दिया है (क्योंकि $timeoutवर्तमान पचाने के चक्र के अंत में निष्पादित होगा - और यह $applyआंतरिक रूप से, इसके विपरीत भी कॉल करेगाsetTimeout )। इसलिए ng-repeatसमाप्त होने के बाद , हम $emitएक इवेंट को बाहरी स्कोप्स (सिबलिंग और पैरेंट स्कोप्स) से बाहर निकालने के लिए उपयोग करते हैं।

और फिर अपने नियंत्रक में, आप इसे पकड़ सकते हैं $on:

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

Html के साथ जो कुछ इस तरह दिखता है:

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

10
+1, लेकिन मैं एक घटना का उपयोग करने से पहले $ eval का उपयोग करूंगा - कम युग्मन। अधिक जानकारी के लिए मेरा जवाब देखें।
मार्क राजकॉक

1
@PigalevPavel मुझे लगता है कि आप भ्रमित कर रहे हैं $timeout(जो मूल रूप से setTimeout+ कोणीय है $scope.$apply()) setInterval। हालत के $timeoutअनुसार केवल एक बार निष्पादित करेगा if, और यह अगले $digestचक्र की शुरुआत में होगा । जावास्क्रिप्ट टाइमआउट पर अधिक जानकारी के लिए, देखें: ejohn.org/blog/how-javascript-timers-work
होलोग्राफिक-सिद्धांत

1
@finishingmove शायद मुझे कुछ समझ न आए :) लेकिन देखिए मेरे पास एक और एनजी-रिपीट में एनजी-रिपीट है। और मुझे यकीन है कि जब उन सभी को खत्म कर रहे हैं जानना चाहता हूँ। जब मैं पेरेंट एनजी-रिपीट के लिए $ टाइमआउट के साथ आपकी स्क्रिप्ट का उपयोग करता हूं तो यह सब ठीक काम करता है। लेकिन अगर मैं $ टाइमआउट का उपयोग नहीं करता हूं, तो बच्चों के एनजी-रिपीट समाप्त होने से पहले मुझे एक प्रतिक्रिया मिलती है। मैं जानना चाहता हूं क्यो? और क्या मैं यह सुनिश्चित कर सकता हूं कि अगर मैं पेरेंट एनजी-रिपीट के लिए $ टाइमआउट के साथ आपकी स्क्रिप्ट का उपयोग करता हूं, तो मुझे सभी प्रतिक्रियाएं मिलेंगी जब सभी एनजी-रिपीट समाप्त हो जाते हैं?
पिगलेव पावेल

1
आप setTimeout()0 विलंब के साथ कुछ का उपयोग क्यों करेंगे यह एक और सवाल है, हालांकि मुझे कहना होगा कि मैं ब्राउज़र की घटना कतार के लिए वास्तविक विनिर्देशन में कभी भी नहीं आया हूं, बस एक-पिरोयापन और इसके अस्तित्व से क्या निहित है setTimeout()
राकसलिस

1
इस उत्तर के लिए धन्यवाद। मेरा एक सवाल है, हमें असाइन करने की आवश्यकता क्यों है on-finish-render="ngRepeatFinished"? जब मैं असाइन on-finish-render="helloworld"करता हूं , तो यह काम करता है।
अरनौद

89

यदि आप अपने कॉलबैक (यानी, परीक्षण) () डोम के निर्माण के बाद निष्पादित किया जाना चाहते हैं, तो $ evalAsync का उपयोग करें , लेकिन ब्राउज़र रेंडर करने से पहले। यह झिलमिलाहट को रोक देगा - रेफरी

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

फील करना

यदि आप वास्तव में रेंडर करने के बाद अपने कॉलबैक को कॉल करना चाहते हैं, तो $ टाइमआउट का उपयोग करें:

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

मैं एक घटना के बजाय $ eval को प्राथमिकता देता हूं। किसी ईवेंट के साथ, हमें ईवेंट का नाम जानना होगा और उस ईवेंट के लिए हमारे कंट्रोलर में कोड जोड़ना होगा। $ Eval के साथ, नियंत्रक और निर्देश के बीच कम युग्मन होता है।


क्या करने के लिए जाँच करता $lastहै? इसे डॉक्स में नहीं पा सकते हैं। क्या इसे हटाया गया था?
एरिक एगर

2
@ErikAigner, $lastयदि ng-repeatतत्व पर सक्रिय है तो गुंजाइश पर परिभाषित किया गया है।
मार्क राजकोक

ऐसा लगता है कि आपका फिडल अनावश्यक रूप से ले रहा है $timeout
bbodenmiller

1
महान। यह स्वीकृत उत्तर होना चाहिए, यह देखते हुए कि प्रदर्शन के दृष्टिकोण से उत्सर्जन / प्रसारण लागत अधिक है।
फ्लोरिन विस्टिग

29

अब तक दिए गए उत्तर केवल पहली बार काम करेंगे जो ng-repeatप्रदान किए जाते हैं , लेकिन यदि आपके पास एक गतिशील है ng-repeat, जिसका अर्थ है कि आप आइटम जोड़ना / हटाना / फ़िल्टर करना चाहते हैं, और आपको हर बार सूचित करने की आवश्यकता है कि ng-repeatप्रदान किया जाता है, वे समाधान आपके लिए काम नहीं करेंगे।

इसलिए, अगर आपको हर समय सूचित किया जाना चाहिए, जो ng-repeatफिर से प्रस्तुत किया जाता है और सिर्फ पहली बार नहीं, मैंने ऐसा करने का एक तरीका ढूंढ लिया है, यह काफी 'हैकी' है, लेकिन अगर आप जानते हैं कि यह ठीक काम करेगा करते हुए। किसी अन्य का उपयोग करने से पहले इसे $filterअपने में उपयोग करें :ng-repeat $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;
    };
})

यह हर बार $emitकहा जाता ngRepeatFinishedहै कि एक घटना ng-repeatप्रदान की जाएगी।

इसे कैसे उपयोग करे:

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

ngRepeatFinishफिल्टर एक करने के लिए सीधे लागू किया जा करने की जरूरत है Arrayया एक Objectअपने में परिभाषित किया गया $scopeहै, तो आप के बाद अन्य फिल्टर लागू कर सकते।

इसका उपयोग कैसे न करें:

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

पहले अन्य फ़िल्टर लागू न करें और फिर ngRepeatFinishफ़िल्टर लागू करें ।

मुझे इसका उपयोग कब करना चाहिए?

यदि आप सूची को पूरा करने के बाद DOM में कुछ css स्टाइल लागू करना चाहते हैं, क्योंकि आपको DOM तत्वों के नए आयामों को ध्यान में रखना होगा जो कि इसके द्वारा फिर से प्रस्तुत किए गए हैं ng-repeat। (बीटीडब्ल्यू: इस तरह के ऑपरेशन एक निर्देश के अंदर किए जाने चाहिए)

समारोह को संभालने वाले समारोह में क्या न करें ngRepeatFinished:

  • $scope.$applyउस फ़ंक्शन में एक प्रदर्शन न करें या आप कोणीय को एक अंतहीन लूप में डाल देंगे जो कि कोणीय का पता लगाने में सक्षम नहीं होगा।

  • $scopeगुणों में परिवर्तन करने के लिए इसका उपयोग न करें , क्योंकि अगले $digestलूप तक वे परिवर्तन आपके विचार में प्रतिबिंबित नहीं होंगे , और चूंकि आप प्रदर्शन नहीं कर सकते हैं, $scope.$applyइसलिए वे किसी काम के नहीं होंगे।

"लेकिन फिल्टर का उपयोग उस तरह से करने के लिए नहीं है !!"

नहीं, वे नहीं हैं, यह एक हैक है, अगर आपको यह पसंद नहीं है तो इसका उपयोग न करें। यदि आप एक ही बात को पूरा करने का बेहतर तरीका जानते हैं तो कृपया मुझे बताएं।

सारांश

यह एक हैक है , और इसे गलत तरीके से उपयोग करना खतरनाक है, इसका उपयोग केवल शैलियों का उपयोग करने के बाद किया गया ng-repeatहै, और आपको कोई समस्या नहीं होनी चाहिए।


टीएक्स 4 टिप। वैसे भी, काम के उदाहरण के साथ एक प्लंकर की बहुत सराहना की जाएगी।
होलोग्राफिक्स

2
दुर्भाग्य से यह मेरे लिए काम नहीं किया है, कम से कम नवीनतम AngularJS 1.3.7 के लिए। इसलिए मैंने एक और वर्कअराउंड की खोज की, नॉटेस्ट वन की, लेकिन अगर यह आपके लिए जरूरी है तो यह काम करेगा। मैंने जो भी किया है, जब भी मैं तत्वों को जोड़ता / बदल / हटाता हूं, तो मैं हमेशा सूची के अंत में एक और डमी तत्व जोड़ता हूं, इसलिए चूंकि $lastतत्व भी बदला गया है , इसलिए अब ngRepeatFinishचेक करके सरल निर्देश $lastकाम करेगा। जैसे ही ngRepeatFinish कहा जाता है मैं डमी आइटम को हटा देता हूं। (मैं इसे सीएसएस के साथ भी छिपाता हूं ताकि यह संक्षेप में प्रकट न हो)
डार्ककोल

यह हैक अच्छा है, लेकिन सही नहीं है; हर बार जब इस घटना में फिल्टर किक को पाँच बार भेजा जाता है: - /
Sjeiti

नीचे वाला Mark Rajcokपूरी तरह से एनजी-रिपीट (जैसे मॉडल परिवर्तन के कारण) के हर री-रेंडर को काम करता है। इसलिए इस घिनौने समाधान की आवश्यकता नहीं है।
द्ज़ु

1
@Josep आप सही कह रहे हैं - जब आइटम स्प्लिस्ड / अनशिफ्टेड होते हैं (यानी म्यूट किया जा रहा है), यह काम नहीं करता है। मेरा पिछला परीक्षण संभवतः उस सरणी के साथ था जिसे पुन: असाइन किया गया है (गन्ने का उपयोग करके) इसलिए यह हर बार काम करता है ... इसे इंगित करने के लिए धन्यवाद
Dzhu

10

यदि आपको एक ही नियंत्रक पर अलग-अलग एनजी-रिपीट के लिए अलग-अलग फ़ंक्शन को कॉल करने की आवश्यकता है, तो आप कुछ इस तरह की कोशिश कर सकते हैं:

निर्देश:

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

अपने नियंत्रक में, $ पर घटनाओं को पकड़ें:

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

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

कई एनजी-दोहराने के साथ अपने टेम्पलेट में

<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

अन्य समाधान प्रारंभिक पृष्ठ लोड पर ठीक काम करेंगे, लेकिन नियंत्रक से $ समयबाह्य कॉल करना यह सुनिश्चित करने का एकमात्र तरीका है कि मॉडल बदलने पर आपका फ़ंक्शन कहा जाता है। यहां एक वर्किंग फिडेल है जो $ टाइमआउट का उपयोग करता है। आपके उदाहरण के लिए यह होगा:

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

ngRepeat केवल एक निर्देश का मूल्यांकन करेगा जब पंक्ति सामग्री नई होती है, इसलिए यदि आप अपनी सूची से आइटम निकालते हैं, तो onFishishRender फायर नहीं करेगा। उदाहरण के लिए, इन fiddles में फ़िल्टर मान दर्ज करने का प्रयास फेंकना


इसी तरह, परीक्षण () हमेशा evalAsynch समाधान में नहीं कहा जाता है जब मॉडल फिडल
जॉन हार्ले

2
क्या है taमें $scope.$watch("ta",...?
मुहम्मद अदील जाहिद

4

यदि आप डबल-डॉलर स्कोप प्रॉप्स का उपयोग करने से पीछे नहीं हैं और आप एक निर्देश लिख रहे हैं जिसकी एकमात्र सामग्री एक दोहराव है, तो एक बहुत ही सरल समाधान है (यह मानते हुए कि आप केवल शुरुआती रेंडर के बारे में परवाह करते हैं)। लिंक फ़ंक्शन में:

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

यह उन मामलों के लिए उपयोगी है, जहां आपके पास एक निर्देश है जो किसी रेंडर सूची के सदस्यों की चौड़ाई या ऊंचाइयों के आधार पर DOM हेरफेर करने की आवश्यकता है (जो मुझे लगता है कि सबसे संभावित कारण यह प्रश्न पूछेगा), लेकिन यह उतना सामान्य नहीं है अन्य समाधानों के रूप में जो प्रस्तावित किए गए हैं।


1

फ़िल्टर्ड एनकैपिट के साथ इस समस्या का एक समाधान उत्परिवर्तन घटनाओं के साथ हो सकता है , लेकिन वे अपवर्तित (बिना प्रतिस्थापन के) हैं।

फिर मैंने एक और आसान सोचा:

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

यह मूल तत्वों के Node.prototype तरीकों में हुक करता है, जो लगातार संशोधनों के लिए एक टिक-टिक $ टाइमआउट के साथ है।

यह ज्यादातर सही काम करता है, लेकिन मुझे कुछ मामले मिले जहां वेडोन को दो बार बुलाया जाएगा।

फिर से ... इस निर्देश को ngRepeat के जनक में जोड़ें।


यह अब तक का सबसे अच्छा काम करता है, लेकिन यह सही नहीं है। उदाहरण के लिए, मैं 70 आइटम से 68 तक जाता हूं, लेकिन यह आग नहीं करता है।
गौई

1
उपरोक्त कोड में क्या काम कर सकता है appendChild(जैसा कि मैंने केवल उपयोग किया है insertBeforeऔर removeChild) जोड़ रहा है ।
सिजिटी

1

बहुत आसान है, यह मैंने कैसे किया।

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


यदि आपके पास अपनी $ blockUI सेवा है, तो यह कोड निश्चित रूप से काम करता है।
CPhelefu

0

कृपया फिडेल, http://jsfiddle.net/yNXS2/ पर एक नज़र डालें । आपके द्वारा बनाए गए निर्देश के बाद से मैं उस तरीके से जारी एक नया दायरा नहीं बना पाया।

$scope.test = function(){... ऐसा हुआ।


गलत फीलिंग? सवाल में एक के रूप में ही।
जेम्स एम।

हम्म, क्या आपने फिडेल को अपडेट किया? वर्तमान में यह मेरे मूल कोड की एक प्रति दिखा रहा है।
पीसीलोहो

0

मैं इस सवाल के जवाब के बीच सबसे सरल समाधान नहीं देखकर बहुत हैरान हूं। आप जो करना चाहते हैं वह ngInitआपके दोहराए गए तत्व ( ngRepeatनिर्देश के साथ तत्व ) पर एक निर्देश जोड़ने के लिए है $last(गुंजाइश में एक विशेष चर सेट ngRepeatजिसके द्वारा दोहराया गया तत्व सूची में अंतिम है)। यदि $lastयह सत्य है, तो हम अंतिम तत्व का प्रतिपादन कर रहे हैं और हम जिस कार्य को चाहते हैं उसे कॉल कर सकते हैं।

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

आपके HTML मार्कअप का पूरा कोड होगा:

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

अंगुलि .js द्वारा प्रदान किए जाने के testबाद से आप इस फ़ंक्शन में (इस मामले में ) कॉल करना चाहते हैं, इसके अलावा आपको अपने एप्लिकेशन में किसी भी अतिरिक्त JS कोड की आवश्यकता नहीं है ngInit। बस अपने testफ़ंक्शन को कार्यक्षेत्र में सुनिश्चित करें ताकि इसे टेम्पलेट से एक्सेस किया जा सके:

$scope.test = function test() {
    console.log("test executed");
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.