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

How to remove Cordova specific events outside Angular controller?

想象一下,我有一个控制器可以处理例如视图更改:

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

但我也想遵循(相对 :) )新的 controllerAs 语法,因为现在建议这样做,例如Todd 座右铭:Opinionated AngularJS styleguide for teams and it allows to remove $scope from controllers when things like $emit or $on are not used. But I can't do it, case I have to call $apply() cause my context is not Angular context when user clicks on device back button. I thought about creating a Service which can be wrapper facade for cordova and inject $scope to this service but as I read here: Injecting $scope into an angular service function() it is not possible. I saw this: Angular JS & Phonegap back button event 并且接受的解决方案还包含 $apply(),这使得 $scope 不可移动。任何人都知道在 Angular 控制器之外删除 Cordova 特定事件的解决方案,以便在不需要明确需要时从控制器中删除 $scope?提前谢谢你。

在我的例子中,我只是借助 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 事件触发,然后手动 bootstrapping AngularJS 应用程序以提供应用程序的顺序加载并防止任何冲突:

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

从我的角度来看,应用程序的逻辑以及您如何 bootstrap 它应该彼此分开。这就是为什么我将 backbutton 的侦听器移动到 run 块。

我看不出为什么要从控制器中删除 $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);
});