Refactor angular ui-router resolver to use it globally - angularjs

I have resolve method inside angular config. It was written to protect the view from unauthorized access. Now the problem is, if I create a different route file, I have to copy the same resolve on each file. Is there any other way so that I can write it once and use it everywhere?
(function(){
'use strict';
var app = angular.module('app');
app.config(/* #ngInject */ function($stateProvider, $urlRouterProvider) {
var authenticated = ['$q', 'MeHelper', '$state', function ($q, MeHelper, $state) {
var deferred = $q.defer();
MeHelper.ready()
.then(function (me) {
if (me.isAuthenticated()) {
deferred.resolve();
} else {
deferred.reject();
$state.go('login');
}
});
return deferred.promise;
}];
$stateProvider
.state('index', {
url: "",
views: {
"FullContentView": { templateUrl: "start.html" }
}
})
.state('dashboard', {
url: "/dashboard",
views: {
"FullContentView": { templateUrl: "dashboard/dashboard.html" }
},
resolve: {
authenticated: authenticated
}
})
$urlRouterProvider.otherwise('/404');
});
})();
Edit: MeHelper is a Service.

To refactor your code, you should register a service and take the authentication code to the service.
Authenticate service:
app.factory('authenticateService', ['$q', 'MeHelper',
function($q,MeHelper){
var obj = {};
obj.check_authentication = function(params)
{
var deferred = $q.defer();
MeHelper.ready()
.then(function (me) {
if (me.isAuthenticated()) {
deferred.resolve();
} else {
deferred.reject();
$state.go('login');
}
});
return deferred.promise;
}
return obj;
}
]);
Then, use this service in any route file in resolve, taking this service name in dependency injection or the function parameter,
Route configuration file:
(function(){
'use strict';
var app = angular.module('app');
app.config(/* #ngInject */ function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('index', {
url: "",
views: {
"FullContentView": { templateUrl: "start.html" }
}
})
.state('dashboard', {
url: "/dashboard",
views: {
"FullContentView": { templateUrl: "dashboard/dashboard.html" }
},
resolve: {
authenticated:
function(authenticateService) {
return authenticateService.check_authentication();
}
}
})
$urlRouterProvider.otherwise('/404');
});
})();
watch the below lines, this is what we changes in the route configuration to resolve.
the service is injected in below lines:
resolve: {
authenticated:
function(authenticateService) {
return authenticateService.check_authentication();
}
}

Do your check on route change.
app.run(function ($rootScope, $state) {
$rootScope.$on('$locationChangeSuccess', function () {
if (unauthorized && $state.current.name !== 'login') {
$state.go('login');
}
});
});

Related

angularjs resolve is undefined

so in my state, i have
angular.module('app', ['ui.router', 'chart.js'])
.config(['$stateProvider', '$urlRouterProvider', function ($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/');
$stateProvider
.state('home', {
url: '/home',
component: 'home',
resolve: {
data: ['$http', function ($http) {
return $http.get('some api call')
.then(function (response) {
console.log("this is the response", response);
return response;
});
}]
}
});
}]);
then i get the proper response back. but when i check my resolve in here,
angular.module('app')
.component('home', {
templateUrl: 'Content/app/components/home.html',
bindings: {
resolve: '<'
},
controller: [
function () {
var vm = this;
vm.$onInit = function () {
console.log("this is the resolve", vm)
}
}]
});
i see that my resolve is undefined. Am i doing something wrong?
$stateProvider will bind what you specify inside the resolve object to your component, rather than binding the whole resolve object itself.
angular.module('app')
.component('home', {
templateUrl: 'Content/app/components/home.html',
bindings: {
data: '<'
},
controller: [
function () {
var vm = this;
vm.$onInit = function () {
console.log("this is the resolve", vm)
}
}]
});
Documentation link: https://ui-router.github.io/ng1/docs/latest/interfaces/state.statedeclaration.html#as-an-object

remove exclamation from routing of state url Mean.io

