ng-repeat가 끝나면 함수 호출


249

내가 구현하려고하는 것은 기본적으로 "반복 완료 렌더링시"처리기입니다. 언제 완료되었는지 감지 할 수는 있지만 함수를 트리거하는 방법을 알 수는 없습니다.

바이올린을 확인하십시오 : 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>

답변 : 마무리 작업에서 바이올린 작업 : http://jsfiddle.net/paulocoelho/BsMqq/4/


호기심에서 element.ready()스 니펫 의 목적은 무엇 입니까? 내 말은 .. 당신이 가지고있는 일종의 jQuery 플러그인입니까, 아니면 요소가 준비되면 트리거되어야합니까?
gion_13

1
하나는 사용 할 수 내장 된 지침 겨-초기화처럼
semiomant


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. $timeoutng-repeated 요소가 렌더링을 완전히 마쳤을 때 실행되는지 확인하십시오 ( $timeout현재 다이제스트주기의 끝에서 will이 실행 되기 때문에 - $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을 사용합니다. 커플 링이 적습니다. 자세한 내용은 내 답변을 참조하십시오.
Mark Rajcok

1
@PigalevPavel 나는 당신이 $timeout(기본적으로 setTimeout+ Angular $scope.$apply())와 혼동한다고 생각합니다 setInterval. 조건 $timeout당 한 번만 실행되며 if다음 $digest주기 의 시작 부분에 있습니다. JavaScript 시간 초과에 대한 자세한 내용은 다음을 참조하십시오. ejohn.org/blog/how-javascript-timers-work
홀로그램 원리

1
@finishingmove 어쩌면 나는 무언가를 이해하지 못할 수도 있습니다 :) 그러나 다른 ng-repeat에 ng-repeat가 있음을 참조하십시오. 그리고 나는 그들 모두가 언제 끝났는지 확실히 알고 싶습니다. 부모 ng-repeat에 대해서만 $ timeout과 함께 스크립트를 사용하면 모든 것이 잘 작동합니다. 그러나 $ timeout을 사용하지 않으면 어린이 ng-repeats가 완료되기 전에 응답을받습니다. 왜 그런지 알고 싶어? 부모 ng-repeat에 대해 $ timeout으로 스크립트를 사용하면 모든 ng-repeats가 끝나면 항상 응답을받을 수 있습니까?
Pigalev Pavel

1
setTimeout()0 지연 과 같은 것을 사용하는 이유 는 또 다른 질문 이지만 브라우저의 이벤트 대기열에 대한 실제 사양을 한번도 보지 못했습니다. 단일 스레드 및의 존재로 인해 암시됩니다 setTimeout().
rakslice

1
이 답변에 감사드립니다. 질문이 있는데 왜 할당해야 on-finish-render="ngRepeatFinished"합니까? 할당 on-finish-render="helloworld"할 때도 동일하게 작동합니다.
Arnaud

89

DOM이 생성 된 후 브라우저가 렌더링되기 전에 콜백 (예 : test ())을 실행 하려면 $ evalAsync를 사용하십시오 . 이 떨림을 방지 할 수 있습니다 - 심판 .

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

깡깡이 .

렌더링 후 콜백을 실제로 호출하려면 $ timeout을 사용하십시오.

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

이벤트 대신 $ eval을 선호합니다. 이벤트의 경우 이벤트 이름을 알고 해당 이벤트의 컨트롤러에 코드를 추가해야합니다. $ eval을 사용하면 컨트롤러와 지시문 사이의 연결이 줄어 듭니다.


수표는 무엇을 $last합니까? 문서에서 찾을 수 없습니다. 제거 되었습니까?
Erik Aigner 2016 년

2
@ErikAigner 는 요소에서 활성화 된 $last경우 범위에서 정의됩니다 ng-repeat.
Mark Rajcok 2016 년

피들이 불필요 한 것 같습니다 $timeout.
bbodenmiller

1
큰. 방출 / 방송 비용이 성능 관점에서 더 비싸다는 것을 알면서도 이것이 정답입니다.
Florin Vistig

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전화 이벤트ngRepeatFinishedng-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.

언제 사용해야합니까?

목록 렌더링이 완료된 후 특정 CSS 스타일을 DOM에 적용하려는 경우 다음에 의해 다시 렌더링 된 DOM 요소의 새로운 차원을 고려해야합니다. ng-repeat . (BTW : 이러한 종류의 작업은 지시문 내에서 수행해야합니다)

ngRepeatFinished이벤트 를 처리하는 함수에서 수행하지 말아야 할 것 :

  • $scope.$apply해당 함수에서을 수행하지 마십시오. 그렇지 않으면 Angular가 감지 할 수없는 무한 루프에 Angular를 배치하게됩니다.

  • $scope속성 을 변경하는 데 사용하지 마십시오. 변경 사항은 다음 $digest루프 까지보기에 반영 되지 않으며 수행 할 수 없으므로 사용 $scope.$apply하지 않습니다.

"그러나 필터는 그런 식으로 사용되지 않습니다 !!"

