Events ng repeat完成时调用函数

Events ng repeat完成时调用函数,events,angularjs,handler,directive,ready,Events,Angularjs,Handler,Directive,Ready,我试图实现的基本上是一个“on ng repeat finished rendering”处理程序。我能够检测何时完成,但我不知道如何从中触发函数 检查小提琴: JS HTML 在finish render=“test()”>{{t} 回答: 完成后的工作小提琴移动:请看一下小提琴。因为您创建的指令没有创建新的作用域,所以我继续这样做 $scope.test=function(){…实现了这一点 var module = angular.module('testApp', []) .

我试图实现的基本上是一个“on ng repeat finished rendering”处理程序。我能够检测何时完成,但我不知道如何从中触发函数

检查小提琴:

JS

HTML


在finish render=“test()”>{{t}

回答
完成后的工作小提琴移动:

请看一下小提琴。因为您创建的指令没有创建新的作用域,所以我继续这样做

$scope.test=function(){…
实现了这一点

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>

{{item.name}}
如果您希望在构建DOM之后但在浏览器呈现之前执行回调(即test()),请使用。这将防止闪烁--

如果确实要在渲染后调用回调,请使用$timeout:

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

我更喜欢$eval而不是事件。对于事件,我们需要知道事件的名称并为该事件向控制器添加代码。对于$eval,控制器和指令之间的耦合更少。

其他解决方案在初始页面加载时可以正常工作,但从控制器调用$timeout是确保当模型更改时调用函数。下面是一个使用$timeout的工作。例如:

.controller('myC', function ($scope, $timeout) {
$scope.$watch("ta", function (newValue, oldValue) {
    $timeout(function () {
       test();
    });
});
<div ng-app="testApp" ng-controller="myC">
    <p ng-repeat="t in ta" ng-init="$last && test()">{{t}}</p>
</div>

ngRepeat仅在行内容为新内容时对指令求值,因此如果从列表中删除项,则不会触发onFinishRender。例如,尝试在这些小提琴中输入筛选值。

到目前为止给出的答案仅在第一次呈现
ng repeat
时有效,但如果您有一个动态的
ng repeat
,这意味着您将要添加/删除/筛选项目,并且每次呈现
ng repeat
时都需要得到通知,这些解决方案将不适用于您

因此,如果您需要在每次重新呈现
ng repeat
时得到通知,而不仅仅是第一次,我已经找到了一种方法,它非常“黑客”,但是如果您知道自己在做什么,它会很好地工作。在使用任何其他
$filte>之前,在
ng repeat
中使用此
$filter
r

.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
的事件

如何使用它: 不要先应用其他过滤器,然后再应用
ngRepeatFinish
过滤器

我什么时候用这个? 如果您希望在列表完成渲染后将某些css样式应用到DOM中,因为您需要考虑由
ng repeat
重新渲染的DOM元素的新维度(顺便说一句:此类操作应在指令内完成)

在处理
ngRepeatFinished
事件的函数中不应执行的操作:
  • 不要在该函数中执行
    $scope.$apply
    ,否则您会将Angular放入Angular无法检测到的无尽循环中

  • 不要使用它在
    $scope
    属性中进行更改,因为这些更改直到下一个
    $digest
    循环才会反映在视图中,并且因为您无法执行
    $scope.$apply
    所以它们没有任何用处

“但是过滤器不应该这样使用!!” 不,他们不是,这是一个黑客,如果你不喜欢它,不要使用它。如果你知道一个更好的方法来完成同样的事情,请让我知道

总结 这是一种黑客行为,以错误的方式使用它是危险的,仅在
ng repeat
完成渲染后应用样式时使用它,您不应该有任何问题


使用过滤的ngRepeat解决此问题的方法可能是使用事件,但它们已被弃用(无需立即替换)

然后我想到了另一个简单的方法:

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');
            }
        }
    };
});
这将通过一个tick$timeout钩住父元素的Node.prototype方法,以监视后续修改

它的工作原理基本上是正确的,但我确实遇到过一些情况,altDone会被调用两次


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

如果需要为同一控制器上的不同ng Repeat调用不同的函数,可以尝试以下操作:

<div ng-repeat="item in items" on-finish-render="ngRepeatFinished">
    <div>{{item.name}}}<div>
</div>
指令:

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');
            });
            }
        }
    }
});
$scope.$on('ngRepeatBroadcast1', function(ngRepeatFinishedEvent) {
// Do something
});

$scope.$on('ngRepeatBroadcast2', function(ngRepeatFinishedEvent) {
// Do something
});
在控制器中,捕捉$on的事件:

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

{{item.name}}
{{item.name}}

非常简单,我就是这样做的

指令('blockOnRender',函数($blockUI){
返回{
限制:“A”,
链接:函数(范围、元素、属性){
如果(范围$first){
$blockUI.blockElement($(element.parent());
}
如果(范围$last){
$blockUI.unblockElement($(element.parent());
}
}
};

})
如果您不反对使用双美元范围道具,并且您编写的指令的唯一内容是重复,那么有一个非常简单的解决方案(假设您只关心初始值)
<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>
const dereg = scope.$watch('$$childTail.$last', last => {
    if (last) {
        dereg();
        // do yr stuff -- you may still need a $timeout here
    }
});
ng-init="$last && test()"
<div ng-app="testApp" ng-controller="myC">
    <p ng-repeat="t in ta" ng-init="$last && test()">{{t}}</p>
</div>
$scope.test = function test() {
    console.log("test executed");
}