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

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

评论

出于好奇,element.ready()代码段的目的是什么?我的意思是..是您拥有的某种jQuery插件,还是应该在元素准备就绪时触发它?

可以使用ng-init
这样的内置指令来做到这一点
ng-repeat完成事件的可能重复

stackoverflow.com/questions/13471129 / ...的副本,请在此处查看我的答案

#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过滤器需要直接应用于ArrayObject中定义的$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