【问题标题】:ng-repeat finish eventng-repeat 完成事件
【发布时间】:2012-11-08 09:32:46
【问题描述】:

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

当我调用它时

$(document).ready()

我没有结果。

还有

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

没用。

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

【问题讨论】:

    标签: javascript jquery angularjs angularjs-ng-repeat document-ready


    【解决方案1】:

    确实,您应该使用指令,并且没有事件与 ng-Repeat 循环的末尾相关联(因为每个元素都是单独构造的,并且有自己的事件)。但是 a) 使用指令可能就是您所需要的,并且 b) 您可以使用一些 ng-Repeat 特定属性来制作“on ngRepeat finished”事件。

    具体来说,如果您只想为整个表格设置样式/添加事件,您可以在包含所有 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 的滚动条。
    • 当然!只需将“window.alert”更改为事件触发函数,并使用 main 指令捕获它。例如,我更新了 de Plunker 来做到这一点。
    • 建议先包含 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
    • Any1 知道如何让它适用于 ngRepeat 指令的后续渲染吗?即:link
    • 这是所有角度指令的命名约定。见docs.angularjs.org/guide/directive#normalization
    【解决方案2】:

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

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

    See a live demo.

    【讨论】:

    • 如果我使用控制器作为语法,这是否有效?请?不是,还有其他方法可以与控制器一起使用吗?
    【解决方案3】:

    不需要创建指令,尤其是为了有一个 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()">
    

    【讨论】:

    • 如果“事物”变成了一个空数组怎么办?
    • @FranePoljak 我已经在答案中添加了解决方案。
    • 如果数组为空,则在控制器中处理
    • Fix Vikas Bansal 答案://如果要检查数组是否没有任何值,请使用第一个 div 并相应地调用函数。
    【解决方案4】:

    这是一个重复完成指令,它在为真时调用指定的函数。我发现被调用的函数必须在进行 DOM 操作之前使用间隔 = 0 的 $timeout,例如在渲染元素上初始化工具提示。 jsFiddle:http://jsfiddle.net/tQw6w/

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

    <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 所做的事情。
    • 您能解释一下为什么会这样吗?我正在尝试访问动态 ID,它仅在延迟为 0 的超时后才有效。我在几篇文章中看到了这个“hacky”解决方案,但没有人解释它为什么有效。
    【解决方案5】:

    这是一个使用ng-init 的简单方法,甚至不需要自定义指令。在某些情况下它对我来说效果很好,例如需要在页面加载时将 ng-repeated 项目的 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 内容时调用一个函数,那么您必须使用该线程上的其他答案之一和自定义指令。

    【讨论】:

    • 漂亮,这完成了工作。我想在文本框中自动选择文本,超时就成功了。否则,当数据绑定 model.value 被注入时,{{model.value}} 文本被选中,然后被取消选中。
    【解决方案6】:

    作为 Pavel 的 answer 的补充,更具可读性和易于理解的内容是:

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

    你为什么认为angular.noop首先存在......?

    优点:

    您不必为此编写指令...

    【讨论】:

    • 既然可以使用布尔逻辑,为什么还要使用三元:ng-init="$last &amp;&amp; doSomething( )"
    • 它更容易阅读@NateWhittaker(并且比三元运算符更难阅读令人印象深刻)。拥有干净、易于理解的代码是件好事
    【解决方案7】:

    使用 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”部分是调用此方法之前的毫秒数最后一次迭代?
    • 在 debounce 链接前添加了 Lodash。是的,20 是方法执行前的延迟 - 更改为 0,因为只要调用是异步的就没有关系。
    • 也在考虑这一点,因为表达式中允许使用三元运算符,简单的ng-init="$last ? refresh() : false" 也可以。这甚至不需要 Lodash。
    • $scope.doSomething = function(lastElement) { if(lastElem) blah blah blah }
    【解决方案8】:

    当您检查scope.$last 变量时,可能还需要使用setTimeout(someFn, 0) 包装您的触发器。 setTimeout 0 是 javascript 中公认的技术,我的 directive 必须正确运行。

    【讨论】:

    • 我发现了同样的问题。在骨干+角度中,如果您需要执行诸如读取应用于 DOM 元素的 css 属性值之类的操作,我无法找到没有超时黑客的方法。
    【解决方案9】:

    我是这样做的。

    创建指令

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

    【讨论】:

      【解决方案10】:
      <div ng-repeat="i in items">
              <label>{{i.Name}}</label>            
              <div ng-if="$last" ng-init="ngRepeatFinished()"></div>            
      </div>
      

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

      【讨论】:

        【解决方案11】:

        这是对其他答案中表达的想法的改进,以展示如何在使用声明性语法和使用元素指令隔离范围(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'?
        • 重点是显示声明性语法 + 隔离范围...是的,如果您愿意,欢迎您在这里使用属性指令。根据您的目的,有时间和地点。
        【解决方案12】:

        我想添加另一个答案,因为前面的答案认为在 ngRepeat 完成后需要运行的代码是一个角度代码,在这种情况下,上面的所有答案都提供了一个伟大而简单的解决方案,一些更通用比其他人更重要,如果摘要生命周期阶段很重要,您可以查看Ben Nadel's blog 关于它,除了使用 $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 的人来说,它会导致另一个完全没有必要的摘要循环

        【讨论】:

          【解决方案13】:

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

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

          【讨论】:

            【解决方案14】:

            我找到了一个答案here很好实践,但仍然需要添加延迟

            创建以下指令:

            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>
            

            根据拉杜:

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

            对我来说它有效,但我仍然需要添加一个 setTimeout

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

            【讨论】:

              【解决方案15】:

              如果您只是想更改类名以使其呈现不同,下面的代码可以解决问题。

              <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 trough 字体呈现购物清单中的购物项目时,此代码对我有用。

              【讨论】:

                猜你喜欢
                • 2016-10-07
                • 1970-01-01
                • 2014-11-13
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2013-05-19
                相关资源
                最近更新 更多