Dependency injection into onEnter callbacks for state changes with ui-router - angularjs

When using ui-router in Angular JS, what dependencies can be injected into onEnter callbacks? Specifically, I want to use the $http service and a constant defined in the parent module. The code I am working with defines ui-router states in module config; I know I cannot use services in config code, but maybe it's ok to use them in callbacks defined by config code? At any rate, the following code seems to work; I am just worried it may not be reliable. I guess
the real question is, when are dependencies injected? Does this happen at the time that a function is defined, or at the time that a function is called?
angular.module('sim', ['ui-router']).
constant('ENV', {
BASE_URL: '/simulation/secure'
}).
config(config);
function config($stateProvider) {
$stateProvider
.state('root.training', {
url: '/training',
controller: 'trainingCtrl',
onEnter: function($http,ENV)
{
$http.get(ENV.BASE_URL + '/setIsRunning');
}
});
};

The dependencies are injected when the function is called. Looking at the source code of ui-router, the onEnter-callback is called during a state transition, when the transition is considering entering. The call to the onEnter-callback is done using Angulars $injector.invoke() function, which resolves any dependencies in the $injector and invokes the method, as per the angular documentation. So pretty much any dependency should be valid to use in onEnter.

Related

ui-router Inherited Resolved Dependencies

I was trying to implement a secure area with an abstract state that is then implemented by concrete states.
According to this blog post, I thought i could have the abstract state resolve the authentication and have the concrete states rely on it, because as stated in ui-router docs
Child states will inherit resolved dependencies from parent state(s)
But later on, the same ui-router doc mentions that
The resolve keys MUST be injected into the child states if you want to wait for the promises to be resolved before instantiating the children.
and indeed, if i comment out the resolveline on the concrete state, it doesnt work anymore.
So i am really missing something. If ChildState inherit resolved dependencies, why should they be re-injected?
my code below :
router.config(function($stateProvider, $urlRouterProvider) {
var authentication = ["Auth", function(Auth) {
return Auth.$requireAuth();
}];
.state('secure', {
abstract: true,
template: '<navbar></navbar><ui-view/>',
resolve: authentication
})
.state('secure.concrete', {
url: '/concrete',
templateUrl: 'views/concrete.html',
controller: 'concreteCtrl',
resolve: authentication // <--- if i comment this out, it wont work
})
UPDATE :
After hours of tears, it looks like i'm going the wrong path. The problem doesnt come from inherited resolved dependencies, but from the fact that once the promisse is resolved, it's not "resetted".
Is there anyway to force the promise to be re-setted / re-evaluated on each state change ?
The problems may result from incorrect resolve syntax, it should be an object:
resolve: {
authentication: authentication
}
'MUST be injected into the child states' means that authentication should be explicitly stated as child state controller dependency. Duplicate resolve on child state isn't an injection; it will just make authentication called twice.
this did the trick for me but i'm not exactly sure why :
instead of calling state.go() or ui-sref normally, i call them with state.go(state, {reload:true}) or
that way the resolve seems to be re-evaluated.

Where is the first place I can call a service in angular ui-router?

I have an Angular app and I am using ui-router.
I need to call my service at the beginning of my App before anything else runs.
Is there a way I can call a service inside of my app.js file without using app.run? Or is there a better solution other than in my app.js file?
Take a look at the resolve functionality of ui-router https://github.com/angular-ui/ui-router/wiki/Quick-Reference. You just add another field to your state called 'resolve' and then can inject the service there. By using a resolve, this route will not execute until this resolve is completed.
I have something similar in an application where I created an abstract state for others to inherit and therefore, none of the states are registered until that initial resolve function is completed.
Ex.
$stateProvider.state('someState', {
template: 'someTemplate.html',
controller: 'someController',
resolve: {
authenticate: function(AuthSrv){
//Do stuff with AuthSrv in here
}
}
})

Resolve objects not available in $injector

I have a route definition as follows:
.state('user_login', {
url: '/user/login',
templateUrl: 'login.tpl.html',
controller: 'AuthenticationCtrl',
resolve: {
practice: ['$q', function($q) {
return $q.when({});
}]
}
})
Things work as expected when I inject "practice" into the controller. When I use the $injector, service however:
$injector.get('practice')
I get an unknown provider exception. Are resolve objects not available to the $injector? How I can expose them in the controller without explicitly injecting them in the controller definition?
Note: I am using Angular 1.2.x
No, you cannot get them separately via $injector. And you cannot even inject them separately as well in other places, say the same controller (AuthenticationCtrl) instantiated by ng-controller directive.
Resolve objects are not any service or any other entity which can be injected separately. It is a special dependency injected by the router when the controller AuthenticationCtrl is bound via the router. You cannot get the instances separately. Only router knows about the resolve properties and while the router instantiates the controller (once all the resolve dependencies are resolved) it looks for resolve properties in the annotation (of the dependency list specified via explicit/implicit dependency annotation in the definision of AuthenticationCtrl) of the route-bound controller and injects them as required.
This kind of special implementation can be found in other components as well like, angular-ui-modal, ui-state-router, angular-router's routeprovider etc..

Angular ui router : calling method on controller from onEnter

My ui router config is like this:
$stateProvider
.state("list",{url:"/list",templateUrl:"list.html",controller:"ctrl as vm"})
.state("list.select",
url:'/select',
templateUrl:'select.html',
onEnter:function( ) { ... }
});
The list.select state uses the same controller as the list state. How can I call a method on the controller from the onEnter function? Note that I'm using the "ctrl as vm" syntax! Can I also access $stateParams here?
You can certainly access $stateParams in onEnter, as well as any other service. However, there's no way to inject the current or parent (or any other) controller instance.
So, while you can't invoke a method on the controller this way, you can use onEnter or resolve to preprocess something and perhaps use a flag for list.select to check and call that method.
It also may make more sense to use a service to coordinate this functionality, but I don't know the purposes of your approach so I'd need to know more.

How to cleanly specify a resolve property for a route

I want to find a clean way to set resolve for a route.
From what I've seen there's 3 main ways of doing this:
declaring global functions (not good)
anonymous functions (bad if the logic is complicated or if I need the same function in multiple places)
setting a method on the controller function (this doesn't work for me because I don't like having controllers as separate function declarations, instead I just do module(...).controller(...);
Is there a better way to do this then the above, especially if I need a resolve shared between similar routes. (e.g. /people and /people/:personID)
Thanks.
How about creating a .constant and inject it during your .config phase? If you look at AngularJS documentation for .constant, you will see that you can define a function as a constant.
So, you should be able to do something like:
app.constant("Resolver", {
"MessageUtils": function () {
return {
get: function (message) {
return "MUTIL: " + message;
}
}
}
});
Then you would use it as:
app.config(function ($routeProvider, Resolver) {
$routeProvider.when("/home", {templateUrl: ..., resolve: Resolver, ...})
...
Here is a working plunker:
http://plnkr.co/edit/psuYHu4rtlp4o42ZgqVs?p=preview
I had a similar request in my angularAMD project and I created a function called .route to help me set the resolve property when defining routes. Here is a link to the code.

Resources