Resolve UI-Router on any state - javascript

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.

Related

Angularjs two stage resolve (resolve dependencies)

I have a state where I need to resolve one item before I have the data to resolve the other:
.state('addtrip', {
url: '/addtrip',
templateUrl: 'views/addtrip.html',
controller: 'AddtripCtrl',
resolve: {
auth : function($state, Auth){
return Auth.$requireAuth().then(function(auth) {
return Auth;
}, function(error){
$state.go('login');
});
},
trips : function(rootRef,$firebaseArray){
return $firebaseArray(rootRef).child(auth.uid).$loaded();
}
}
So first I want to get the auth object and only after I want to retrieve the trips of that specific user.
What's the best way to handle situations like this?
the guy above forgot to return a promise on the inner function
.state('addtrip', {
url: '/addtrip',
templateUrl: 'views/addtrip.html',
controller: 'AddtripCtrl',
resolve: {
auth :function($state, Auth,rootRef,$firebaseArray,$q){
return Auth.$requireAuth().then(function(auth) {
var p = new Promise (resolve, reject)
resolve({
auth: auth,
trips: $firebaseArray(rootRef).child(auth.uid).$loaded();
});
return p;
})
}
}
}
First of all i use the way explained on the firebase website and then just use currentauth and waitforauth in your trips function. You will have to change it a little bit to fit to your program but i use it myself and it works like this. (sorry for bad indentation in the code)
.run(["$rootScope", "$state", function($rootScope, $state) {
$rootScope.$on("$stateChangeError", function(event, toState, toParams, fromState, fromParams, error) {
// We can catch the error thrown when the $requireAuth promise is rejected
// and redirect the user back to the home page
if (error === "AUTH_REQUIRED") {
$state.go("login");
}
});
}])
.state('addtrip', {
url: '/addtrip',
templateUrl: 'views/addtrip.html',
controller: 'AddtripCtrl',
resolve: {
"currentAuth": ["firebaseRef", function (firebaseRef) {
// $requireAuth returns a promise so the resolve waits for it to complete
// If the promise is rejected, it will throw a $stateChangeError (see above)
//return firebaseRef.refAuth().$requireAuth();
return firebaseRef.refAuth().$requireSignIn();
}],
"waitForAuth": ["firebaseRef", function (firebaseRef) {
// $requireAuth returns a promise so the resolve waits for it to complete
// If the promise is rejected, it will throw a $stateChangeError (see above)
return firebaseRef.refAuth().$waitForSignIn();
}],
trips : function(currentAuth, waitForAuth, rootRef,$firebaseArray){
return $firebaseArray(rootRef).child(currentAuth.uid).$loaded();
}
}
Than lets try with promise nesting. It should work,
.state('addtrip', {
url: '/addtrip',
templateUrl: 'views/addtrip.html',
controller: 'AddtripCtrl',
resolve: {
auth :function($state, Auth,rootRef,$firebaseArray,$q){
var defer = $q.defer();
var obj = [];
Auth.$requireAuth().then(function(auth) {
obj.push(auth);
var a = $firebaseArray(rootRef).child(auth.uid).$loaded();
obj.push(a);
defer.resolve(obj);
});
return defer.promise;
}
}
OR,
.state('addtrip', {
url: '/addtrip',
templateUrl: 'views/addtrip.html',
controller: 'AddtripCtrl',
resolve: {
auth :function($state, Auth,rootRef,$firebaseArray,$q){
return Auth.$requireAuth().then(function(auth) {
return {
auth: auth,
trips: $firebaseArray(rootRef).child(auth.uid).$loaded();
};
})
}
}
}

$state.go in resolve not working

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');
});
}
}]

UI Router parent State for Authorization

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?

Angular one resolve for all states

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.

Angular UI-Router Overwrite Resolve in Child States

I'm running into the issue, that I have the same resolve function in parent & child states - and depending on the child state, i would like to have it return a different value.
Somehow, instead of overwriting the implementation for it, it simply just takes the behavior from the parent state.
.state('wines', {
url: '/wines',
templateUrl: 'partials/products/index',
controller: 'cwProductsController',
resolve: {
merchandiseView: function() {
return "featured";
}
}
}).state('wines.featured', {
url: "/featured",
templateUrl: 'partials/products/index',
controller: 'cwProductsController',
resolve: {
merchandiseView: function() {
return "featured";
}
}
}).state('wines.curatorsChoice', {
url: "/curators-choice",
templateUrl: 'partials/products/index',
controller: 'cwProductsController',
resolve: {
merchandiseView: function() {
return "curators-choice";
}
}
}).state('wines.stillAvailable', {
url: "/still-available",
templateUrl: 'partials/products/index',
controller: 'cwProductsController',
resolve: {
merchandiseView: function() {
return "still-available";
}
}
});
Here, it always keeps on returning "featured", even when visiting wines/still-available, where I expect merchandiseView to be "still-available".
This is my controller:
angular.module('clubwApp').controller('cwProductsController', [
'$scope', 'cwProduct', '$stateParams', 'merchandiseView', function($scope, cwProduct, $stateParams, merchandiseView) {
console.log(merchandiseView);
$scope.wines = cwProduct.available();
return $scope.merchandiseView = angular.copy(merchandiseView);
}
]);
Is there a way, how i can overwrite this?
Attach you specific data to the data state property instead of the resolve.
At controller initialization time read $state.current.data from the $state injectable.
This is apparently an issue (up until 0.2.15).
Here is a hacky solution to the problem:
http://plnkr.co/edit/j1wCThlv1uczlX7d5cdg?p=preview
the resolve: {test: {this.data.someObject}}

Categories