ng-repeat 完成事件

211
我希望调用一些针对含有表格的div的jQuery函数。该表格是由ng-repeat填充的。
当我在...(此处省略)
$(document).ready()

我没有任何结果。

另外

$scope.$on('$viewContentLoaded', myFunc);

有没有办法在 ng-repeat 完成后立即执行函数?我读到了一个使用自定义 directive 的建议,但是我不知道如何将它与 ng-repeat 和我的 div 一起使用...

15个回答

244

确实,你应该使用指令,并且没有与ng-Repeat循环结束相关联的事件(因为每个元素都是单独构建的,而且有它自己的事件)。但是a)使用指令可能是你需要的全部内容;b)有一些ng-Repeat特定的属性可以用来创建“在ngRepeat完成时”的事件。

具体而言,如果你只想为整个表格设置样式/添加事件,可以在一个包含所有ngRepeat元素的指令中这样做。另一方面,如果你想分别处理每个元素,你可以在ngRepeat内部使用指令,它将在每个元素创建后发生作用。

然后,还有$index、$first、$middle和$last等属性可用于触发事件。因此,对于这个HTML:

<div ng-controller="Ctrl" my-main-directive>
  <div ng-repeat="thing in things" my-repeat-directive>
    thing {{thing}}
  </div>
</div>

你可以这样使用指令:

angular.module('myApp', [])
.directive('myRepeatDirective', function() {
  return function(scope, element, attrs) {
    angular.element(element).css('color','blue');
    if (scope.$last){
      window.alert("im the last!");
    }
  };
})
.directive('myMainDirective', function() {
  return function(scope, element, attrs) {
    angular.element(element).css('border','5px solid red');
  };
});

在这个Plunker中可以看到它的实际应用。


