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.).
I chose to split the application as follow:
- 1 module hosting a service, responsible for exposing initialization methods for the pad component
- N modules hosting custom directives (and their controller), corresponding to the different widgets in the application
- 1 module responsible for gathering parameters and initializing the pad component
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".
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);. 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);
});
}
resolvemethod github.com/angular-ui/ui-router/blob/master/src/resolve.jsnotify()inner method when an event occurs, which will in turn call each registered callback with some arguments (really this is just a custom implementation of$onand$broadcastto avoid polluting the global event names). The service does not need to know anything about its subscribers to function, but it needs to be initialized with a set of parameters (here an authentication token provided by theapp.initializermodule).run()method, but executing after the controllers? Is it my design which is totally biased, or is no one actually using things like OAuth2 token parsing (to initialize a 3rd party component) in the angular community?$controllerservice to get your instantiation in a promise chain. It's a "trivial need" but trying to fight async is never easy.