在AngularJS中,如何在初始化所有控制器后运行代码?

时间:2016-01-18 15:33:54

标签: javascript angularjs

我有一个由多个模块组成的单页AngularJS应用程序,其目的是为用户提供协作板(主窗口小部件)和其他相关窗口小部件(其他连接用户,pad metadatas等)。

我选择拆分应用程序如下:

  • 1个托管服务的模块,负责公开pad组件的初始化方法
  • N个模块托管自定义指令(及其控制器),对应于应用程序中的不同小部件
  • 1个负责收集参数和初始化pad组件的模块

让我们通过假设我只有一个小部件来简化这一点,小部件的唯一目标是向用户显示状态消息:“authenticating”,“authenticated”,“error”或“ready”。

我选择在服务中使用订阅/通知模式,以通知窗口小部件共享组件状态发生变化。

服务:

angular.module("app.core").factory("padService", padService);
function padService() {
    // Callback registration and notification code omitted
    return {
        initialize: function (authToken) { ... },
        onAuthenticated: function (callback) { ... },
        onReady: function (callback) { ... },
        onError: function (callback) { ... }
    };
}

小部件:

angular.module("app.widget").directive("widget", widget);
function widget() {
    return {
        templateUrl: 'app/widget.html',
        restrict: 'E',
        controller: widgetController
    };
}
function widgetController($scope, padService) {
    $scope.message = "authenticating";
    padService.onAuthenticated(function (user) {
        $scope.message = "authenticated";
        // Do other stuff related to user authentication event
    });
    padService.onReady(function (padInstance) {
        $scope.message = "ready";
        // Do other stuff related to pad readiness event
    });
    padService.onError(function (error) {
        $scope.message = "error";
        // Do other stuff related to error event
    });
}

现在,“初始化模块”以最简单的形式从URL片段(类似于OAuth2)收集身份验证令牌authToken,然后只需调用padService.initialize(authToken);。请注意,它也可以是专用的身份验证弹出窗口,这就是它驻留在自己的模块中的原因。

我的问题是我不知道在哪里放这段代码。我试过的所有地方都导致在角度引导过程中过早放置和/或没有更新小部件:

angular.module("app.initializer").run(run);
function run($document, $timeout, tokenService, padService) {
    // This does not work because run() is called before the
    // controllers are initialized (widget does not get notified)
    var authToken = tokenService.getTokenFromUrl();
    padService.initialize(authToken);

    $document.ready(function () {
        // This does not work because angular does not detect
        // changes made to the widget controller's $scope
        var authToken = tokenService.getTokenFromUrl();
        padService.initialize(authToken);

        // This does not work in firefox for some reason (but
        // does in chrome!)... except if I enter debug mode or
        // set the timeout to a longer value, which makes it
        // either really difficult to diagnostic or ugly as hell
        $timeout(function () {
            var authToken = tokenService.getTokenFromUrl();
            padService.initialize(authToken);
        }, 0);
    });
}

3 个答案:

答案 0 :(得分:2)

  

控制器是同步创建的(我假设),因此在此之后运行某些代码不会有任何困难。

这是一个错误的假设。 AngularJS框架在应用程序的生命周期中定期创建和销毁指令及其控制器。 ng-repeatng-ifng-include等都创建并销毁包含指令的DOM。如果你的小部件"是ng-repeat的一部分,其控制器被多次实例化,对ng-repeat监视的列表中的每个项目进行一次实例化。

要保留在应用程序的整个生命周期中持续存在的数据,请将其保留在服务中。 (或$rootScope;不推荐,但是选项。)控制器不能假设它们在引导期间已经启动。他们需要赶上"并订阅更改。

将持久数据保存在工厂服务中,并提供 setter getter 功能。

angular.module("app").factory("padService", function () {
    //Store service status here
    var status = "none-yet";

    function setStatus(s) {
        status = s;
        return status;
    };

    function getStatus() {
        return status;
    };

    return {
        setStatus: setStatus,
        getStatus: getStatus
    };
});

在您的"小部件"中,注入服务,订阅更改,以及"赶上"。

angular.module("app").directive("widget", function() {
    function widgetController($scope, padService) {
        //subscribe with $watch
        $scope.$watch(padService.getStatus, function(newStatus) {
            //catch-up and react to changes
            case (newStatus) {  
                "authenticated":
                     // Do stuff related to authenticated state
                     break;
                "ready":
                     // Do stuff related to pad ready state
                     break;
                "error":
                     // Do stuff related to error state
                     break;
                default:
                     // Do something else
             }
            $scope.message = newStatus;
        };
    };
    return {
            templateUrl: 'app/widget.html',
            restrict: 'E',
            controller: widgetController
    }
});

当指令首先使用$watch注册侦听器时,AngularJS框架执行watch函数(在本例中为padService.getStatus),并执行侦听器函数。这使得指令可以赶上"赶上"到目前的服务状态。

在每个摘要周期中,AngularJS框架执行padService.getStatus。如果状态已更改,则框架将以新状态作为第一个参数执行侦听器函数。这允许指令对更改做出反应。

您不能假设指令及其控制器是同步创建的。但是你知道服务被实例化并且它的构造函数在被注入控制器之前执行。

答案 1 :(得分:0)

将状态存储在服务中

function padService() {
  var ctx = this;
  ctx.status = 'authenticating';
  return {
      initialize: function (authToken) { ... },
      onAuthenticated: function (callback) { ... },
      onReady: function (callback) { ... },
      onError: function (callback) { ... },
      getStatus: function() { return ctx.status; }
  };
}

在你的指令中从服务获取状态而不是定义它。

function widgetController($scope, padService) {
  $scope.message = padService.getStatus();
  padService.onAuthenticated(function () {
    $scope.message = "authenticated";
  });
  padService.onReady(function () {
    $scope.message = "ready";
  });
  padService.onError(function () {
    $scope.message = "error";
  });
}

这里有很大的改进空间,但首先,上面的代码允许从服务中在整个模块中共享相同的数据。

您可能想要做的下一件事就是只有一个订阅者方法来向监听器广播对status所做的更改

答案 2 :(得分:0)

添加更完整的解决方案

<强>服务

padService.$inject = ['$rootScope'];
function padService($rootScope) {
  return {
    status: "authenticating",
    initialize: function (authToken) { 
      //Update the status
      $rootScope.$broadcast('STATUS_CHANGED');
    },
    subscribe: function(scope, callback){
      var ctx = this;
      scope.$on('STATUS_CHANGED', function (){
        callback(ctx.status);
      });
    }
  };
}

<强>控制器

function widgetController($scope, padService) {
  $scope.status = padService.status;
  padService.subscribe($scope, function(status){
    $scope.status = status;
  });
}