ng-repeat完成事件


207

我想用表调用一些针对div的jQuery函数。该表填充了ng-repeat

当我打电话时

$(document).ready()

我没有结果

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

没有帮助。

ng-repeat填充完成后,有什么方法可以执行功能?我已经阅读了有关使用custom的建议directive,但是我不知道如何在ng-repeat和div中使用它。

Answers:


241

确实,您应该使用指令,并且没有与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中查看它的实际操作。希望能帮助到你!


我可以myMainDirective只在循环结束后执行的代码吗?我需要更新父div的滚动条。
ChruS 2012年

2
当然!只需将“ window.alert”更改为事件触发函数,并使用main指令捕获它即可。我以de Plunker为例进行了更新。
TiagoRoldão2012年

9
建议先包含jQuery库(在Angular之前)。这将导致所有Angular元素都被jQuery包装(而不是jqLit​​e)。然后,只需编写element.css()和element.children()。css()即可,而不是angular.element(element).css()和$(element).children()。css()。另请参阅groups.google.com/d/msg/angular/6A3Skwm59Z4/oJ0WhKGAFK0J
Mark Rajcok 2012年

11
Any1知道如何使它在ngRepeat指令上的后续渲染中起作用吗?即:链接
RavenHursT 2014年

1
这是所有角度指令的命名约定。参见docs.angularjs.org/guide/directive#normalization
TiagoRoldão2015年

68

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

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

观看现场演示。


如果我使用控制器作为语法,这行得通吗?请?否,还有其他与控制器兼容的方式吗?
丹阮

63

无需创建指令,尤其是仅具有ng-repeat完整事件的指令。

ng-init 为你做魔术。

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

$last做肯定的,那finished只有被炒鱿鱼,当最后一个元素已经被渲染到了DOM。

不要忘记创建$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()">

如果“物”变成空数组怎么办?
Frane Poljak

1
@FranePoljak我已经在答案中添加了解决方案。
维卡斯·班萨尔

1
如果是空数组,请在控制器中处理
-JSAddict

修复Vikas Bansal答案://如果要检查数组是否没有任何值,请使用first div并相应地调用该函数。<div ng-if =“!things.length” ng-hide =“ true” ng-init =“ finished()”> </ div> <div ng-repeat =“事物” ng-init =“ $ last &&
finish

41

这是一个重复执行指令,当为true时会调用指定的函数。我发现被调用的函数在执行DOM操作之前必须使用带有时间间隔= 0的$ timeout,例如在呈现的元素上初始化工具提示。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”解决方案,但是没有人解释它为什么起作用。

30

这是一种ng-init甚至不需要自定义指令的简单方法。在某些情况下,这对我来说效果很好,例如需要在页面加载时自动将ng重复的div项自动滚动到特定项,因此滚动功能需要等到ng-repeatDOM渲染完毕后才能触发。

<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}}文本,然后在注入数据绑定的model.value时取消选择它们。
JoshGough

27

作为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

使用ngInitLodash的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”部分,在最后一次发信后调用此方法之前的毫秒数是吗?
耶尔SKÄR菲舍尔

去抖动链接之前添加了Lodash。是的,方法执行之前有20个延迟-更改为0,因为只要调用是异步的,都没有关系。
Pavel Horal 2014年

1
还要考虑这一点,因为表达式中允许使用三元运算符,简单ng-init="$last ? refresh() : false"也可以。甚至不需要Lodash。
Pavel Horal 2014年

<div ng-repeat =“ i in [1,2,4]” ng-init =“ doSomething($ last)”> </ div> $ scope.doSomething = function(lastElement){if(lastElem)等等等等}
JSAddict '16

4

当您检查scope.$last变量以将触发器换行时,也可能有必要setTimeout(someFn, 0)。A setTimeout 0是javascript中可接受的技术,必须directive正确运行。


我发现了同样的问题。无论是在主干还是在角度,如果您需要执行读取应用于DOM元素的css属性值之类的操作,我都找不到解决超时黑客的方法。
chovy