아니, 그들은, 그것은 당신이 그것을 사용하지 않는 것을 좋아하지 않는 경우, 해킹입니다. 같은 것을 성취하는 더 좋은 방법을 알고 있다면 알려주십시오.

요약

이것은 해킹 이며 잘못된 방식으로 사용하는 것은 위험하므로 ng-repeat렌더링이 완료된 후에 스타일을 적용하는 데만 사용하면 아무런 문제가 없습니다.


Thx 4 팁. 어쨌든, 실례가있는 플런저는 대단히 감사하겠습니다.
holographix

2
불행히도 이것은 최신 AngularJS 1.3.7에서는 작동하지 않습니다. 그래서 가장 좋은 방법은 아니지만 다른 해결 방법을 발견했지만 이것이 필수적이라면 효과가 있습니다. 내가 한 것은 요소를 추가 / 변경 / 삭제할 때마다 항상 목록 끝에 다른 더미 요소를 추가하는 것입니다. 따라서 $last요소가 변경 되기 때문에 ngRepeatFinish검사를 통해 간단한 지시문 $last이 작동합니다. ngRepeatFinish가 호출되는 즉시 더미 항목을 제거합니다. (또한 CSS로 숨겨서 짧게 표시되지 않습니다)
darklow

이 핵은 훌륭하지만 완벽하지는 않습니다. 이벤트에서 필터 킥이 5 번 발송 될 때마다 :-/
Sjeiti

아래 그림 Mark Rajcok은 ng-repeat를 다시 렌더링 할 때마다 완벽하게 작동합니다 (예 : 모델 변경으로 인해). 따라서이 해키 솔루션은 필요하지 않습니다.
Dzhu

1
@Josep 당신이 맞아요-항목이 접합 / 변이가없는 경우 (즉, 배열이 변경되는 경우) 작동하지 않습니다. 내 이전 테스트는 아마도 sugarjs를 사용하여 다시 할당 된 배열을 사용했을 때마다 항상 작동했습니다 ...이 점을 지적 해 주셔서 감사합니다
Dzhu

10

동일한 컨트롤러에서 다른 ng-repeats에 대해 다른 함수를 호출 해야하는 경우 다음과 같이 시도 할 수 있습니다.

지시어 :

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

컨트롤러에서 $ on으로 이벤트를 잡으십시오.

$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

다른 솔루션은 초기 페이지로드에서 제대로 작동하지만 컨트롤러에서 $ timeout을 호출하는 것이 모델이 변경 될 때 함수가 호출되도록하는 유일한 방법입니다. $ timeout을 사용 하는 작동하는 바이올린 은 다음과 같습니다 . 귀하의 예를 들면 다음과 같습니다.

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

ngRepeat는 행 내용이 새로운 경우에만 지시문을 평가하므로 목록에서 항목을 제거하면 onFinishRender가 실행되지 않습니다. 예를 들어, 이러한 바이올린 방출 에 필터 값을 입력하십시오 .


마찬가지로, 모델이 바이올린을
John Harley

2
뭐가 ta$scope.$watch("ta",...?
Muhammad Adeel Zahid

4

이중 달러 범위 소품을 사용하지 않고 내용 만 반복되는 지시문을 작성하는 경우 매우 간단한 해결책이 있습니다 (초기 렌더링에만 관심이 있다고 가정). 링크 기능에서 :

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

이것은 렌더링 된 목록의 멤버의 너비 또는 높이를 기반으로 DOM 조작을 수행 해야하는 지시문이있는 경우에 유용하지만 (이 질문을 할 가능성이 가장 큰 이유라고 생각합니다) 제안 된 다른 솔루션으로.


1

필터링 된 ngRepeat 의이 문제에 대한 해결책은 다음과 같습니다. 돌연변이 이벤트와 관련이 있었지만 더 이상 사용되지 않습니다 (즉시 교체하지 않음).

그런 다음 또 다른 쉬운 것을 생각했습니다.

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

이것은 한 번의 $ timeout으로 부모 요소의 Node.prototype 메소드에 연결되어 연속적인 수정을 감시합니다.

대부분 올바르게 작동하지만 altDone이 두 번 호출되는 경우가 있습니다.

다시 ...이 지시어를 ngRepeat 의 부모 에 추가하십시오 .


이것은 지금까지 가장 잘 작동하지만 완벽하지는 않습니다. 예를 들어 70 개 항목에서 68 개 항목으로 이동하지만 실행되지 않습니다.
Gaui

1
무엇 일할 수있는 것은 추가이다 appendChild(I 만 사용하기 때문에뿐만 아니라 insertBeforeremoveChild위의 코드에서).
Sjeiti

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(){... 그렇게 했어요


잘못된 바이올린? 질문에있는 것과 동일합니다.
James M

흠, 바이올린을 업데이트 했습니까? 현재 내 원본 코드의 사본을 보여줍니다.
PCoelho

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>

Angular.js에서 제공하기 test때문에 호출하려는 범위 함수 (이 경우 ) 외에 앱에 추가 JS 코드가 필요하지 않습니다 ngInit. test템플릿에서 액세스 할 수 있도록 해당 범위에 함수 가 있어야합니다 .

$scope.test = function test() {
    console.log("test executed");
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.