检查小提琴: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>
答案:
从finishmove的工作提琴:http://jsfiddle.net/paulocoelho/BsMqq/4/
#1 楼
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重复的元素真正完成渲染后执行(因为$timeout
将在当前摘要周期的末尾执行-并且也将在内部调用$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>
评论
+1,但我会在使用事件之前使用$ eval -减少耦合。请参阅我的答案以获取更多详细信息。
– Mark Rajcok
13年4月4日在19:21
@PigalevPavel我认为您将$ timeout(基本上是setTimeout + Angular的$ scope。$ apply())与setInterval混淆了。 $ timeout对于每个if条件仅执行一次,并且将在下一个$ digest周期的开始执行。有关JavaScript超时的更多信息,请参见:ejohn.org/blog/how-javascript-timers-work
–全息原理
13年7月11日在16:06
@finishingmove也许我听不懂:)但是,看看我在另一个ng-repeat中有ng-repeat。我想确定所有的完成时间。当我将您的脚本与$ timeout一起用于父级ng-repeat时,一切正常。但是,如果我不使用$ timeout,则在ng-repeat子项完成之前会得到响应。我想知道为什么吗?而且我可以确定,如果我将您的脚本与$ timeout一起用于父级ng-repeat,那么当所有ng-repeats完成后,我总是会得到响应吗?
– Pigalev Pavel
13年7月11日在16:17
为什么要使用诸如setTimeout()的延迟为0的问题是另一个问题,尽管我必须说,我从未在任何地方遇到过有关浏览器事件队列的实际规范,而单线程性和setTimeout()的存在暗示了什么。
– rakslice
2013年12月4日20:10
感谢您的回答。我有一个问题,为什么我们需要分配on-finish-render =“ ngRepeatFinished”?当我分配on-finish-render =“ helloworld”时,它的工作原理相同。
– Arnaud
2015年4月24日4:18
#2 楼
如果您希望在构造DOM之后但在浏览器呈现之前执行回调(即test()),请使用$ evalAsync。这将防止闪烁-ref。if (scope.$last) {
scope.$evalAsync(attr.onFinishRender);
}
小提琴。
如果确实要在渲染后调用回调,请使用$ timeout:
if (scope.$last) {
$timeout(function() {
scope.$eval(attr.onFinishRender);
});
}
我更喜欢$ eval而不是事件。对于事件,我们需要知道事件的名称,并将代码添加到该事件的控制器中。使用$ eval,控制器和指令之间的耦合更少。
评论
$ last的支票有什么作用?在文档中找不到它。它被删除了吗?
–埃里克·艾格纳(Erik Aigner)
2013年6月5日10:18
@ErikAigner,如果ng-repeat在元素上处于活动状态,则在作用域上定义$ last。
– Mark Rajcok
2013年6月5日14:14
看来您的小提琴正在不必要的$ timeout中。
– bbodenmiller
2014年4月28日上午8:16
大。从性能的角度来看,发射/广播的成本更高,这应该是公认的答案。
–弗洛林·维斯蒂格(Florin Vistig)
16-09-30在8:27
#3 楼
到目前为止,已经给出的答案仅在第一次显示ng-repeat
时起作用,但是如果您具有动态ng-repeat
,则意味着您将要添加/删除/过滤项目,并且每次都需要通知ng-repeat
被渲染,那么这些解决方案将对您不起作用。因此,如果您需要每次都被告知
ng-repeat
被重新渲染而不仅仅是第一次通知您,我已经找到了一种方法这样做,确实很“ hacky”,但是如果您知道自己在做什么,它将很好地工作。在使用任何其他$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
时,都会ngRepeatFinished
一个名为ng-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,因为您需要考虑到
ng-repeat
已重新渲染的DOM元素的新尺寸。 (顺便说一句:这类操作应在指令内完成)在处理
ngRepeatFinished
事件的函数中不可以做的事情:不要执行
$scope.$apply
函数,否则您将Angular陷入Angular无法检测的无限循环中。请勿使用它对
$scope
属性进行更改,因为这些更改不会会在您的视图中反映出来,直到下一个$digest
循环为止,并且由于您无法执行$scope.$apply
,因此它们将无用。“但是不打算使用过滤器就是这样!“
不,他们不是,这是一个hack,如果您不喜欢它,请不要使用它。如果您知道完成同一件事的更好方法,请告诉我。
总结
这是一个技巧,以错误的方式使用它是危险的,请仅将其用于申请
ng-repeat
完成渲染后的样式,您应该不会有任何问题。评论
Thx 4小费。无论如何,一个有工作实例的小伙子将不胜感激。
–全息
2014年11月5日,9:06
不幸的是,这至少对最新的AngularJS 1.3.7无效。因此,我发现了另一种解决方法,而不是最好的解决方法,但是如果这对您来说必不可少的话,它将可以使用。我所做的是每当我添加/更改/删除元素时,我总是将另一个虚拟元素添加到列表的末尾,因此,由于$ last元素也已更改,因此通过检查$ last来执行简单的ngRepeatFinish指令现在可以使用。一旦ngRepeatFinish被调用,我就删除了虚拟物品。 (我也用CSS隐藏了它,所以它不会短暂出现)
–黑暗
2014-12-16 19:29
这个技巧不错,但并不完美;每次在事件中启动过滤器时,都会分派五次:-/
–Sjeiti
2015年4月22日在7:36
马克·拉杰科克(Mark Rajcok)的以下文章在每次重新渲染ng-repeat时都可以正常工作(例如,由于模型更改)。因此,不需要此解决方案。
– Dzhu
2015年10月1日在9:16
@Josep你是对的-当项目被拼接/未移位时(即数组发生突变),它不起作用。我之前的测试可能是使用已重新分配(使用sugarjs)的数组,因此它每次都有效...感谢您指出这一点
– Dzhu
2015年10月16日,0:52
#4 楼
如果需要在同一控制器上为不同的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
});
在具有多个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>
#5 楼
其他解决方案可以在初始页面加载时正常工作,但是从控制器调用$ timeout是确保在模型更改时调用函数的唯一方法。这是使用$ timeout的有效提琴。对于您的示例,它是:.controller('myC', function ($scope, $timeout) {
$scope.$watch("ta", function (newValue, oldValue) {
$timeout(function () {
test();
});
});
ngRepeat仅在行内容是新内容时才评估指令,因此,如果从列表中删除项目,则不会触发onFinishRender 。例如,尝试在这些小提琴中发出过滤器值。
评论
同样,当模型更改小提琴时,在evalAsynch解决方案中并不总是调用test()。
–约翰·哈雷
2014年1月31日,1:50
$ scope。$ watch(“ ta”,...是什么?
–穆罕默德·阿德埃尔·扎希德(Muhammad Adeel Zahid)
2014年12月29日19:50
#6 楼
如果您不反对使用双美元范围的道具,并且正在编写仅重复内容的指令,那么有一个非常简单的解决方案(假设您只关心初始渲染)。在链接函数中:const dereg = scope.$watch('$$childTail.$last', last => {
if (last) {
dereg();
// do yr stuff -- you may still need a $timeout here
}
});
这在您有一个需要根据渲染的成员的宽度或高度进行DOM操作的指令的情况下非常有用。列表(我认为这是最可能会问这个问题的原因),但是它不像已经提出的其他解决方案那样通用。
#7 楼
我很惊讶没有看到这个问题的答案中最简单的解决方案。您要做的是在重复的元素(带有
ngInit
指令的元素)上添加ngRepeat
指令,以检查$last
(a ngRepeat
在范围内设置的特殊变量,表示重复的元素是列表中的最后一个)。如果$last
为true,则我们正在渲染最后一个元素,然后可以调用所需的函数。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
,因此您除了要调用的作用域函数(在本例中为ngInit
)之外,不需要任何其他JS代码。 。只需确保在范围内具有test
函数,以便可以从模板对其进行访问:$scope.test = function test() {
console.log("test executed");
}
#8 楼
解决此问题的方法可能是使用经过过滤的ngRepeat进行Mutation事件,但不建议使用它们(不立即替换)。然后我想到了另一个简单的方法:
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的父级。
评论
到目前为止,这种方法效果最好,但是还不完美。例如,我从70项增加到68项,但没有触发。
–盖伊
2015年5月14日17:17
在上面的代码中,也可以添加appendChild(因为我只使用了insertBefore和removeChild)。
–Sjeiti
15年5月15日在7:49
#9 楼
很简单,这就是我的方法。 .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
16年1月28日在16:26
#10 楼
请查看小提琴,http://jsfiddle.net/yNXS2/。由于您创建的指令没有创建新的作用域,因此我继续这样做。$scope.test = function(){...
做到了这一点。评论
摆弄错了吗?与问题中的相同。
–詹姆斯M
13年4月4日在18:15
哼,你更新小提琴了吗?当前显示的是我原始代码的副本。
– PCoelho
13年4月4日在18:16
评论
出于好奇,element.ready()代码段的目的是什么?我的意思是..是您拥有的某种jQuery插件,还是应该在元素准备就绪时触发它?可以使用ng-init
这样的内置指令来做到这一点
ng-repeat完成事件的可能重复
stackoverflow.com/questions/13471129 / ...的副本,请在此处查看我的答案