2
当然可以!只需将“window.alert”更改为事件触发函数,并使用主指令捕获它。我已经更新了Plunker以提供示例。 - Tiago Roldão
9
建议先引入jQuery库(在Angular之前)。这将导致所有的Angular元素都被jQuery包装(而不是jqLite)。然后,您可以使用element.css()和element.children().css()来代替angular.element(element).css()和$(element).children().css()。请参阅https://groups.google.com/d/msg/angular/6A3Skwm59Z4/oJ0WhKGAFK0J。 - Mark Rajcok
你能告诉我 ng-options 是否有类似的方法吗?我需要知道 Angular 何时构建了我的选择框,但是没有 ng-repeat。 - Neil
11
有人知道如何让ngRepeat指令在后续渲染中正常工作吗?例如:[链接](http://stackoverflow.com/questions/21270845/how-do-i-create-a-callback-for-ng-repeat-orderby) - RavenHursT
1
这是所有Angular指令的命名约定。请参阅https://docs.angularjs.org/guide/directive#normalization - Tiago Roldão
显示剩余7条评论

69

如果你只是想在循环结束时执行一些代码,这里有一个稍微简单一点的变体,不需要额外的事件处理:

<div ng-controller="Ctrl">
  <div class="thing" ng-repeat="thing in things" my-post-repeat-directive>
    thing {{thing}}
  </div>
</div>
function Ctrl($scope) {
  $scope.things = [
    'A', 'B', 'C'  
  ];
}

angular.module('myApp', [])
.directive('myPostRepeatDirective', function() {
  return function(scope, element, attrs) {
    if (scope.$last){
      // iteration is complete, do whatever post-processing
      // is necessary
      element.parent().css('border', '1px solid black');
    }
  };
});

查看演示。



如果我使用控制器作为语法,这个能行吗?拜托了。如果不行,还有其他任何可以与控制器一起使用的方法吗? - Dan Nguyen

66

无需专门创建指令来实现ng-repeat完成事件。

ng-init可以为你完成这个魔术。

  <div ng-repeat="thing in things" ng-init="$last && finished()">

$last 确保只有在最后一个元素被渲染到DOM时才触发 finished 事件。

不要忘记创建 $scope.finished 事件。

祝编码愉快!

编辑:2016年10月23日

如果您希望在数组中没有项目时也调用 finished 函数,则可以使用以下解决方法

<div style="display:none" ng-init="things.length < 1 && finished()"></div>
//or
<div ng-if="things.length > 0" ng-init="finished()"></div>

只需将上述代码添加到 ng-repeat 元素的顶部,它会检查数组是否有任何值,并相应地调用函数。

例如:

<div ng-if="things.length > 0" ng-init="finished()"></div>
<div ng-repeat="thing in things" ng-init="$last && finished()">

如果“things”最终是一个空数组会怎么样? - Frane Poljak
1
@FranePoljak 我已经在答案中添加了解决方案。 - Vikas Bansal
1
如果数组为空,在控制器中处理它。 - JSAddict
修复Vikas Bansal的答案://如果您想检查数组是否没有任何值并相应地调用函数,请使用第一个div。 <div ng-if="!things.length" ng-hide="true" ng-init="finished()"></div> <div ng-repeat="thing in things" ng-init="$last && finished()"> - Alex Teslenko

41

以下是一个repeat-done指令,当值为true时调用指定函数。我发现被调用的函数必须使用interval=0的$timeout,然后再进行DOM操作,例如在渲染元素上初始化工具提示。 jsFiddle: http://jsfiddle.net/tQw6w/

在$scope.layoutDone中,尝试注释掉$timeout并取消注释“NOT CORRECT!”行,以查看工具提示中的差异。

<ul>
    <li ng-repeat="feed in feedList" repeat-done="layoutDone()" ng-cloak>
    <a href="{{feed}}" title="view at {{feed | hostName}}" data-toggle="tooltip">{{feed | strip_http}}</a>
    </li>
</ul>

JS:

angular.module('Repeat_Demo', [])

    .directive('repeatDone', function() {
        return function(scope, element, attrs) {
            if (scope.$last) { // all are rendered
                scope.$eval(attrs.repeatDone);
            }
        }
    })

    .filter('strip_http', function() {
        return function(str) {
            var http = "http://";
            return (str.indexOf(http) == 0) ? str.substr(http.length) : str;
        }
    })

    .filter('hostName', function() {
        return function(str) {
            var urlParser = document.createElement('a');
            urlParser.href = str;
            return urlParser.hostname;
        }
    })

    .controller('AppCtrl', function($scope, $timeout) {

        $scope.feedList = [
            'http://feeds.feedburner.com/TEDTalks_video',
            'http://feeds.nationalgeographic.com/ng/photography/photo-of-the-day/',
            'http://sfbay.craigslist.org/eng/index.rss',
            'http://www.slate.com/blogs/trending.fulltext.all.10.rss',
            'http://feeds.current.com/homepage/en_US.rss',
            'http://feeds.current.com/items/popular.rss',
            'http://www.nytimes.com/services/xml/rss/nyt/HomePage.xml'
        ];

        $scope.layoutDone = function() {
            //$('a[data-toggle="tooltip"]').tooltip(); // NOT CORRECT!
            $timeout(function() { $('a[data-toggle="tooltip"]').tooltip(); }, 0); // wait...
        }

    })

我本来不想使用$timeout,但当你说它可以设置为0时,我决定尝试一下,结果效果很好。我想知道这是否与消化有关,是否有一种方法可以在不使用$timeout的情况下完成$timeout所做的事情。 - Jameela Huq
请问能否解释一下为什么这个方法可以成功运行?我正在尝试访问动态ID,只有在设置了0毫秒的延迟后才能起作用。我在几篇帖子中看到了这种“hacky”的解决方案,但没有人解释为什么它可以工作。 - Jin

30

这里有一个简单的方法,使用ng-init甚至不需要自定义指令。在某些情况下,例如需要在页面加载时将ng-repeat项目的div自动滚动到特定项目时,它对我很有效,因此滚动函数需要等待ng-repeat完成DOM的渲染才能触发。

<div ng-controller="MyCtrl">
    <div ng-repeat="thing in things">
        thing: {{ thing }}
    </div>
    <div ng-init="fireEvent()"></div>
</div>

myModule.controller('MyCtrl', function($scope, $timeout){
    $scope.things = ['A', 'B', 'C'];

    $scope.fireEvent = function(){

        // This will only run after the ng-repeat has rendered its things to the DOM
        $timeout(function(){
            $scope.$broadcast('thingsRendered');
        }, 0);

    };
});

请注意,这只适用于您需要在ng-repeat首次渲染后调用一次的函数。如果您需要在ng-repeat内容更新时调用函数,则必须使用本线程中的其他答案之一,配合自定义指令。


1
太棒了,这个解决了问题。我想要在文本框中自动选择文本,而超时设置起到了关键作用。否则,当数据绑定的{{model.value}}被注入时,该文本会被选中然后取消选中。 - JoshGough

28

在补充 Pavel 的回答之后,更易读且易于理解的内容如下:

<ul>
    <li ng-repeat="item in items" 
        ng-init="$last ? doSomething() : angular.noop()">{{item}}</li>
</ul>

你觉得为什么 angular.noop 会首先存在呢?

优点:

您不需要为此编写指令...


20
当可以使用布尔逻辑时,为什么要使用三元运算符:ng-init="$last && doSomething()" - Nate Whittaker
阅读 @NateWhittaker 的代码比三元运算符难度大,但更易于理解。拥有干净、简单易懂的代码是很好的。 - Justin

21

可以使用ngInit和Lodash的debounce方法,不需要自定义指令,以一种更简单的方式来处理:

控制器:

$scope.items = [1, 2, 3, 4];

$scope.refresh = _.debounce(function() {
    // Debounce has timeout and prevents multiple calls, so this will be called 
    // once the iteration finishes
    console.log('we are done');
}, 0);

模板:

<ul>
    <li ng-repeat="item in items" ng-init="refresh()">{{item}}</li>
</ul>

更新

使用三元运算符,甚至可以提供更简单的纯 AngularJS 解决方案:

模板:

<ul>
    <li ng-repeat="item in items" ng-init="$last ? doSomething() : null">{{item}}</li>
</ul>

请注意,ngInit使用预链接编译阶段 - 即在处理子指令之前调用表达式。这意味着仍可能需要异步处理。


需要注意的是,这需要lodash.js才能正常工作 :)另外:$scope.refresh = , 中的",20"部分,是指在最后一次迭代之后调用此方法的毫秒数吗? - Jørgen Skår Fischer
debounce 链接前添加了 Lodash。是的,20 是方法执行前的延迟时间 - 改为 0 是因为只要调用是异步的,延迟时间并不重要。 - Pavel Horal
1
同时考虑到这一点,由于三元运算符在表达式中是允许的,简单的 ng-init="$last ? refresh() : false" 也可以工作。而且这甚至不需要 Lodash。 - Pavel Horal
<div ng-repeat="i in [1,2,4]" ng-init="doSomething($last)"></div> $scope.doSomething = function(lastElement) { if(lastElem) blah blah blah } <div ng-repeat="i in [1,2,4]" ng-init="doSomething($last)"></div> $scope.doSomething = function(lastElement) { if(lastElem) blah blah blah } - JSAddict

4

当你检查scope.$last变量时,可能需要用setTimeout(someFn, 0)包装你的触发器。在javascript中,setTimeout 0是一种被接受的技术,对于我的指令正确运行来说非常重要。


我发现了同样的问题。在backbone和angular中,如果你需要做一些类似于读取应用于DOM元素的CSS属性值的操作,我无法找到不使用timeout hack的方法。 - chovy

4

我用这种方式做了。

创建指令。

function finRepeat() {
    return function(scope, element, attrs) {
        if (scope.$last){
            // Here is where already executes the jquery
            $(document).ready(function(){
                $('.materialboxed').materialbox();
                $('.tooltipped').tooltip({delay: 50});
            });
        }
    }
}

angular
    .module("app")
    .directive("finRepeat", finRepeat);

在您将其添加到此ng-repeat标签中之后。
<ul>
    <li ng-repeat="(key, value) in data" fin-repeat> {{ value }} </li>
</ul>

并已准备好在ng-repeat结束时运行。


4
<div ng-repeat="i in items">
        <label>{{i.Name}}</label>            
        <div ng-if="$last" ng-init="ngRepeatFinished()"></div>            
</div>

我的解决方案是为重复的最后一个项目添加一个div,并调用一个函数。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接