I got a routeProvider for my states.
$routeProvider.
when("/register",{
templateUrl: "templates/register.html",
controller: "RegisterCtrl",
resolve: {
user: function(Auth) {
return Auth.resolveUser();
}
}
}).
when("/home",{
templateUrl: "templates/home.html",
controller: "HomeCtrl",
resolve: {
user: function(Auth) {
return Auth.resolveUser();
}
}
}). .... [.....]
Every state got a promise which resolves, when user-state is loggedIn. Then the code of the different controllers is executed. Now I want to have a mainController for the navigation bar, which should be present on all sites. The controller needs the userdata for checking for new messages etc.
Now: how is it possible to define the resolve globally in a root state (so i can access the userdata in the root controller for all sites) and all the other controllers execute their code only, if the promise from this roote state is resolved?
I hope I formulated my question understandable...
I think you're looking for something like $routeChangeStart, that is a way to execute something you want everytime the user changes his route inside your web app. Take a look at Route and this other question from stackoverflow. Hope it helps.
You can do this by defining your routes outside of the $routeProvider.when statements:
var routes = [
{
url: "/register",
config: {
templateUrl: "templates/register.html",
controller: "RegisterCtrl"
}
},
{
url: "/home",
config: {
templateUrl: "templates/home.html",
controller: "HomeCtrl"
}
}
];
Then iterating through your routes to extend the resolve property before registering them with the $routeProvider:
angular.forEach(routes, function (route) {
var url = route.url;
var routeConfig = route.config;
routeConfig.resolve = angular.extend(routeConfig.resolve || {}, {
// add your global resolves here
user: function(Auth) {
return Auth.resolveUser();
}
});
$routeProvider.when(url, routeConfig);
});
Your Auth.resolveUser() should be responsible for returning the fulfilled promise if it was already resolved previously.
Related
How do I get parameters for a state from a service?
I have this state:
.state('cashbox.list', {
title: 'Cashbox',
url: '/:from/:to?q',
templateUrl: '/cashbox_list.html',
controllerUrl: 'controllers/CashboxCtrl.js',
controller: 'CashboxCtrl',
controllerAs: 'mainCtrl',
params: {
from: getCashboxParamFrom,
to: getCashboxParamTo
},
resolve: {
cashboxData: getTransactionsData
}
})
function getTransactionsData($stateParams)
{
return CashboxService.getTransactions($stateParams.from, $stateParams.to);
}
Now my problem is, that params from and to are dynamic and are fetched by another service. I need something like a resolve before the resolve to get the params. I already tried to operate with promises in getCashboxParamTo/From but the app then stops routing when going to state. Also tried to use a parent state with redirectTo into child state but don't find anything on how to dynamically create stateparams from resolved data in parent state.
.state('cashbox', {
title: trns('title.cashbox_list'),
url: '/cashbox',
redirectTo: {
state: 'cashbox.list',
params: HERE DATES FROM RESOLVE
},
resolve: {
dates: getFromToDates
}
})
Solutions with state.go won't work as I'm running into a 'The transition has been superseded by a different transition' issue.
The answer is to declare two states cashbox and cashbox.list. Then redirect in a onStart transition hook after fetching data from service:
$transitions.onStart({to: 'cashbox'}, function (trans)
{
var curMonth = gfService.getCurrentMonth();
return $state.target('cashbox.list', {from: curMonth.dateFrom.string, to: curMonth.dateTo.string})
});
I would like to create an abstract parent state, that has only one job: to resolve the current user through an ajax server call, and then pass this object to the child state. The problem is that the child state never gets loaded. Please have a look at this plunker: Example
a state
angular.module('test', ['ui.router'])
.config(function($stateProvider, $urlRouterProvider){
// Parent route
$stateProvider.state('main', {
abstract:true,
resolve: {
user: function(UserService){
return UserService.getUser();
}
}
});
// Child route
$stateProvider.state('home', {
parent: 'main',
url: '/',
controller: 'HomeController',
controllerAs: '$ctrl',
template: '<h1>{{$ctrl.user.name}}</h1>'
});
$urlRouterProvider.otherwise('/');
});
a factory
angular.module('test').factory('UserService', function($q){
function getUser() {
var deferred = $q.defer();
// Immediately resolve it
deferred.resolve({
name: 'Anonymous'
});
return deferred.promise;
}
return {
getUser: getUser
};
});
a controller
angular.module('test').controller('HomeController', function(user){
this.user = user;
});
In this example, the home state will never display the template, I don't really understand why. If I remove the parent: 'main' line, then it displays the template, but of course I get an error because it cannot find the user dependency in the HomeController.
What am I missing? I did everything like it is described in ui-router's documentation, I think this should work.
Every parent must have a target ui-view in template for its child
$stateProvider.state('main', {
abstract:true,
resolve: {
user: function(UserService){
return UserService.getUser();
}
}
template: '<div ui-view=""></div>'
});
NOTE: Another option is to use absolute names and target index.html .. but in this case the above is the way to go (Angularjs ui-router not reaching child controller)
I design my SPA like this:
angular.module('app', ['submodule0', 'submodule1']);
Main module:
$stateProvider.state("sub0index", {
url: "/sub0",
// pass states defined in submodule0, is that possible?
}).state("sub1index", {
url: "/sub1",
// pass states defined in submodule1
})
And here are some states defined in submodule0
$stateProvider.state("index", {
url: "/index",
templateUrl: "template/index.html"
}).state("info", {
url: "/info",
templateUrl: "template/info.html"
})
So is that possible that I pass sub-state from sub-module to the main module? I ask this because now I define all my state in my main module, I think it may be more elegant to define the state of one submodule in the submodule itself.
And another question is: I'm not sure my module design is reasonable or not, is my submodules not necessary? Or just keep my whole app logic to one module? Thanks.
====Edited====
And here is the problem I've met.
var app = angular.module('test', ['ui.router', 'app.sub']);
app.config(['$stateProvider', function ($stateProvider) {
$stateProvider.state('index', {
url: "/a",
views: {
"general": {
templateUrl: "/template.html"
}
},
resolve: {
data: 'GetDataService'
}
});
}
The service GetDataService is defined in my submodule app.sub, and here is the service:
angular.module('app.sub',['ui.router'])
.service('GetDataService', ['$stateParams', function($stateParams) {
console.log($stateParams);
return null; // return null just for demo
}]);
The output of console.log($stateParams) is an empty object. But if use the service which is defined in its own module, the current state can be get correctly. So whats the issue?
===Edit===
Thanks for the example, it works fine if give a factory to data directly. But how about I give it a string?
I check the document of ui-router, and there is something about map object in resolve:
factory - {string|function}: If string then it is alias for service.
So if I use the code like this:
resolve: {
data: "GetDataService"
}
And the definition of GetDataService:
.service('GetDataService', ['$stateParams', function($stateParams) {
console.log($stateParams);
return null;
}])
But output of console.log($stateParams) is always an empty object.
Do I have some misunderstanding about the api document?
===Edit again===
If I use code like this:
resolve: {
// data: "GetDataService"
data: ['$stateParams', function($stateParams) {
console.log($stateParams);
return null;
}]
}
I can get the params object.
I would say, that modules should not stop us... we can split the app into many if needed.
But I would suggest: Services should be independent on $state.current. We should pass to them function parameters as needed, but these should be resolved outside of the Service body.
Bette would be to show it in action - there is one working example
This is the service:
angular.module('app.sub',['ui.router'])
.service('DataService', ['$state', function($state) {
return {
get: function(stateName, params){
console.log(stateName);
console.log(params);
return stateName;
}
}
}]);
And here is some adjsuted state def:
app.config(['$stateProvider', function ($stateProvider) {
$stateProvider.state('index', {
url: "/a/{param1}",
views: {
"general": {
templateUrl: "tpl.html"
}
},
resolve: {
data: ['DataService','$stateParams'
, function(DataService,$stateParams, $state){
return DataService.get('index', $stateParams)
}],
},
});
}])
Hope it helps a bit. The plunker link
Because this approach is ready to test service without any dependency on some "external" $state.current. We can just pass dummy, testing params
I have an AngularJS service which communicates with the server and returns
translations of different sections of the application:
angular
.module('utils')
.service('Translations', ['$q','$http',function($q, $http) {
translationsService = {
get: function(section) {
if (!promise) {
var q = $q.defer();
promise = $http
.get(
'/api/translations',
{
section: section
})
.success(function(data,status,headers,config) {
q.resolve(result.data);
})
.error(function(data,status,headers,config){
q.reject(status);
});
return q.promise;
}
}
};
return translationsService;
}]);
The name of the section is passed as the section parameter of the get function.
I'm using AngularJS ui-router module and following design pattern described here
So I have the following states config:
angular.module('app')
.config(['$stateProvider', function($stateProvider) {
$stateProvider
.state('users', {
url: '/users',
resolve: {
translations: ['Translations',
function(Translations) {
return Translations.get('users');
}
]
},
templateUrl: '/app/users/list.html',
controller: 'usersController',
controllerAs: 'vm'
})
.state('shifts', {
url: '/shifts',
resolve: {
translations: ['Translations',
function(Translations) {
return Translations.get('shifts');
}
]
},
templateUrl: '/app/shifts/list.html',
controller: 'shiftsController',
controllerAs: 'vm'
})
This works fine but as you may notice I have to explicitly specify translations in the resolve parameter. I think that's not good enough as this duplicates the logic.
Is there any way to resolve translations globally and avoid the code duplicates. I mean some kind of middleware.
I was thinking about listening for the $stateChangeStart, then get translations specific to the new state and bind them to controllers, but I have not found the way to do it.
Any advice will be appreciated greatly.
Important note:
In my case the resolved translations object must contain the translations data, not service/factory/whatever.
Kind regards.
Let me show you my approach. There is a working plunker
Let's have a translation.json like this:
{
"home" : "trans for home",
"parent" : "trans for parent",
"parent.child" : "trans for child"
}
Now, let's introduce the super parent state root
$stateProvider
.state('root', {
abstract: true,
template: '<div ui-view=""></div>',
resolve: ['Translations'
, function(Translations){return Translations.loadAll();}]
});
This super root state is not having any url (not effecting any child url). Now, we will silently inject that into every state:
$stateProvider
.state('home', {
parent: 'root',
url: "/home",
templateUrl: 'tpl.html',
})
.state('parent', {
parent: 'root',
url: "/parent",
templateUrl: 'tpl.html',
})
As we can see, we use setting parent - and do not effect/extend the original state name.
The root state is loading the translations at one shot via new method loadAll():
.service('Translations', ['$http'
,function($http) {
translationsService = {
data : {},
loadAll : function(){
return $http
.get("translations.json")
.then(function(response){
this.data = response.data;
return this.data;
})
},
get: function(section) {
return data[section];
}
};
return translationsService;
}])
We do not need $q at all. Our super root state just resolves that once... via $http and loadAll() method. All these are now loaded, and we can even place that service into $rootScope:
.run(['$rootScope', '$state', '$stateParams', 'Translations',
function ($rootScope, $state, $stateParams, Translations) {
$rootScope.$state = $state;
$rootScope.$stateParams = $stateParams;
$rootScope.Translations = Translations;
}])
And we can access it anyhwere like this:
<h5>Translation</h5>
<pre>{{Translations.get($state.current.name) | json}}</pre>
Wow... that is solution profiting almost from each feature coming with UI-Router... I'd say. All loaded once. All inherited because of $rootScope and view inheritance... all available in any child state...
Check that all here.
Though this is a very old question, I'd like to post solution which I'm using now. Hope it will help somebody in the future.
After using some different approaches I came up with a beautiful angularjs pattern by John Papa
He suggest using a special service routerHelperProvider and configure states as a regular JS object. I'm not going to copy-paste the entire provider here. See the link above for details. But I'm going to show how I solved my problem by the means of that service.
Here is the part of code of that provider which takes the JS object and transforms it to the states configuration:
function configureStates(states, otherwisePath) {
states.forEach(function(state) {
$stateProvider.state(state.state, state.config);
});
I transformed it as follows:
function configureStates(states, otherwisePath) {
states.forEach(function(state) {
var resolveAlways = {
translations: ['Translations', function(Translations) {
if (state.translationCategory) {
return Translations.get(state.translationCategory);
} else {
return {};
}
}],
};
state.config.resolve =
angular.extend(state.config.resolve || {}, resolveAlways || {});
$stateProvider.state(state.state, state.config);
});
});
And my route configuration object now looks as follows:
{
state: ‘users’,
translationsCategory: ‘users’,
config: {
controller: ‘usersController’
controllerAs: ‘vm’,
url: ‘/users’.
templateUrl: ‘users.html'
}
So what I did:
I implemented the resolveAlways object which takes the custom translationsCategory property, injects the Translations service and resolves the necessary data. Now no need to do it everytime.
I am currently setting up resolves for my admin panel routes and am wondering what the best way of storing them is as ideally I don't want to have my router filled with methods like so:
when('/admin', {
templateUrl: 'app/private/admin/view.html',
controller: 'admin',
resolve: ['$q', '$location', 'api', function($q, $location, api){
var deferred = $q.defer(),
session = api.session();
if(session){
deferred.resolve(session);
} else {
api.authorise().success(function(response){
deferred.resolve(response);
}).error(function(error){
$location.path('/login');
deferred.reject(error);
});
}
return deferred.promise;
}]
})
I think an ideal structure would be to store the resolves in the controller I'm using for that route, so something like:
when('/admin', {
templateUrl: 'app/private/admin/view.html',
controller: 'admin',
resolve: adminCtrl.resolve
})
However the admin controller is not accessible from the config so this leaves me with having to use a provider which is still going to be messy when expanding my application.
How do you all handle your resolves/is it possible to store it in my controller?
I usually use services for the things I want to get resolved:
when('/admin', {
templateUrl: 'app/private/admin/view.html',
controller: 'admin',
resolve: { adminData: function(myService) { return myService.list(); } }
});
More advanced angular routers like UI-router allow for states to inherit from parent-states. If you want to have a resolve in multiple states you could use inheritance, and define the resolve in your parent-state. (https://github.com/angular-ui/ui-router/wiki/Nested-States-%26-Nested-Views).