【问题标题】:How to safely clean up AngularJS event binding in a directive如何安全地清理指令中的 AngularJS 事件绑定
【发布时间】:2014-05-26 17:14:11
【问题描述】:

我有一个 Angular 指令,它将元素的高度设置为等于浏览器窗口的内部高度(+/- 给定的偏移量)。该指令响应窗口的“resize”事件并相应地调整其高度。当我的指令的范围发出 '$destory' 事件时,我删除了对“resize”事件的绑定(我认为将其保留在适当的位置会导致一些问题,如果我错了,请纠正我)。

我不知道如何以“安全”的方式进行此事件分离。如果我在整个应用程序中有多个该指令的实例,并且如果我有附加到“调整大小”事件的其他指令怎么办?

JQuery 具有事件命名空间的概念,这似乎是一个很好的解决方案,但 Angular 的实现 (JQLite) does not support this。我宁愿不使用 JQuery,因为我已经在使用 Angular,那我该怎么办?

这是我今天的指令的代码

window.angular.module('arcFillClient', [])
    .directive('arcFillClientY', ['$window',
        function ($window) {

            function link($scope, el, attrs) {

                var setHeight,
                    onResize,
                    cleanUp;

                setHeight = function (offSetY) {
                    var newHeight;
                    offSetY = offSetY || 0;
                    newHeight = Math.max($window.innerHeight + parseInt(offSetY, 10)) + 'px';
                    el.height(newHeight);
                };

                onResize = function () {
                    var offset;
                    offset = attrs.arcFillClientY || 0;
                    setHeight(offset);
                };

                attrs.$observe('arcFillClientY', setHeight);
                window.angular.element($window).on('resize', onResize);

                cleanUp = function () {
                    window.angular.element($window).off('resize');
                };

                $scope.$on('$destroy', cleanUp);
            }
            return {
                link: link
            };

更新 看起来像 RTFM 的情况,但以防万一其他人在这里徘徊,这里有更多信息。将原始函数(在我的情况下为 OnResize)传递给 .off() 可以隔离 .off() 函数的范围。来自docs

也可以通过在处理程序参数中指定函数名称来删除处理程序。当 jQuery {ahem... JQLite} 附加一个事件处理程序时,它会为处理程序函数分配一个唯一的 id。

这是我指令中更新的cleanUp 函数:

cleanUp = function () {
    window.angular.element($window).off('resize', onResize);
};

感谢 tasseKATT、Karolis 和 Hans 的贡献。

【问题讨论】:

    标签: javascript angularjs


    【解决方案1】:

    将相同的函数引用传递给off,就像传递给on

    window.angular.element($window).off('resize', onResize);
    

    代替:

    window.angular.element($window).off('resize');
    

    演示 - 将函数引用传递给 offhttp://plnkr.co/edit/1rfVPNXl6TrEcuYvzPAj?p=preview

    演示 - 不将函数引用传递给 offhttp://plnkr.co/edit/IsLqSLAzNcHqDnhMty7Q?p=preview

    演示包含两个监听窗口调整大小事件的指令。使用代码和预览之间的垂直分隔符来触发事件。

    您会注意到,如果您销毁其中一个,则在将函数引用传递给 off 时,另一个将继续工作。否则,两者都将停止工作。

    【讨论】:

    • 有兴趣的人可以了解有关该主题的更多信息:stackoverflow.com/a/27016855/2887841
    • 我对此很在意。当您将指令应用于 10 个元素时,这是否意味着窗口有 10 个带有 onResize 函数的调整大小事件侦听器?还是每次设置时都会覆盖onResize函数......
    【解决方案2】:

    几周前我也有同样的问题。

    查看jqLit​​e源码(https://github.com/angular/angular.js/blob/master/src/jqLite.js)后,我们看到on方法添加了事件,off方法通过jqLiteOff函数删除了事件。

    深入观察,我们看到jqLiteRemoveData 调用jqLiteOffjqLiteRemoveDatajqLiteDealoc 调用。 jqLiteDealoc 在几个地方被调用:jqLiteEmptyhtmlreplaceWithremovejqLiteEmpty 被分配给元素的 empty 方法,该方法清除 jQuery 中的元素。 htmlreplaceWithremove 是 jQuery 模仿对象。

    搜索remove() 在元素上的调用位置,我们发现它用于大多数(如果不是全部)DOM 操作逻辑。您可以在ngIfngSwitchngIncludengView 中看到它。

    所以我认为 Angular 确实会处理事件侦听器清理,只要您使用 jqLit​​e 附加事件并在您自己的 DOM 操作逻辑中适当地调用 remove()。使用 jQuery 包装元素会搞乱很多流程,包括事件侦听器清理,但我想您已经完全意识到这一点,因为您使用的是 angular.element

    【讨论】:

    • 汉斯,这是很有价值的见解,谢谢。我现在已经自己深入研究了源代码,似乎正如您所提到的,每当我使用 remove() (通过内置指令显式或隐式)时,Angular 都会清理事件侦听器。但是,由于我的侦听器在 $window 上(或者更确切地说是 JQLite 包装的版本),它可能永远不会被删除,对吧?这意味着当我的元素被销毁时,我最终会得到一个带有未定义处理程序的侦听器,不是吗?抱歉,如果我遗漏了什么。
    • 如果没有取消注册,你应该仍然有一个工作的监听器。监听器通过闭包引用的对象也永远不会被垃圾回收。
    【解决方案3】:

    首先,同时使用 JQuery 和 AngularJS 绝对没有错。

    除此之外,我喜欢做的是有一个body 指令来监听window.on('resize', ...) 并将大小写入$rootScope.windowSize。然后在元素上设置另一个指令 $watch("windowSize", ...) 并根据需要设置为宽度。 (您实际上不必在 $scope 中公开大小 - 您可以改为使用 require)。

    【讨论】:

    • 我并不是要暗示一起使用 JQuery 和 Angular 有任何“错误”,只是我现在更愿意避免使用它。我认为您的解决方案会起作用,但它依赖于应用程序和指令彼此了解太多。有没有一种解决方案可以让我的指令更加独立?
    • @BillSparks,应用程序和指令什么都不知道。这是两个相关的指令。一种不同的方法可以使用服务进行引用计数,但我认为 JQuery 是最明智的。
    • 对不起,我误用了“应用程序”这个词。我所说的要点是我希望我的指令能够被很好地封装,以便我可以分享它。所以,我不想创建额外的依赖项。另外,我认为您的解决方案只是将问题推高了一个层次。抱歉,如果我遗漏了什么。
    • @BillSparks,如果你使用两个指令,你只需要听一次 - 'top' 指令可以被多个 'bottom' 指令共享。如果你听一次,你也清理一次。尽管。 jqLit​​e 中的off 实际上不采用处理程序参数吗?文档只说“不支持命名空间或选择器”...如果您这样做了.off('resize', onResize) 会怎样?
    • Karolis,实际上你的最后一条评论是关键。将原始方法传递给“.off”将完成我想要的。谢谢!
    猜你喜欢
    • 2013-09-29
    • 1970-01-01
    • 2017-10-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-17
    • 2016-03-25
    相关资源
    最近更新 更多