如何在 Angular 控制器之外删除 Cordova 特定事件?

Posted

技术标签:

【中文标题】如何在 Angular 控制器之外删除 Cordova 特定事件?【英文标题】:How to remove Cordova specific events outside Angular controller? 【发布时间】:2015-11-28 11:12:50 【问题描述】:

假设我有一个控制器来处理例如视图更改:

function Controller($scope)
    var viewModel = this;
    viewModel.goBack= function()
        viewModel.visible = visibleLinks.pop(); //get last visible link 
        viewModel.swipeDirection = 'left';// for view change animation
    

但我不仅想处理它,例如使用<body> 中的 html 按钮,还想使用设备上的后退按钮。所以我必须为deviceready 事件添加事件侦听器,并显式调用$scope.$apply(),以便在AngularJS 上下文之外调用它,如下所示:

document.addEventListener("deviceready", function()
        document.addEventListener("backbutton", function()
             viewModel.goBack();
             $scope.$apply();
         , false);
    , false);
 

但我也想关注(相对 :))新的controllerAssyntax,因为现在推荐这个,例如托德座右铭:Opinionated AngularJS styleguide for teams,它允许在不使用 $emit$on 之类的东西时从控制器中删除 $scope。但是我做不到,如果我必须调用$apply(),因为当用户单击设备后退按钮时,我的上下文不是 Angular 上下文。我考虑过创建一个Service,它可以作为cordova 的包装外观,并将$scope 注入此服务,但正如我在这里读到的:Injecting $scope into an angular service function() 这是不可能的。我看到了这个:Angular JS & Phonegap back button event 并且接受的解决方案还包含 $apply() 这使得 $scope 无法删除。任何人都知道在 Angular 控制器之外删除 Cordova 特定事件的解决方案,以便在不需要明确需要时从控制器中删除 $scope?提前谢谢你。

【问题讨论】:

【参考方案1】:

我看不出为什么要从控制器中删除 $scope。遵循最佳实践并在不需要时将其删除是可以的,但正如您所说,您仍然需要它来 $emit、$on、$watch.. 并且您可以肯定地将它添加到列表中 $apply()。

我在这里建议的替代解决方案是实现一个可以处理该问题的辅助函数。我们可以将它放在一个服务中并使用可注入的 $rootScope 服务。

app.factory('utilService', function ($rootScope) 

    return 
        justApply: function () 
            $rootScope.$apply();
        ,
        createNgAware: function (fnCallback) 
            return function () 
                fnCallback.apply(this, arguments);
                $rootScope.$apply();
            ;
        
    ;
); 
// use it   
app.controller('SampleCtrl', function(utilService) 

    var backBtnHandler1 = function () 
        viewModel.goBack();
        utilService.justApply(); // instead of $scope.$apply();
    
    // or
    var backBtnHandler2 = utilService.createNgAware(function() 
        viewModel.goBack();
    );
    document.addEventListener("backbutton", backBtnHandler2, false);
);

【讨论】:

【参考方案2】:

在我的例子中,我只是在 Angular $broadcast 的帮助下在 $rootScope 上触发 Cordova 事件。基本上任何应用程序控制器都会收到这个自定义事件。侦听器附加在配置阶段 - 在 run 块中,在任何控制器初始化之前。这是一个例子:

angular
.module('app', [])
.run(function ($rootScope, $document) 

    $document.on('backbutton', function (e) 
        // block original system back button behavior for the entire application
        e.preventDefault();
        e.stopPropagation();

        // forward the event
        $rootScope.$broadcast('SYSTEM_BACKBUTTON', e);
    );

)
.controller('AppCtrl', function ($scope) 

    $scope.$on('SYSTEM_BACKBUTTON', function () 
        // do stuff
       viewModel.goBack();
    );

);

显然,在$scope.$on 处理程序中,您不必调用$scope.$apply()

此解决方案的优点是:

在将事件广播到所有控制器之前,您可以修改事件或为整个应用程序执行其他操作; 当您每次实例化控制器时使用$document.on() 时,除非您手动取消订阅此事件,否则事件处理程序将保留在内存中;使用$scope.$on 会自动关心它; 如果系统调度 Cordova 事件的方式发生变化,您必须在一处进行更改

缺点:

在继承已经在初始化阶段附加了事件处理程序的控制器时,以及如果您希望在子级中使用自己的处理程序时,您必须小心。

监听器和转发器的放置位置取决于您,并且很大程度上取决于您的应用程序结构。如果您的应用程序允许,您甚至可以将 backbutton 事件的所有逻辑保留在 run 块中,并在控制器中删除它。组织它的另一种方法是指定附加到 $rootScope 的单个全局回调,例如,如果它们对后退按钮有不同的行为,则可以在控制器内部覆盖它,而不是弄乱事件。

虽然我不确定deviceready 事件,它在一开始就触发一次。在我的情况下,我首先等待deviceready 事件触发,然后手动引导 AngularJS 应用程序以提供应用程序的顺序加载并防止任何冲突:

document.addEventListener('deviceready', function onDeviceReady() 
    angular.element(document).ready(function () 
        angular.bootstrap(document.body, ['app']);
    );
, false);

从我的角度来看,应用程序的逻辑和引导它的方式应该彼此分开。这就是为什么我将 backbutton 的侦听器移动到 run 块。

【讨论】:

以上是关于如何在 Angular 控制器之外删除 Cordova 特定事件?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Angular 1.5 组件中监听范围事件?

如何在 Angular 世界之外发生更改时更新 Angular 2 Reactive 表单字段

如何在 Angular 2 轮询服务中访问 .subscribe 之外的值

Angular 5:如何访问应用程序或资产文件夹之外的图像文件?

Angularjs 范围之外的服务功能

APP IONIC3 angular4