当ng-repeat完成时调用一个函数

我试图实现的基本上是“在重复完成渲染”处理程序。 我能够检测到什么时候完成,但我不知道如何从中触发function。

检查小提琴: 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> 

:从finishingmove工作小提琴: http : //jsfiddle.net/paulocoelho/BsMqq/4/

 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确保在ng-repeated元素已经完成渲染时执行它(因为$timeout将在当前摘要循环结束时执行 – 它也$apply内部调用$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> 

如果你希望你的callback函数 (即test())在构造DOM之后,但是在浏览器渲染之前被执行,可以使用$ evalAsync 。 这将防止闪烁 – ref 。

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

小提琴 。

如果您真的想在渲染后调用callback函数,请使用$ timeout:

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

我更喜欢$ eval而不是事件。 有了一个事件,我们需要知道事件的名称,并为该事件添加代码给我们的控制器。 使用$ eval,控制器和指令之间的耦合就会减less。

到目前为止所给出的答案只会在ng-repeat被渲染的时候才起作用 ,但是如果你有一个dynamic的ng-repeat ,这意味着你要添加/删除/过滤项目,你需要每当ng-repeat被渲染的时候都会收到通知,这些解决scheme将不适合你。

所以, 如果每次都需要通知ng-repeat被重新渲染,而不是第一次,我已经find了一种方法来做到这一点,这是相当“黑客”,但如果你知道的话,它会工作的很好你在做什么。 在使用任何其他$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; }; }) 

每当ng-repeat被渲染时,这将$emit一个名为ngRepeatFinished的事件。

如何使用它:

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

ngRepeatFinishfilter需要直接应用于$scope定义的ArrayObject ,您可以在其后应用其他filter。

怎么不使用它:

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

不要先应用其他filter,然后应用ngRepeatFinishfilter。

我应该什么时候使用这个?

如果您想在列表完成呈现后将某些CSS样式应用于DOM,因为您需要考虑由ng-repeat重新呈现的DOM元素的新维度。 (顺便说一下,这些操作应该在指令内完成)

在处理ngRepeatFinished事件的函数中不要做什么:

  • 不要执行$scope.$apply在这个函数中,否则你会把Angular放在Angular无法检测的无限循环中。

  • 不要用它在$scope属性中进行更改,因为这些更改不会反映在视图中,直到下一个$digest循环,并且由于您不能执行$scope.$apply它们将不会任何使用。

“但filter并不意味着要这样使用!”

不,他们不是,这是一个黑客,如果你不喜欢它不使用它。 如果你知道一个更好的方法来完成同样的事情,请让我知道它。

总结

这是一个黑客 ,并以错误的方式使用它是危险的,使用它只适用于ng-repeat已完成渲染后的应用样式,你不应该有任何问题。

如果你需要在同一个控制器上调用不同的ng-repeat的函数,你可以尝试如下的方法:

指令:

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

在你的模板与多个ng-repeat

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

其他解决scheme在初始页面加载时工作正常,但是从控制器调用$ timeout是确保在模型更改时调用函数的唯一方法。 这是一个使用$超时工作的小提琴 。 举例来说,这将是:

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

当行内容是新的时,ngRepeat将只评估一个指令,所以如果你从你的列表中删除项目,onFinishRender不会触发。 例如,尝试在这些小提琴发出inputfilter值。

如果你不反对使用双美元的范围道具,而你正在编写一个指令,其唯一的内容是重复,那么有一个非常简单的解决scheme(假设你只关心初始渲染)。 在链接function中:

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

这对于需要根据呈现列表的成员的宽度或高度来做DOM操作的指令(我认为这是最可能的原因之一)来说是非常有用的,但它不是通用的作为已经提出的其他解决scheme。

用过滤的ngRepeat解决这个问题的办法可能是使用突变事件,但不推荐使用(不要立即replace)。

然后我想到了另一个简单的问题:

 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方法中,用一个单一的$超时来监视连续的修改。

它的工作主要是正确的,但我确实得到了一些情况下,altDone将被调用两次。

再次…将此指令添加到ngRepeat的级。

请看一下小提琴, http://jsfiddle.net/yNXS2/ 。 由于您创build的指令没有创build一个新的范围我继续在这种方式。

$scope.test = function(){...发生。

很容易,这是我做到的。

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