I'm trying to create a parent state in my Route definition to handle Autorization like this:
$stateProvider.state('root', {
abstract: true,
resolve: {
auth: ['Auth',
function (Auth) {
return Auth.authorize()
}
]}
})
this parent state is defined in my other states like this:
.state('home', {
parent: 'root',
url: '/',
templateUrl: 'views/home.html',
controller: 'HomeCtrl'
})
From my understanding of parent states and this post it should work just like this.
The Auth.authorize method works fine, since I can do a simple resolve on each state. Here's the function:
function authorize() {
var user = getCurrentUser();
if (user) return $q.when(user);
else {
$timeout(function() {
$state.go('login')
});
return $q.reject();
}
}
The response is ok, but the page doesn't get displayed. What am I missing here?
Related
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)
Is there a way to have the stateprovider from ui-route in AngularJS to execute a resolve whenever any route is hitted.
I'm thinking something similar to this:
$stateProvider
.state('signin', {
url: "/signin",
templateUrl: 'app/modules/signin/signin.html',
controller: 'signinController',
resolve: {
reset: function (Master, Storage) {
Master.reset();
Storage.set('preventSigninLeave', true);
}
}
})
.state('menu', {
url: "/menu",
templateUrl: 'app/modules/menu/menu.html',
controller: 'menuController',
resolve: {
config: function (Config) {
return Config.get()
.then(function(response){
return response;
})
.catch(function(error){
console.error(error);
return undefined;
});
},
reset: function (Master) {
Master.reset();
}
}
})
.state('view', {
url: '/view/:view',
templateUrl: 'app/view/view.html',
controller: 'viewController',
resolve: {
config: function (Config) {
return Config.get()
.then(function(response){
return response;
})
.catch(function(error){
console.error(error);
return undefined;
});
}
}
})
.always({
resolve: {
message: function () {
return 'hey Bub';
}
}
});
Or do I have to set the variable in every state called?
I usually do this, by using state inheritance, this way resolved properties are just "resolved" once, unless user get out from parent state. I usually create a parent abstract state called "app", and make all my states inherit from it, this way if i need to introduce one resolved property for all my states i just add it.
If renaming and updating your state tree is not possible (maybe you have several states defined), I would use angular.extend or jQuery.extend, this way you could define your "always" property as a separate object and use it on demand.
So I have these routes set up:
.state('createOrder', {
url: '/customer-services/orders/create',
templateUrl: 'tpl/customerServices/orders/save.html',
controller: 'SaveOrderController',
controllerAs: 'controller',
resolve: {
order: ['SaveOrderService', function (shared) {
shared.order = { forDelivery: true };
}]
},
data: {
requireLogin: true,
pageTitle: 'Add order'
}
}).state('createOrder.lines', {
url: '/lines',
views: {
'#': {
templateUrl: 'tpl/customerServices/orders/save/line.html',
controller: 'SaveOrderLinesController',
controllerAs: 'controller'
}
},
resolve: {
validate: ['$state', 'SaveOrderService', function ($state, shared) {
// If we don't have an account number
if (!shared.order.accountNumber) {
console.log('redirecting');
// Redirect to the create order view
$state.go('createOrder');
}
}]
},
data: {
requireLogin: true,
pageTitle: 'Add order : Lines'
}
})
But the state does not change. I thought that there might be an error somewhere, so I subscribed the the state events like this:
// On state change
$rootScope.$on('$stateChangeStart', function (event, toState) {
var data = toState.data; // Get our state data
var requireLogin = typeof data === 'undefined' ? false : data.requireLogin; // Check to see if we have any data and if so, check to see if we need login rights
var user = service.get(); // Get our current user
console.log(toState);
$rootScope.currentUser = user; // Set our current user on the rootScope
// If we require login rights and we are not authenticated
if (requireLogin && !user.authenticated) {
event.preventDefault(); // Stop processing
$state.transitionTo('login'); // And redirect to the login page
}
});
$rootScope.$on('$stateNotFound', function () {
console.log('state not found');
});
$rootScope.$on('$stateChangeError', function () {
console.log('state errored');
});
$rootScope.$on('$stateChangeSuccess', function () {
console.log('state changed');
});
and when I refresh my lines view, the console outputs this:
Object { url: "/lines", views: Object, resolve: Object, data: Object, name: "createOrder.lines" } app.js:42:9
redirecting app.js:2436:21
Object { url: "/customer-services", templateUrl: "tpl/customerServices/index.html", controller: "CustomerServicesController", controllerAs: "controller", data: Object, name: "customerServices" } app.js:42:9
state changed app.js:63:9
Object { order: Object, account: null, collectionPoint: null }
As you can see, the states think they have changed, but I still see the createOrder.lines view.
Does anyone have any idea why?
I think you'll need to wrap the $state change in a function that will trigger a digest cycle whilst also rejecting the promise in the resolve method...
$timeout(function() { $state.go("createOrder") });
return $q.reject("Rejection message!");
Remember to inject $timeout and $q into your resolve function! =)
Should also add that rejecting the resolve will fire stateChangeError.
So, it turns out you don't need the promise. Just adding the timeout works.
I found another post which suggests that the timeout is needed to avoid digest issues (which I am guessing is what is causing my states to not change).
Here is the final code:
validate: ['$state', '$timeout', 'SaveOrderService', function ($state, $timeout, shared) {
// If we don't have an account number
if (!shared.order.accountNumber) {
// Timeout to avoid digest issues
$timeout(function () {
// Redirect to the create order view
$state.go('createOrder');
});
}
}]
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.
I'm trying to change a parent view template on runtime - inside a service.
My app config looks like:
$stateProvider
.state('base', {
abstract: true,
views: {
'header': {
controller: 'HeaderCtrl',
templateUrl: 'header.html'
},
'': {
template: '<div ui-view="main"></div>'
}
}
})
.state('base.home', {
url: '/',
views: {
'main': {
controller: 'SomeContentCtrl',
templateUrl: 'content.html'
}
}
});
I then have a service which is called from SomeContentCtrl that will listen for an event and upon such event I want to set the templateUrl for the header to null. Something like:
angular
.module('RemoveTemplate', [ ])
.factory('RemoveTemplate', ['$window', '$view', '$state',
function RemoveTemplate ( $window, $view, $state ) {
var windowElem = angular.element($window);
var listen = function ( ) {
windowElem.on('RemoveTemplate', function ( event ) {
$view.load('header#base', {
templateUrl: null
});
// Trying both, even tried without refreshing the state
$state.reload();
$state.go('wh.lobby');
});
};
return {
listen: listen
};
}
]);
});
But this isn't working at all. Have anyone came across a similar use case before?
Thanks
You can specify a templateURL function that runs each time you navigate to the state.
https://github.com/angular-ui/ui-router/wiki/Quick-Reference#template-templateurl-templateprovider
this method can check if it should supply a url or something else;
example:
$stateProvider.state('home', {
url: '/',
templateUrl: function () {
if (header === true) {
return 'app/templates/home.html';
} else {
return somethingElse;
}
}
})
If you want to 'hide' the header section without removing the html try to use a ng-show directive inside the header tag and check the actual state.
<div ng-show="state.is('base')">
This way you only need to inject the $state service in the controller.
When the state is base the div will show, if you navigate to child states it will hide.
p.s. $state.is returns a boolean, this method works with ng-if as well.