AngularJS navigation - angularjs

I have a requirement that states:
Logged in user can view employee list, individual employee view etc. For administrator, left navigation menu provides list of different entities and administrator should be able to view list of all employees for selected entity.
But I need to navigate administrator to home page once the entity is changed in left navigation drop down. employee-config.js uses employeeListCtrl and home page has its own controller (defined in separate home-config.js)
How to navigate from
$stateProvider.state("employeeList",{
url: "/employee/list",
templateUrl: cfg.URL_ROOT + "/IC/employee-app/employee-list/employee-list.html",
data: {
listStateName: "homeMain" // This is not working
},
controller: "employeeListCtrl as vm"
});
TO
$stateProvider
.state("homeMain", {
url: "/home/main",
templateUrl: cfg.URL_ROOT + "/IC/home-app/home-main/home-main.html",
data: {
listStateName: "homeMain"
},
controller: "homeMainController as vm"
});

If you talking about just redirecting to other states you can do with simple
$location.url("here your state names like 'employeeList')
and if you are talking about authorization of the current users this you can do with $stateprovider's onstatechangestart function in .config i would share this code for reference.
Blockquote
$rootScope.$on("$routeChangeStart", function(event, next, current) {
$rootScope.onLoading(); var authorised = null;
if (next.access !== undefined && next.access.requiresLogin) {
var userLoggedIn = UserService.getCurrentUser();
authorization.getConditions(next.access,userLoggedIn);
} });
there you can find next.access with reference to routes
ROUTER.when('dashboard_path', '/dashboard', {
controller : 'DashboardCtrl',
templateUrl : CONFIG.prepareViewTemplateUrl('videos/index'),
access: {
requiresLogin: true,
requiredPermissions: ['admin', 'AccountManager'],
permissionType: 'AtLeastOne'
}
});
there access would store values and we can get it into routechange function and compare.
Please note there is authorization is service which check server side user session existance.
please let me know if it helps or needed more details.

Related

How do you protect /handle authenticated routes with ui-router?

Just started using angular and I'm trying to learn as fast as I can. I'm relatively new to SPA's so please bear with me and feel free to tell me if what I want to do is not feasible. What I'm currently stuck on now, is how do I protect my routes when using the ui-router?
What do I want to do?
There are routes that I don't want non-logged in users to access.
For example, /home and /login are okay for anonymous users.
/dashboard should only be for those that are logged in.
I want it so if a user tries to access /dashboard in the future without being logged in, they are not able to.
What have I already tried?
I have tried using the angular-permission module found here: https://github.com/Narzerus/angular-permission
The problem is..I'm not quite sure how to use it (nor if I'm using it properly).
What is currently happening?
In my login controller, once a user submits their username and password it makes a /POST to my web-sever. Once it gets the result, (regardless of what it is for the moment) I've got it redirecting to /dashboard.
Right now nothing should be getting to the /dashboard because no permissions have been set, yet I am (incorrectly) allowed to see the dashboard. I can both (1) successfully be redirected to the dashboard without permission and (2) access /dashboard without permission.
What does my code look like right now?
controllers.js
var controllers = angular.module('controllers',[])
// Login Controller -- This handles the login page that the user can enter
// enter his username & password.
controllers.controller('loginController', function($scope, $state,$location, LoginService){
$scope.email = "";
$scope.password = ""
$scope.login = function(){
var data = ({email:"test", password: "ayylmao"})
LoginService.login(data).then(function(res){
console.log(res);
})
.catch(function(err){
console.log("ERROR!");
console.log(err);
$state.go('dashboard')
})
}
})
app.js
//Definition: The parent module
var myApp = angular.module('clipboardApp', ['services','controllers', 'permission','ui.router']);
//Code below taken from the angular-permission docs.
angular
.module('fooModule', ['permission', 'user'])
.run(function (PermissionStore, User) {
// Define anonymous permission)
PermissionStore
.definePermission('anonymous', function (stateParams) {
// If the returned value is *truthy* then the user has the permission, otherwise they don't.
//True indicates anonymous.
//Always returning true to indicate that it's anonymous
return true;
});
});
//This will be serving as the router.
myApp.config(function($stateProvider, $urlRouterProvider, $locationProvider) {
//By default go
$urlRouterProvider.otherwise('/home');
//Views are
$stateProvider
.state('home', {
url: '/home',
templateUrl: 'views/home.html',
})
.state('login', {
url: '/login',
templateUrl: 'views/login.html',
controller: 'loginController'
})
.state('dashboard', {
url: '/dashboard',
templateUrl: 'views/dashboard.html',
controller: 'dashboardController',
data: {
permissions: {
except: ['anonymous'],
redirectTo: 'login'
}
}
});
});
Here is a working example with secured routes. In this example any state start with app. will go via the auth interceptor. $transitions.onBefore hook can be use as follows to satisfy your requirement.
.run(($transitions, $state, $injector) => {
$transitions.onBefore({
to: 'app.**'
}, () => {
const $window = $injector.get('$window');
if (!$window.sessionStorage.getItem('user')) {
return $state.target('login', $state.transition.params());
}
return true
});
});
https://plnkr.co/edit/ZCN2hB34mMEmBJulyaAJ?p=info