I want to remove exclamation marks from url state routing like my url is now
http://localhost:3000/#!/auth/register
i just want to remove this "!" marks from url after "#"
Is it possible to do? with mean.io
here is my app.js/system.js
'use strict';
//Setting up route
angular.module('mean').config(['$stateProvider', '$urlRouterProvider',
function($stateProvider, $urlRouterProvider) {
// For unmatched routes:
//$urlRouterProvider.otherwise('/');
var checkLoggedin = function($q, $timeout, $http, $location) {
// Initialize a new promise
var deferred = $q.defer();
// Make an AJAX call to check if the user is logged in
$http.get('/loggedin').success(function(user) {
// Authenticated
if (user !== '0') $timeout(deferred.resolve);
// Not Authenticated
else {
$timeout(deferred.reject);
$location.url('/auth/login');
}
});
return deferred.promise;
};
// console.log($stateProvider);
// states for my app
$stateProvider
.state('tasks', {
url: '/kanban/:projectId/:projectSlug',
templateUrl: 'system/views/index.html',
controller: 'IndexController',
resolve: {
loggedin: checkLoggedin,
onEnter: function($stateParams,$state, $uibModal) {
if ( $stateParams.projectId != "" ) {
updateTopMenu('Kanban','task','#!/kanban/'+$stateParams.projectId+'/'+$stateParams.projectSlug);
updateTopMenu('Schedule','schedule','#!/schedule');
}
}
}
}).state('home',{
url:'/',
templateUrl: 'projects/views/index.html',
controller: 'ProjectController',
resolve:{
loggedin: checkLoggedin
}
}).state('taskEdit',{
url:'/kanban/:projectId/:projectSlug/:taskSlug',
templateUrl: 'system/views/index.html',
controller: 'IndexController',
resolve:{
loggedin: checkLoggedin
}
}).state('taskAdd',{
url: "/task/taskAdd",
onEnter: function($stateParams, $state, $uibModal) {
$uibModal.open({
templateUrl: "system/views/include/model.html",
resolve: {},
controller: function($scope, $state, itemService) {
/*
$scope.state = $state.current;
$scope.params = $stateParams;
$scope.item = itemService.get($stateParams.id);
*/
$scope.ok = function () {
$scope.$close('clicked ok');
};
$scope.dismiss = function () {
$scope.$dismiss('clicked cancel');
};
}
}).result.then(function (result) {
// $scope.$close
alert('result ->' + result);
}, function (result) {
// $scope.$dismiss
return $state.transitionTo("home");
alert('dismiss ->' + result);
}).finally(function () {
// handle finally
return $state.transitionTo("tasks");
});
}
});
}
]).config(['$locationProvider',
function($locationProvider) {
$locationProvider.hashPrefix('!');
}
]);
You can make some transformation on url with $urlRouterProvider.rule in your config function like this:
$urlRouterProvider.rule(function ($injector, $location) {
var path = $location.path(),
normalized = path.replace('!/', '');
if (path !== normalized) {
return normalized;
}
});
Not sure if this is the best way but it seems to work.
Inspired from: https://ui-router.github.io/docs/1.0.0-alpha.5/classes/url.urlrouterprovider.html

Use (asynchronous) $http result to (synchronously) check permissions on routes

