angularjs – 如何在$compile之后删除由angular指令创建的观察者

在角度打开多次模态时我遇到了一些性能问题,我发现每次要求对话框服务创建一个新模态时,范围内的观察者数量会急剧增加.

DialogService如下:

.factory("DialogService", function($q, $compile){
        return {
            toast:function(text){
                Dialog.toast(text);
            },
            alert:function(text){
                var deferred = $q.defer();
                var d = Dialog.alert(text);
                d.bind("hide", function(){
                    deferred.resolve();
                });
                return deferred.promise;
            },
            showModal:function(options){
                var dialog = new Dialog(options);
                dialog.show(function(){
                    var self = this;
                    if ( "scope" in options ) $compile(self.contentLayer)(options.scope);

                    options.scope.dismiss = function(){
                        dialog.hide();
                    }   
                });
                if ( "hide" in options ){
                    dialog.bind("hide", options.hide);
                }
            },...

我面临的问题是,每次创建新模态时,它都会在使用指令时使用新观察者来调整范围.

我通过添加观察者来解决大部分问题,这些观察者在我的自定义指令中销毁节点时删除了观察者,但ng-repeat,ng-if等等……在模态内容中,每次调用showModal时都会添加观察者.

现在,我甚至不确定应该尝试什么方法.

现在我试图删除用对话框创建的观察者,但我没有成功,所以我将不胜感激任何帮助.

最佳答案 花了我一些时间,但最后我能够实现理想的行为

首先,不是直接将范围传递给对话框服务,如问题所示,最好从选项中传递的范围创建子范围.

这样,每次调用$compile来构建模态时,它都不会用监视器和其他东西污染父作用域,并且当模态关闭时很容易破坏新创建的作用域.

生成的服务代码如下所示:

angular.module("app", [])
    .factory("DialogService", function($q, $compile){
        return {
            toast:function(text){
                Dialog.toast(text);
            },
            alert:function(text){
                var deferred = $q.defer();
                var d = Dialog.alert(text);
                d.bind("hide", function(){
                    deferred.resolve();
                });
                return deferred.promise;
            },
            showModal:function(options){
                var childScope;
                var dialog = new Dialog(options);
                dialog.show(function(){
                    var self = this;    
                    if ( "scope" in options ){
                        var childScope = options.scope.$new();
                        $compile(self.contentLayer)(childScope);

                        options.scope.dismiss = function(){ 
                            dialog.hide();
                        }

                        dialog.bind("hide", function(){
                            childScope.$destroy();
                        });
                    }   
                });
                if ( "hide" in options ){
                    dialog.bind("hide", options.hide);
                }   
            },
            confirm:function(text){
                var deferred = $q.defer();
                Dialog.confirm(text, function(){
                    deferred.resolve();
                }, function(){
                    deferred.reject();
                });
                return deferred.promise;
            }
        }
    })
;

额外

此时,我以为我解决了这个问题,但是通过控制台日志我可以看到每次打开一个新对话框时仍会运行越来越多的摘要周期,尽管这次手表保持不变.

发生的事情是我有很多自定义指令;我非常小心在每个指令的链接函数末尾添加以下行,以避免在上述情况下出现性能问题:

var watchers = [
     $scope.$watch(...),
     $scope.$watch(...),
     ...
];
...
var observer = new MutationObserver(function(mutations) {
    if (!document.body.contains($element[0])){
        observer.disconnect();
        dropdown.remove();
        for ( var i = 0; i < watchers.length; i++ ){
            watchers[i]();
        }
        $scope.$destroy();
        return;
    }
});  
var config = { childList: true, subtree: false  /*attributes: true, characterData: true*/ };
observer.observe(document.querySelector('body'), config);

看起来很好,对吗?嗯……大部分时间都有效,但是当我在指令中有这样的东西时(就是这种情况):

var clickHandler = function(event){
    var isChild = $($element).has(event.target).length > 0 || $(dropdown).has(event.target).length > 0;
    var isSelf = $element[0] == event.target || dropdown == event.target;

    $scope.$apply(function(){
        if (!isChild && !isSelf) {
            $scope.mdSelectCtrl.dismiss();
        }
    }); 
}

$document.bind('click', clickHandler);

它是在每个$compile上添加click事件,click事件导致另一个摘要周期(即使范围已经被破坏,此时也不想深入挖掘);考虑到我可以在模态内容中轻松使用相同的指令10-15次,这意味着每次调用$compile都会导致在每次点击时出现大量的摘要周期,从而导致性能下降.

解决方案很简单:在销毁指令范围时删除DOM事件侦听器:

var observer = new MutationObserver(function(mutations) {
    if (!document.body.contains($element[0])){
        observer.disconnect();
        dropdown.remove();
        for ( var i = 0; i < watchers.length; i++ ){
            watchers[i]();
        }
        $scope.$destroy();
        $document.unbind('click', clickHandler);
        return;
    }
});  

注意

更改对话框服务后,我意识到可能不需要MutationObserver来跟踪节点是否仍然存在,我可以使用:

$scope.$on("destroy")

但我不想让今天更改代码来测试它.

点赞