4

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

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

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

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

但我也想遵循(相对:))新controllerAs语法,因为现在推荐使用这种方法,例如 Todd Motto:适用于团队的 Opinionated AngularJS 样式指南,它允许在使用或不使用$scope某些东西时从控制器中删除。但是我不能这样做,如果用户单击设备后退按钮时我必须调用,因为我的上下文不是 Angular 上下文。我考虑过创建一个可以作为cordova的包装外观并注入到该服务中,但正如我在这里所读到的:Injecting $scope into an angular service function()这是不可能的。我看到了这个:Angular JS & Phonegap back button event和接受的解决方案还包含这使得$emit$on$apply()Service$scope$apply()$scope不可移动。任何人都知道在 Angular 控制器之外删除 Cordova 特定事件的解决方案,以便$scope在不需要明确需要时从控制器中删除?先感谢您。

4

2 回答 2

2

我看不出为什么要从控制器中删除 $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);
});
于 2015-09-07T18:55:50.817 回答
1

在我的例子中,我只是在 Angular 的帮助下转发 Cordova$broadcast事件$rootScope。基本上任何应用程序控制器都会收到这个自定义事件。侦听器附加在配置阶段 - 在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()每次实例化控制器时,除非您手动取消订阅此事件,否则事件处理程序将保留在内存中;using$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街区的原因。

于 2015-09-07T10:59:34.267 回答