3

这是对其他答案中表达的思想的改进,目的是展示使用声明性语法并隔离范围时如何访问ngRepeat属性($ index,$ first,$ middle,$ last,$ even,$ odd)。 Google建议使用带有元素指令的最佳做法。请注意主要区别:scope.$parent.$last

angular.module('myApp', [])
.directive('myRepeatDirective', function() {
  return {
    restrict: 'E',
    scope: {
      someAttr: '='
    },
    link: function(scope, element, attrs) {
      angular.element(element).css('color','blue');
      if (scope.$parent.$last){
        window.alert("im the last!");
      }
    }
  };
});

将指令用作属性而不是元素不是更好的主意吗?即限制:“ A”?
Tejasvi Hegde

2
关键是要显示声明性语法+隔离范围...是的,如果需要,欢迎在此处使用属性指令。有一个时间和地点,取决于您的目的。
JoshuaDavid

3

我是这样做的。

创建指令

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结束时运行。


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

我的解决方案是,如果该项是重复项中的最后一项,则添加一个div来调用函数。


2

我想添加另一个答案,因为前面的答案都认为ngRepeat完成后需要运行的代码是角度代码,在这种情况下,上述所有答案都提供了一个很好而简单的解决方案,其中一些比其他的更通用,如果它对摘要生命周期阶段很重要,那么您可以查看Ben Nadel的博客,但使用$ parse而不是$ eval除外。

但是以我的经验,正如OP所言,它通常在最终编译的DOM上运行一些JQuery插件或方法,在这种情况下,我发现最简单的解决方案是使用setTimeout创建指令,因为setTimeout函数被推送了到浏览器队列的末尾,它总是在以角度完成所有操作之后,通常是ngReapet,它在其父级postLinking函数之后继续

angular.module('myApp', [])
.directive('pluginNameOrWhatever', function() {
  return function(scope, element, attrs) {        
    setTimeout(function doWork(){
      //jquery code and plugins
    }, 0);        
  };
});

对于想知道在那种情况下为什么不使用$ timeout的人,它会导致另一个完全不需要的摘要循环


1

ng-repeat结束后,我不得不使用MathJax渲染公式,以上答案都不能解决我的问题,因此我做了如下所示。这不是一个很好的解决方案,但是对我有用。

<div ng-repeat="formula in controller.formulas">
    <div>{{formula.string}}</div>
    {{$last ? controller.render_formulas() : ""}}
</div>

0

我在这里找到了一个很好的答案,但是仍然有必要增加延迟

创建以下指令:

angular.module('MyApp').directive('emitLastRepeaterElement', function() {
return function(scope) {
    if (scope.$last){
        scope.$emit('LastRepeaterElement');
    }
}; });

将其作为属性添加到中继器,如下所示:

<div ng-repeat="item in items" emit-last-repeater-element></div>

根据Radu所说:

$scope.eventoSelecionado.internamento_evolucoes.forEach(ie => {mycode});

对我来说,它可以工作,但是我仍然需要添加setTimeout

$scope.eventoSelecionado.internamento_evolucoes.forEach(ie => {
setTimeout(function() { 
    mycode
}, 100); });

-4

如果您只是想更改类名以使其呈现不同的样式,则可以使用下面的代码来解决。

<div>
<div ng-show="loginsuccess" ng-repeat="i in itemList">
    <div id="{{i.status}}" class="{{i.status}}">
        <div class="listitems">{{i.item}}</div>
        <div class="listitems">{{i.qty}}</div>
        <div class="listitems">{{i.date}}</div>
        <div class="listbutton">
            <button ng-click="UpdateStatus(i.$id)" class="btn"><span>Done</span></button>
            <button ng-click="changeClass()" class="btn"><span>Remove</span></button>
        </div>
    <hr>
</div>

当我有类似的要求以Strick低谷字体呈现购物清单中的购物商品时,此代码对我有用。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.