I'm trying to check permissions on routes in an AngularJS application. Routes are handled by angular-ui-router.
My routes and permissions-per-route are defined as this:
angular.module('my_app')
.config(function ($stateProvider, $urlRouterProvider) {
$stateProvider
.state('root', {
abstract: true,
templateUrl: 'content.html',
resolve: {
user_info: function (UserService) {
return UserService.user_info();
}
}
})
.state('root.some_page', {
url: '/',
templateUrl: 'some_page.html',
controller: 'home',
data: {
roles: ['some_right', 'some_other_right]
}
});
}
I then check for permissions as this:
[...].run(function ($rootScope, $state, UserService) {
$rootScope.$on('$stateChangeStart',
function (event, toState) {
if (!!toState.data) {
var required_roles = toState.data.roles;
if (!UserService.has_permission_in(required_roles)) {
event.preventDefault();
$state.go('root.access_forbidden');
}
}
});
});
All good for now. My issue is resolving user permissions. There are provided by a backend and fetched using $http.get(...). Since the $http service returns a promise, it don't always have the result when the $stateChangeStart is event triggered.
It's "good" when the application is "warm", but if I land on a protected page, it fails (the data are not ready). I have no way to "wait" for the promise.
What would be a "good" way of doing access control on routes?
In your protected state's definition:
.state('root.some_page', {
url: '/',
templateUrl: 'some_page.html',
controller: 'home',
resolve: {
access: function($http, $state){ //Note: you can inject your own services/factories too
var requiredPermissions = ['some_right', 'some_other_right'];
return $http.get(...).then(function(response){
var permissions = response.data.permissions;
var granted = permissions.reduce((prev,curr) => requiredPermissions.indexOf(curr) > -1 || prev, false);
if(!granted)
$state.go('publicState');
return permissions;
});
}
}
})
Resolve waits for promises to settle before moving to a new state.
Better:
function checkPermissions(requiredPermissions){
return function ($http, $state){ //Note: you can inject your own services/factories too
return $http.get(...).then(function(response){
var permissions = response.data.permissions;
var granted = permissions.reduce((prev,curr) => requiredPermissions.indexOf(curr) > -1 || prev, false);
if(!granted)
$state.go('publicState');
return permissions;
});
}
}
//In state definition
.state('root.some_page', {
url: '/',
templateUrl: 'some_page.html',
controller: 'home',
resolve: {
access: checkPermissions(['first','second'])
}
})

Easily redirect to login in Angular?

Is there an easier way to redirect to the login state without using a resolve: {} on each state? My app is growing and adding resolves like loginRequired or skipIfLoggedIn to individual states is cluttering my config blocks. If it helps, I'm using Satellizer for authentication.
Example module route config:
(function () {
'use strict';
angular
.module('uberCoolModule')
.config(configRoutes);
configRoutes.$inject = ['$stateProvider'];
function configRoutes ($stateProvider) {
$stateProvider
.state('login', {
url : '/login',
templateUrl : 'entrance/login/login.html',
data : { pageTitle: 'Login' },
resolve: {
skipIfLoggedIn: skipIfLoggedIn
}
})
.state('logout', {
url : '/logout',
controller : 'Logout'
resolve: {
loginRequired: loginRequired
}
})
.state('members', {
url : '/members',
controller : 'Home'
resolve: {
loginRequired: loginRequired
}
});
function skipIfLoggedIn($q, $auth) {
var deferred = $q.defer();
if ($auth.isAuthenticated()) {
deferred.reject();
} else {
deferred.resolve();
}
return deferred.promise;
}
function loginRequired($q, $location, $auth) {
var deferred = $q.defer();
if ($auth.isAuthenticated()) {
deferred.resolve();
} else {
$location.path('/login');
}
return deferred.promise;
}
}
})();
The easiest way is have a parent state that contains the authorization resolve and make all states that require login be children of that parent state.
A child state can not be accessed if resolve of any of it's ancestor states gets rejected
app.state('members', {
url: '/members',
controller: 'Home'
resolve: {
loginRequired: loginRequired
}
}).state('members.profile', {
url: '/profile',
});
In this example going to /members/profile will fail if loginRequired is rejected

AngularJS UI router: Block view

Right now i am making an AngularJS+UI router install application. But i have a problem, the problem is, that i want to disable access to the views, associated with the install application. I want to do it in resolve in the state config.
But the problem is i need to get the data from a RESTful API, whether the application is installed or not. I tried making the function, but it loaded the state before the $http.get request was finished.
Here was my code for the resolve function:
(function() {
var app = angular.module('states', []);
app.run(['$rootScope', '$http', function($rootScope, $http) {
$rootScope.$on('$stateChangeStart', function() {
$http.get('/api/v1/getSetupStatus').success(function(res) {
$rootScope.setupdb = res.db_setup;
$rootScope.setupuser = res.user_setup;
});
});
}]);
app.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise("/404");
$stateProvider.state('db-install', {
url: "/install/db",
templateUrl: 'admin/js/partials/db-install.html',
controller: 'DBController',
resolve: {
data: function($q, $state, $timeout, $rootScope) {
var setupStatus = $rootScope.setupdb;
var deferred = $q.defer();
$timeout(function() {
if (setupStatus === true) {
$state.go('setup-done');
deferred.reject();
} else {
deferred.resolve();
}
});
return deferred.promise;
}
}
})
.state('user-registration', {
url: "/install/user-registration",
templateUrl: "admin/js/partials/user-registration.html",
controller: "RegisterController"
})
.state('setup-done', {
url: "/install/setup-done",
templateUrl: "admin/js/partials/setup-done.html"
})
.state('404', {
url: "/404",
templateUrl: "admin/js/partials/404.html"
});
}]);
})();
EDIT:
Here is what my ajax call returns:
Try this way:
$stateProvider.state('db-install', {
url: "/install/db",
templateUrl: 'admin/js/partials/db-install.html',
controller: 'DBController',
resolve: {
setupStatus: function($q, $state, $http) {
return $http.get('/api/v1/getSetupStatus').then(function(res) {
if (res.db_setup === true) {
$state.go('setup-done');
return $q.reject();
}
return res;
});
}
}
})
Then inject setupStatus in controller:
.state('setup-done', {
url: "/install/setup-done",
templateUrl: "admin/js/partials/setup-done.html",
controller: ['$scope', 'setupStatus', function ($scope, setupStatus) {
$scope.setupdb = setupStatus.db_setup;
$scope.setupuser = setupStatus.user_setup;
}]
})

Resources