Angular UI-router new state based on user id

I'm using angular-ui-router to build a one page app.
In my "cerca" state I have a table that show all user in my db.
The last columns of any row contains a link to the i-th user.
I would like to be able to click on this link and show a user page with all information about a specific user (maybe retrieved from db using user id).
In my "cerca" state I have the ids of all users. I'm trying to use them to build a dynamic url but I have difficult to pass the id to the new state. This is my app.config:
app.config(function($stateProvider, $urlRouterProvider){
$urlRouterProvider.otherwise('/home');
$stateProvider
.state('home',{
url:'/home',
templateUrl: 'pages/home.html',
controller: 'homeCtrl'
})
.state('inserisciP',{
url:'/inserisciP',
templateUrl: 'pages/inserisciPaziente.html',
controller: 'inserisciController'
})
.state('cerca',{
url:'/cerca',
templateUrl:'pages/cercaPaziente.html',
controller: 'cercaController'
})
.state('paziente',{
url:'/paziente',
templateUrl:'pages/paziente.html',
controller: 'pazienteController'
})
.state('inserisciE',{
url:'/inserisciE',
templateUrl:'pages/inserisciEsame.html'
})
.state('listaLavoro',{
url:'/listaLavoro',
templateUrl:'pages/listaLavoro.html',
})
.state('refertazione',{
url:'/refertazione',
templateUrl:'pages/refertazione.html'
})
.state('nomeUtente',{
url:'/nomeUtente',
templateUrl:'pages/nomeUtente.html'
})
.state('amministrazione',{
url:'/amministrazione',
templateUrl:'pages/amministrazione.html'
})
.state('richiestaRadiologia',{
url:'/richiestaRadiologia',
templateUrl:'pages/richiestaRadiologia.html'
})
.state('help',{
url:'/help',
templateUrl:'pages/help.html'
});
});
and this is my table:
<tr ng-repeat="p in copiaTable">
<td>{{p.id}}</td>
<td>{{p.last_name}}</td>
<td>{{p.first_name}}</td>
<td>{{p.codice_fiscale}}</td>
<td>{{p.phone_number}}</td>
<td><a ui-sref="paziente">edit</a></td>
</tr>
I also tryed to create a service to share data between states...
Any suggestion on how can I solve this issue with ui-router?
You can pass a parameter to a state when you click the link. Let's say you want to pass an user ID. The code of the link would be like this:
<a ui-sref="paziente({userID: p.id})">edit</a>
In your route configuration file, you can define paziente state like this:
state('paziente',{
url:'/paziente/:userID',
templateUrl:'pages/paziente.html',
controller: 'pazienteController'
})
With the passed ID, You can find an user from your factory.
The user data should be resolved like this:
state('userInfo', {
url: 'user/:userId',
templateUrl: 'userProfile.html',
resolve: {
user: function($stateParams, UserService) {
return UserService.getUser($stateParams.userId);
}
}
});

Refresing the state after logging in, in order to display content depending on user roles in Angular JS & ui-router

In my angular app, when a user visits the "home" state (I am using ui-router), he gets a welcome message.
When pressing a login button, a overlay appears and the user can authenticate.
Upon a successful authentication, I want to do some API calls and display stuff on the template.
All of this in code, looks like this:
.config(['$stateProvider', function($stateProvider) {
$stateProvider.state('home', {
url: "/",
templateUrl: "app/sections/home/homeTpl.html",
controller: 'HomeCtrl'
});
}])
.controller('HomeCtrl', ['$scope', 'Session', 'MoviesService', function ($scope, Session, MoviesService) {
if (Session.getUserRole() === 'USER') {
// Add a movie.
$scope.newMovie = {
name: 'I am the new movie'
};
MoviesService.save($scope.newMovie);
}
}])
When the users authenticates, the controller check's the user role and if he is a "USER" do stuff....
Now, in order for everything to work, I need Angular ui-router to refresh the state, after logging in:
$state.transitionTo($state.current, {}, {
reload: true,
inherit: false,
notify: true
});
While it works, I am not sure this is the way to go regarding best practices...
Any ideas?
I don't know if you really need notify but you can use $state.reload that an alias for transitionTo. $state doc
After I think your are right, I do the same for my app.

