简体   繁体   English

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

[英]In AngularJS, how to run code after all controllers are initialized?

I have a single-page AngularJS application composed of multiple modules, whose purpose is to provide the user with a collaborative pad (main widget) and other related widgets (other connected users, pad metadatas, etc.). 我有一个由多个模块组成的单页AngularJS应用程序,其目的是为用户提供协作板(主要小部件)和其他相关小部件(其他连接用户,pad元数据等)。

I chose to split the application as follow: 我选择拆分应用程序如下:

  • 1 module hosting a service, responsible for exposing initialization methods for the pad component 1个托管服务的模块,负责公开pad组件的初始化方法
  • N modules hosting custom directives (and their controller), corresponding to the different widgets in the application N个模块托管自定义指令(及其控制器),对应于应用程序中的不同小部件
  • 1 module responsible for gathering parameters and initializing the pad component 1个模块负责收集参数和初始化pad组件

Let's simplify this by assuming I have only 1 widget, whose sole goal is to display a status message to the user: "authenticating", "authenticated", "error" or "ready". 让我们通过假设我只有一个小部件来简化这一点,小部件的唯一目标是向用户显示状态消息:“authenticating”,“authenticated”,“error”或“ready”。

I chose to use a subscribe/notify pattern with the service to let the widget be notified of a change in the shared component's state. 我选择在服务中使用订阅/通知模式,以通知窗口小部件共享组件状态的更改。

The service: 服务:

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

The widget: 小部件:

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

Now the "initializer module", in its simplest form, gathers an authentication token authToken from the URL fragment (similar to OAuth2) and simply calls padService.initialize(authToken); 现在,“初始化模块”以其最简单的形式从URL片段(类似于OAuth2)收集身份验证令牌authToken ,并简单地调用padService.initialize(authToken); . Note that it could as well be a dedicated authentication popup, that's why it resides in its own module. 请注意,它也可以是专用的身份验证弹出窗口,这就是它驻留在自己的模块中的原因。

My problem is that I don't know where to put that piece of code. 我的问题是我不知道在哪里放这段代码。 All the places I tried resulted in being placed too early in the angular bootstrap process and/or not updating the widget: 我尝试过的所有地方都导致在角度引导过程中过早放置和/或没有更新小部件:

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

The controllers are created synchronously (I assume), so there shouldn't be any difficulty to make some code run after that. 控制器是同步创建的(我假设),因此在此之后运行某些代码应该没有任何困难。

That is an erroneous assumption. 这是一个错误的假设。 The AngularJS framework routinely creates and destroys directives and their controllers during the life of the application. AngularJS框架在应用程序的生命周期中定期创建和销毁指令及其控制器。 ng-repeat , ng-if , ng-include , etc. all create and destroy DOM containing directives. ng-repeatng-ifng-include等都创建和销毁包含指令的DOM。 If your "widget" is part of an ng-repeat , its controller gets instantiated multiple times, once for each item in the list that ng-repeat watches. 如果您的“小部件”是ng-repeat ,则其控制器会多次实例化,对于ng-repeat手表列表中的每个项目进行一次实例化。

To retain data that persists throughout the lifetime of an application, keep it in a service. 要保留在应用程序的整个生命周期中持续存在的数据,请将其保留在服务中。 (Or on $rootScope ; not recommended but an option.) Controllers can't assume that they have been started during bootstrap. (或者在$rootScope ;不推荐,但是选项。)控制器不能假设它们在引导期间已经启动。 They need to "catch-up" and subscribe to changes. 他们需要“赶上”并订阅变化。

Keep persistent data in a factory service and provide setter and getter functions. 将持久数据保存在工厂服务中,并提供settergetter功能。

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

In your "widget", inject the service, subscribe to changes, and "catch-up". 在您的“小部件”中,注入服务,订阅更改和“追赶”。

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

When the directive first registers the listener using $watch , the AngularJS framework, executes the watch function (in this case padService.getStatus ), and executes the listener function. 当指令首先使用$watch注册侦听器时,AngularJS框架执行watch函数(在本例中为padService.getStatus ),并执行侦听器函数。 This allows the directive to "catch up" to the current status of the service. 这允许指令“赶上”服务的当前状态。

On each digest cycle, the AngularJS framework executes padService.getStatus . 在每个摘要周期中,AngularJS框架执行padService.getStatus If the status has changed, the framework executes the listener function with the new status as the first parameter. 如果状态已更改,则框架将以新状态作为第一个参数执行侦听器函数。 This allows the directive to react to changes. 这允许指令对更改做出反应。

You can not assume that the directive and its controller are created synchronously. 您不能假设该指令及其控制器是同步创建的。 But you do know that the service is instantiated and its constructor function executed before it is injected into the controller. 但是您确实知道该服务已实例化,并且在将其注入控制器之前执行其构造函数。

Store the status in the service 将状态存储在服务中

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

In your directive get the status from the service instead of defining it. 在您的指令中从服务获取状态而不是定义它。

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

There's are a lot of room for improvements here but for a start, the code above allows sharing of the same data throughout the module from the service. 这里有很大的改进空间,但首先,上面的代码允许从服务中在整个模块中共享相同的数据。

Next thing you might want to do is just have one subscriber method to broadcast changes made to the status to the listeners 您可能想要做的下一件事就是只有一个订阅者方法来向侦听器广播对status所做的更改

Adding on for a more complete solution 添加更完整的解决方案

Service 服务

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

Controller 调节器

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM