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();
};
})
}
}
}
Related
I want to verify if the user can access a state before he gets there, if he doesn't have permissions will be redirected to another page.
The problem is that I'm doing a SPA and it verifies the permissions, but it takes a while until the server send the response and the user is redirected, so what happen is that a screen appears for 1 or 2 seconds and then is redirected successfully. Is there anyway to avoid this?
This is the code for the state change:
webApp.run(function ($rootScope, $state, StateService) {
$rootScope.$on('$stateChangeStart', function (event, toState, fromState, toParams) {
StateService.hasAccessTo(toState.name, function(data){
if (data.data != ""){
event.preventDefault();
$state.go(data.data);
}
});
});
});
and the service:
webApp.service('StateService', function($http, $rootScope){
this.hasAccessTo = function(state, callback){
$http.get("state/" + state).then(callback);
}
});
I have also tried with a promise in the $stateChangeStart, but it didn't work.
I read about interceptors, but they work if the user is in another page and access mine, if he is already on the page and type a link manually it doesn't intercepts.
Any modifications or suggestions of new ideas or improvements are welcome!
EDIT
Now I have this:
var hasAccessVerification = ['$q', 'StateService', function ($q, $state, StateService) {
var deferred = $q.defer();
StateService.hasAccessTo(this.name, function (data) {
if (data.data !== '') {
$state.go(data.data);
deferred.reject();
} else {
deferred.resolve();
}
});
return deferred.promise;
}];
$urlRouterProvider.otherwise("/");
$compileProvider.debugInfoEnabled(false);
$stateProvider
.state('welcome',{
url:"/",
views: {
'form-view': {
templateUrl: '/partials/form.html',
controller: 'Controller as ctrl'
},
'#': {
templateUrl: '/partials/welcome.html'
}
},
data: {
requireLogin: false
},
resolve: {
hasAccess: hasAccessVerification
}
})
And it validates, but it doesn't load the template. It doesn't show de views. What might I be doing wrong?
EDIT 2
I forgot to add $state here:
var hasAccessVerification = ['$q', '$state', 'StateService', function ($q, $state, StateService){...}
Consider using the resolve in your state configuration instead of using $stateChangeStart event.
According to the docs:
If any of these dependencies are promises, they will be resolved and
converted to a value before the controller is instantiated and the
$stateChangeSuccess event is fired.
Example:
var hasAccessFooFunction = ['$q', 'StateService', function ($q, StateService) {
var deferred = $q.defer();
StateService.hasAccessTo(this.name, function (data) {
if (data.data !== '') {
$state.go(data.data);
deferred.reject();
} else {
deferred.resolve();
}
});
return deferred.promise;
}];
$stateProvider
.state('dashboard', {
url: '/dashboard',
templateUrl: 'views/dashboard.html',
resolve: {
hasAccessFoo: hasAccessFooFunction
}
})
.state('user', {
abstract: true,
url: '/user',
resolve: {
hasAccessFoo: hasAccessFooFunction
},
template: '<ui-view/>'
})
.state('user.create', {
url: '/create',
templateUrl: 'views/user/create.html'
})
.state('user.list', {
url: '/list',
templateUrl: 'views/user/list.html'
})
.state('user.edit', {
url: '/:id',
templateUrl: 'views/user/edit.html'
})
.state('visitors', {
url: '/gram-panchayat',
resolve: {
hasAccessFoo: hasAccessFooFunction
},
templateUrl: 'views/visitor/list.html'
});
And according to the docs https://github.com/angular-ui/ui-router/wiki/Nested-States-%26-Nested-Views#inherited-resolved-dependencies resolve are inherited:
New in version 0.2.0
Child states will inherit resolved dependencies from parent state(s),
which they can overwrite. You can then inject resolved dependencies
into the controllers and resolve functions of child states.
But, please note:
The resolve keyword MUST be on the state not the views (in case you
use multiple views).
The best practice is to have interceptor on responseError which checks the response status and acts accordingly:
webApp.config(['$httpProvider' ($httpProvider) {
var interceptor = ['$q', '$rootScope', function ($q, $rootScope) {
return {
request: function (config) {
// can also do something here
// for example, add token header
return config;
},
'responseError': function (rejection) {
if (rejection.status == 401 && rejection.config.url !== '/url/to/login') {
// If we're not on the login page
$rootScope.$broadcast('auth:loginRequired');
}
}
return $q.reject(rejection);
}
}
}];
$httpProvider.interceptors.push(interceptor);
}]);
And handle redirection in run block
webApp.run(['$rootScope', function($rootScope){
$rootScope.$on('auth:loginRequired', function () {
$state.go('loginState');
});
}]);
The good thing is that $state service does not need to deal with permission logic:
$stateProvider
.state('someState', {
url: '/some-state',
templateUrl: '/some-state.html',
resolve: {
dataFromBackend: ['dataService', function (postingService) {
// if the request fails, the user gets redirected
return dataService.getData();
}],
},
controller: function ($scope, dataFromBackend) {
}
})
Notice
With this approach, you do not need StateService, all you need to do is to return proper response statuses from backend. For example, if the user is guest, return 401 status.
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.
I'm developing an Angular application. In this, I'm authenticating the user before going to dashboard. To achieve this I have wrote the signIn function as
Sign-In Function
this.signIn = function(credentials) {
console.info('AccountController[signIn] Called');
AuthService
.login(credentials)
.then(function(authenticatedUser) {
$scope.globals['currentUser'] = authenticatedUser;
AuthService.setCurrentUser(authenticatedUser);
$scope.globals['isAuthenticated'] = true;
$location.path('/dashboard');
}).catch(function(error) {
console.warn('AccountController[signIn] :: ', error);
Flash.Error(error);
$scope.credentials.password = '';
});
};
I also want to restrict the user from accessing the routes, if they are not logged in. To achieve that I came up with this dirty code.
Routes
$stateProvider
.state('signIn', {
url: '/signIn',
templateUrl: 'partials/signIn/signIn.html',
data: {
pageTitle: 'SignIn'
},
controller: 'AccountController',
controllerAs: 'ac',
resolve: {
auth: ['$q', 'AuthService', function($q, AuthService) {
var userInfo = AuthService.isAuthenticated();
console.info('SignIn Route[isAuthenticated] :: ', userInfo);
if (!userInfo) {
return $q.when(userInfo);
} else {
return $q.reject({
isAuthenticated: true
});
}
}]
}
})
.state('dashboard', {
url: '/dashboard',
templateUrl: 'partials/dashboard.html',
controller: 'DashboardController',
access: {
requiredLogin: true
},
resolve: {
auth: ['$q', 'AuthService', function($q, AuthService) {
var authenticated = AuthService.isAuthenticated();
console.info('dashboard Route[isAuthenticated] :: ', authenticated);
if (authenticated) {
return $q.when(authenticated);
} else {
return $q.reject({
isAuthenticated: false
});
}
}]
}
})
.state('manageStudent', {
url: '/manageStudent',
templateUrl: 'partials/manageStudent.html',
access: {
requiredLogin: true
},
resolve: {
auth: ['$q', 'AuthService', function($q, AuthService) {
var authenticated = AuthService.isAuthenticated();
if (authenticated) {
return $q.when(authenticated);
} else {
return $q.reject({
isAuthenticated: false
});
}
}]
}
});
App.run(['$rootScope', 'settings', '$state', 'AuthService', '$location', function($rootScope, settings, $state, AuthService, $location) {
$rootScope.$state = $state; // state to be accessed from view
$rootScope.$settings = settings; // state to be accessed from view
$rootScope.$on('$stateChangeStart', function(event, next,nextParams,prev,prevParams) {
// If the user is logged in don't allow him to land on the Login Page
if (next.access !== undefined) {
if (next.access.requiredLogin && !AuthService.isAuthenticated()) {
$location.path('/signIn');
}
}
});
$rootScope.$on('$stateChangeError', function(event, toState, toParams, fromState, fromParams, error) {
event.preventDefault();
if (!error.isAuthenticated) {
console.warn("I'm not Authenticated.Going to Sign-in");
return $location.path('/signIn');
} else {
console.info("I'm Authenticated");
$location.path('/dashboard');
}
});
}]);
Reason I said the above code DIRTY is because, If I have 10 routes which I want to protect from Unauthenticated user, I have to copy the same resolve function in all the routes.
So my question is , what should I do to get rid of multiple resolve function and being able to write DRY code?
Since auth should be resolved on each route change, it is insufficient to just wrap it into separate factory (which is a singleton and will run only once). To get round this limitation it should be a function
app.factory('authResolver', function ($q, AuthService) {
return function () {
// ...
};
});
which runs on every route resolve
...
resolve: {
auth: function (authResolver) {
return authResolver();
}
}
Still not that DRY, but that's the recommended humidity level.
More radical approach that may save the one from boilerplate resolve and save a few lines of code will be similar to that:
app.run(function ($rootScope, authResolver) {
$rootScope.$on('$stateChangeStart', function (e, to) {
if (to.doAuthPlease)
to.resolve.auth = authResolver();
});
});
and
...
doAuthPlease: true,
resolve: {}
The obvious difference with ngRoute in the mentioned answer is that in UI Router you need to have resolve object defined to be able to add new resolvers to the state dynamically. It can be treated like that or leaved as is.
You're on the right track so far. You have what looks like a custom data member access: { requiredLogin: true} on your state objects.
The next step is to use this with the State Change Events that ui-router provides:
$rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState) {
if (toState.access.requiredLogin) {
if(!AuthService.isAuthenticated()) {
event.preventDefault();
// redirect to signIn?
}
}
});
This would be placed in your .run block somewhere which means AuthService needs to be injected there as well. This should remove the need for the resolve block on every route.
Hope that helps.
Update:
if your AuthService.isAuthenticated() function returns a promise, it could be potentially dangerous to rely on the promise to resolve within the event handler (it may move on before the promise resolves). Its probably better that you run the AuthService function before the block (as the application starts) and then store it in a variable:
var isAuth;
AuthService.isAuthenticated().then(function (result) { isAuth = result });
$rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState) {
if (toState.access.requiredLogin) {
if(!isAuth) {
event.preventDefault();
// redirect to signIn?
}
}
});
var $delegate = $stateProvider.state;
$stateProvider.state = function(name, definition) {
var unrestricted = ['signIn'];
if (unrestricted.indexOf(name) === -1) {
definition.resolve = angular.extend({}, definition.resolve, {
auth: ['$q', 'AuthService', function($q, AuthService) {
var authenticated = AuthService.isAuthenticated();
if (authenticated) {
return $q.when(authenticated);
} else {
return $q.reject({
isAuthenticated: false
});
}
}]
});
}
return $delegate.apply(this, arguments);
};
Here I'm dynamically adding the resolve to the routes which I want to restrict.
Because you're using ui.router states (and assuming you're using v0.2.0 or greater), you can use state inheritance to solve this with the resolve and not have to duplicate it all over your various states.
What Do Child States Inherit From Parent States?
Child states DO inherit the following from parent states:
Resolved dependencies via resolve
Custom data properties
Nothing else is inherited (no controllers, templates, url, etc).
Inherited Resolved Dependencies
New in version 0.2.0
Child states will inherit resolved dependencies from parent state(s), which they can overwrite. You can then inject resolved dependencies into the controllers and resolve functions of child states.
src - https://github.com/angular-ui/ui-router/wiki/Nested-States-%26-Nested-Views#what-do-child-states-inherit-from-parent-states
I accomplish this by using an abstract base state that will defined essentially the same thing you're doing, checking to see if the user is allowed to proceed. Since all of my UI states inherit from the abstract parent state, the authentication dependency is resolved for each of them.
abstract base state
.state('baseState', {
url: '',
abstract: true,
template: '<ui-view></ui-view>'
resolve: {
auth: ['$q', 'AuthService', function($q, AuthService) {
var authenticated = AuthService.isAuthenticated();
console.info('dashboard Route[isAuthenticated] :: ', authenticated);
if (authenticated) {
return $q.when(authenticated);
} else {
return $q.reject({
isAuthenticated: false
});
}
}]
}
})
other states
.state('dashboard', {
parent: 'baseState'
url: '/dashboard',
templateUrl: 'partials/dashboard.html',
controller: 'DashboardController',
...
})
I have routes setup like so:
app.config(function($routeProvider) {
$routeProvider
//login
.when("/", {
templateUrl : "framework/views/login.html",
controller : "LoginCtrl",
title: "Login",
authenticate: false
})
//dashboard
.when("/dashboard", {
templateUrl : "framework/views/dashboard.html",
controller : "DashboardCtrl",
title: "Dashboard",
authenticate: true
});
});
Now I want to redirect location changes if authenticate is set to true on the route but a session variable is not true.
For example:
$rootScope.$on("$locationChangeStart", function(event, newURL, oldURL){
if (toState.authenticate && $window.sessionStorage.isLoggedIn) {
$location.path("/");
}
});
This works if I use $routeChangeStart instead, but then I see the next route briefly before it redirects. Location change seems to stop that, but I can't work out how to access the route parameters (i.e. the authenticate parameter).
How do I do this? Or is there a better way entirely?
you should use the resolve parameter within the .when(). This acts as a promise where you can set certain criteria that must be satisfied before the view is rendered. You can find a good demo video here: https://egghead.io/lessons/angularjs-resolve
As I stated in the comment and on demand of Cooper
I post an example:
angular.module('myApp',[])
.factory('httpInterceptor', ['$q', '$location',function ($q, $location) {
var canceller = $q.defer();
return {
'request': function(config) {
// promise that should abort the request when resolved.
config.timeout = canceller.promise;
return config;
},
'response': function(response) {
return response;
},
'responseError': function(rejection) {
if (rejection.status === 401) {
canceller.resolve('Unauthorized');
$location.url('/user/signin');
}
if (rejection.status === 403) {
canceller.resolve('Forbidden');
$location.url('/');
}
return $q.reject(rejection);
}
};
}
])
//Http Intercpetor to check auth failures for xhr requests
.config(['$httpProvider',function($httpProvider) {
$httpProvider.interceptors.push('httpInterceptor');
}])
.config(['$stateProvider',function($stateProvider) {
// states for users
$stateProvider
.state('users', {
abstract: true,
templateUrl: 'users/views/users.html',
resolve: {
issessionedin: function(Sessions){
return Sessions.isLoggedIn();
}
}
})
.state('users.account', {
url: '/user/account/:id',
templateUrl: 'users/views/account.html',
resolve: {
user: function(Users, $stateParams){
return Users.get($stateParams.id);
}
},
controller:'UserAccountController'
})
}])
.factory('Sessions', ['$http',
function($http) {
return{
isSessionedIn :function() {
$http.get('/api/v1/issessionedin');
},
isLoggedIn :function() {
$http.get('/api/v1/isloggedin');
},
hasAccess :function(permission) {
$http.get('/api/v1/hasaccess/'+permission);
}
};
}
]);
of course you need the code server side to return the http status code
When the route change, I try to access to /settings
.when('/settings', {
templateUrl: 'partials/settings',
controller: 'SettingsCtrl',
authenticate: true
})
.run(function ($rootScope, $location, Auth) {
$rootScope.$on('$routeChangeStart', function (event, next) {
Auth.checkUser();
if (next.authenticate && !Auth.isLoggedIn()) {
$location.path('/login');
}
});
The code calls to Auth.checkUser() (Asynch) and later to !Auth.isLoggedIn(). The problem is that I need to wait for Auth.checkUser(), that gives me the right status of the user, or find a way to check the response before serving the template.
checkUser: function(user, callback) {
var cb = callback || angular.noop;
return User.check(user,
function(user) {
$rootScope.currentUser = user.id;
return cb(user);
},
function(err) {
return cb(err);
}).$promise;
},
isLoggedIn: function() {
var user = $rootScope.currentUser;
return !!user;
}
You should use the resolve mechanism of angularjs controllers. This way you can define any pre-requisite (service injection, waiting promises, etc) of your controller before render it.
See angular doc : https://docs.angularjs.org/api/ngRoute/provider/$routeProvider
See a working example on angular-app project : https://github.com/angular-app/angular-app/tree/master/client/src/common/security
Example :
.when('/settings', {
templateUrl: 'partials/settings',
controller: 'SettingsCtrl',
authenticate: true,
resolve: {
isUserLogged: ['yourservice' , function(yourservice) {
return yourservice.isUserLoggedPromise();
}]
}
})
Your screen will be rendered only after (and if) isUserLoggedPromise is resolved