In AngularJS, how to run code after initializing all controllers?

I have a one-page AngularJS application consisting of several modules, the purpose of which is to provide the user with a joint panel (main widget) and other related widgets (other related users, pad metadata, etc.).

I decided to break the application as follows:

  • 1 module servicing the service responsible for expanding the initialization methods for the pad component
  • N modules containing user directives (and their controller) corresponding to various widgets in the application
  • 1 module responsible for collecting parameters and initializing the pad component

Let's simplify this by assuming that I have only 1 widget whose sole purpose is to display the status of the message to the user: “authentication”, “authentication”, “error” or “availability”.

I decided to use the subscription / notification template with the service to allow the widget to receive a notification about the state change of the common component.

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

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 “initialization module” in its simplest form collects the authToken authentication authToken from the URL fragment (similar to OAuth2) and simply calls padService.initialize(authToken); . Please note that it can also be a highlighted popup, so it is in its own module.

My problem is that I don’t know where to put this piece of code. All the places that I tried led to the fact that they were ranked at the beginning of the angular boot process and / or did not update 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 $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); }); } 
+5
source share
3 answers

Controllers are created synchronously (I assume), so there should be no difficulty in executing some code after that.

This is an erroneous assumption. The AngularJS framework regularly creates and destroys directives and their controllers throughout the life of the application. ng-repeat , ng-if , ng-include , etc. all create and destroy directives containing the DOM. If your widget is part of ng-repeat , its controller receives several instances, once for each element in the list that ng-repeat looking at.

To save data that is stored throughout the life of the application, store it in the service. (Or on $rootScope ; not recommended, but an option.) Controllers cannot assume that they were running at boot time. They need to “catch up” and subscribe to the changes.

Keep persistent data in the factory service and provide setter and getter functions.

 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", enter the service, subscribe to the 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 a listener using $watch , the AngularJS framework executes the watch function (in this case padService.getStatus ) and performs the function of a listener. This allows the directive to catch up with the current status of the service.

In each cycle, the AngularJS framework digest executes padService.getStatus . If the status has changed, the environment acts as a listener with the new status as the first parameter. This allows the directive to respond to changes.

You cannot assume that a directive and its controller are created synchronously. But you know that this service is created, and its constructor function is executed before it is introduced into the controller.

+2
source

Save status in 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 status from the service, not define 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 are many opportunities for improvement, but for starters, the code above allows you to use the same data in the module from the service.

The next thing you might want to do is just one subscriber method to relay changes made to status listeners

0
source

Addendum 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; }); } 
0
source

Source: https://habr.com/ru/post/1240855/


All Articles