Controlling order of operations with services and controllers

I have two services - one to store user details and the other to make a call to retrieve those details:
userService stores user details to be used across the entire app (i.e. injected in controllers, services, etc.)
function userService($log) {
var id = '';
var username = '';
var isAuthenticated = false;
var service = {
id: id,
username: username,
isAuthenticated: isAuthenticated
};
return service;
}
authService is used (hopefully just once) to retrieve the user details from a Web API controller:
function authService($log, $http, userService) {
$log.info(serviceId + ': Inside authService method');
var service = {
getUserDetails: getUserDetails
};
return service;
function getUserDetails() {
$log.info(serviceId + ': Inside getUserDetails method');
return $http.get('api/authentication', { cache: true });
}
}
Initially, I had the call to the authService fire in a .run block like so:
.run(['$log', 'authService', 'userService', function ($log, authService, userService) {
authService.getUserDetails()
.then(querySucceeded);
function querySucceeded(result) {
userService.id = result.data.Id;
userService.username = result.data.username;
}
}]);
But the problem was that the getUserDetails-returned promise did not resolve until after I my controllers fired and, thus, too late for me. The user data was not ready.
I then looked at the resolve option in the $stateProvider (for UI-Router):
.state('dashboard', {
url: '/dashboard',
views: {
header: {
templateUrl: 'app/partials/dashboard/header.template.html',
controller: 'DashboardHeaderController',
controllerAs: 'dashboardHeaderVM',
resolve: {
user: function (authService) {
return authService.getUserDetails();
}
}
}
}
})
The assumption is that the view won't be rendered until the promise in the resolve section is, well, resolved. That seems to work fine.
Here's the (relevant part of the) controller where I use the returned user property:
function DashboardHeaderController($log, user) {
var vm = this;
// Bindable members
vm.firstName = user.data.firstName;
}
However, I have two routes (more to come) and a user can navigate to either one. Do I need to have a resolve property in each state section for the authService? I want to fire the call to authService.getUserDetails just once no matter which route is served and have it available after that for any route, controller, etc.
Is there a better (best practice) way to do this?
Not sure about better or best practice, but here is a plunker with my way.
The point is to move resolve into some parent root state. The one who is ancestor of all states in the application:
$stateProvider
.state('root', {
abstract : true,
// see controller def below
controller : 'RootCtrl',
// this is template, discussed below - very important
template: '<div ui-view></div>',
// resolve used only once, but for available for all child states
resolve: {
user: function (authService) {
return authService.getUserDetails();
}
}
})
This is a root state with resolve. The only state with resolve. Here is an example of its first child (any other would be defined similar way:
$stateProvider
.state('index', {
url: '/',
parent : 'root',
...
This approach will work out of the box. I just would like to mention that if the 'RootCtrl' is defined like this:
.controller('RootCtrl', function($scope,user){
$scope.user = user;
})
we should understand the UI-Router inheritance. See:
Scope Inheritance by View Hierarchy Only
small cite:
Keep in mind that scope properties only inherit down the state chain if the views of your states are nested. Inheritance of scope properties has nothing to do with the nesting of your states and everything to do with the nesting of your views (templates).
It is entirely possible that you have nested states whose templates populate ui-views at various non-nested locations within your site. In this scenario you cannot expect to access the scope variables of parent state views within the views of children states...
More explanation could be found in this Q & A
So, what does it mean?
Our root view can pass the resolved stuff into child state only - if their views are nested.
For example, the $scope.user will be inherited in child states/views/$scopes only if they are nested like this
.state('index', {
url: '/',
parent : 'root',
views: {
'' : { // the root view and its scope is now the ancestor
// so $scope.user is available in every child view
templateUrl: 'layout.html',
controller: 'IndexCtrl'
},
'top#index' : { templateUrl: 'tpl.top.html',},
'left#index' : { templateUrl: 'tpl.left.html',},
'main#index' : { templateUrl: 'tpl.main.html',},
},
Check it here
If I correctly understand you want that on page load you would have user info before any controller or service request it.
I had similar task in my current project.
To solve the problem I manually requested current user info before app bootstapping & store it in localStorage.
Then after app bootstrapping all controllers/services have accesss to current user info.
TIP: to get user info before app bootstrap you can still use $http service by manually injecting it:
angular.injector(['ng']).get('$http');

Angular UI Router - Default Parameter

Is there anyway to specify a default parameter for every route using the Angular UI Router?
My app is entered through the context of another application by selecting a user and then navigating to my application. The URL in my application will always have the user ID in the URL so that people can bookmark the URL, email it, etc. So, as you navigate around, the URL always follows a scheme of:
#/{userId}/view/...
#/{userId}/edit/...
etc.
This userId will always be the same for a user inside the app for any route they go to. If they happen to log out, go back to the main app, select a new user and come back to my app, this userId will change, but will be the same value for every route.
Is there anyway to read this value from say a service/factory and then plug it into every route?
EDIT:
I should mention I want to avoid having to explicitly set this parameter on every route when I navigate to a state. For example, I don't want to have to do ui-sref="new-state({userId : blah})" every time I navigate to a new state. That userId will never change in the context of my application.
EDIT AGAIN:
I actually went about this a different way concerning the requirement to not have to send 'userId' to every route manually. Instead of using a directive, I used a $provide.decorator to add this functionality to the 'go' method. I've added an answer below to what I did.
You can declare an abstract parent state from which child states inherit:
$stateProvider
.state('user', {
url: '/:userid',
abstract: true,
resolve:
// assuming some kind of User resource factory
currentUser: function($stateParams, User) {
return User.get($stateParams.userid);
}
}
})
.state('user.view', {
url: '/view', // actual url /:userid/view
controller: function($scope, currentUser) {
// currentUser resource available
}
});
.state('user.edit', {
url: '/edit', // actual url /:userid/edit
controller: function($scope, currentUser) {
// currentUser resource available
}
});
In terms of navigating to a state, you need to pass in the desired user:
$state.go('user.view', {userid: 'myuserid'});
As a consequence it might make sense to create some kind of .go() wrapper method on your currentUser service, so that you needn't specify the user id each time.
UPDATE:
To counter the problem posted in your edit, you could introduce a directive like this:
angular.module('app')
.directive('userSref', function($state) {
return function(scope, elem, attrs) {
var state = 'user.' + attrs.userSref;
elem.bind('click', function() {
$state.go(state, {userid: $state.params.userid});
});
scope.$on('$destroy', function() {
elem.unbind('click');
});
};
});
Then, any future links to user-based states can be done so with:
<a user-sref="view">View User</a>
Instead of writing a directive that handled the auto-sending of userID, I used $provide.decorator as follows:
app.config(['$provide',
function($provide) {
$provide.decorator('$state', function($delegate, UserService) {
// Save off delegate to use 'state' locally
var state = $delegate;
// Save off reference to original state.go
state.baseGo = state.go;
// Decorate the original 'go' to always plug in the userID
var go = function(to, params, options) {
params.userID = UserService.userID;
// Invoke the original go
this.baseGo(to, params, options);
};
// assign new 'go', decorating the old 'go'
state.go = go;
return $delegate;
});
}
]);
I got the idea from this post:
Changing the default behavior of $state.go() in ui.router to reload by default
You can use the "nested states" and "resolves" features of UI-Router to create a hierarchy of states in your app. You'll define a top level state that resolves the userId. Then define any number of child states, and they will automatically inherit the "resolved" userId.
Check out this page of the documentation, in particular the section titled "Important $stateParams Gotcha". I will paste the two code snippets from that page here.
Incorrect method:
$stateProvider.state('contacts.detail', {
url: '/contacts/:contactId',
controller: function($stateParams){
$stateParams.contactId //*** Exists! ***//
}
}).state('contacts.detail.subitem', {
url: '/item/:itemId',
controller: function($stateParams){
$stateParams.contactId //*** Watch Out! DOESN'T EXIST!! ***//
$stateParams.itemId //*** Exists! ***//
}
})
Correct method using "resolves":
$stateProvider.state('contacts.detail', {
url: '/contacts/:contactId',
controller: function($stateParams){
$stateParams.contactId //*** Exists! ***//
},
resolve:{
contactId: ['$stateParams', function($stateParams){
return $stateParams.contactId;
}]
}
}).state('contacts.detail.subitem', {
url: '/item/:itemId',
controller: function($stateParams, contactId){
contactId //*** Exists! ***//
$stateParams.itemId //*** Exists! ***//
}
})
Since the "contactId" parameter is resolved by the parent state, the child state will inherit that.

Resources