Angular - same urls with different templates (ui-router) - angularjs

I have a scenario, where many of the urls are accessible while logged in and out as well. I was thinking of having a parent state where I can handle the necessary resolving (getting user info if logged in, etc.) and all of the state would be a child of this root state. Then in the templates I can do ng-if for elements depending on user status. This is all good, except one thing: The API server sends 401 responds when the access token is invalid which causes the state not to load.
I would really like to solve this issue while keeping the HTTP responds.
Basic example:
Root state:
$stateProvider.state('base', {
controller: 'BaseController',
controllerAs: 'vm',
template: '<ui-view layout="row" flex />',
abstract: true,
resolve: {
me: function (userService) {
return userService.me();
}
},
data: {
auth: false
}
});
Home state:
$stateProvider.state('base.home', {
controller: 'HomeController',
controllerAs: 'vm',
url: '/home',
templateUrl: 'home/home.html',
data: {
auth: false
}
});
What I would like to do is even if me resolves to 401 still load the controller/template WHEN the data.auth is false. Otherwise I would just $state.go('login') or something like that.

What's happening is UI-Router is getting the 401 response from your Ajax call, in which case it won't be able to resolve me, and doesn't load the state. You need to wrap your ajax call in another Promise, that way you can handle the 401, and still resolve the Promise, just resolve it with null.
$stateProvider.state('base', {
controller: 'BaseController',
controllerAs: 'vm',
template: '<ui-view layout="row" flex />',
abstract: true,
resolve: {
me: ['userService', function (userService) {
return userService.me().catch(function(error) {
//user is not logged in, but eat the error so
//the state still loads, just with user = null
return null;
});
}]
},
data: {
auth: false
}
});

Related

Is it possible to use resolves outside of states?

I have site navigation that doesn't exist in any particular state. It's always available along the top of the page, regardless of which state the application is in.
I need to hide/show certain menu options depending on who the user is. I'm using windows authentication so a trip to the server is a necessity. The problem is since the nav bar doesn't belong to any particular state I don't know where to put the resolve.
Is there something like a global state which would be resolved first before any other states where I could put the resolve?
Something like:
.state('$global', {
url: '/',
templateUrl: 'partials/navigation/navbar.html',
controller: 'NavCtrl',
resolve: {
navData: ['$http', 'SettingsFactory', 'ViewMatrixService', function($http, SettingsFactory, ViewMatrixService) {
return $http.get(SettingsFactory.APIUrl + 'api/nav', { withCredentials: true }).then(function (response) {
ViewMatrixService.GenerateHomeViewMatrix(response.data.CurrentUser);
return response.data;
});
}]
}
})
I considered using $broadcast but then I'd need to make sure every possible point of entry to the application gets the information the nav bar needs from the server and broadcasts it which contaminates all my other controllers with nav bar responsibilities.
I was unable to find a way to use resolves outside of states, but I did find a solution.
I created an abstract parent state called app. I have a resolve in app that gets the user's profile. Then it can be injected into any child state controllers.
Since the nav bar is stateless, in the same resolve I pass the currentUser object to a service which is used across the app to store visibility and disabled flags for all controls.
It looks like this:
$stateProvider
.state('app', {
abstract: true,
template: '<ui-view/>',
resolve: {
currentUser: ['$http', 'SettingsFactory', 'ViewMatrixService', function($http, SettingsFactory, ViewMatrixService) {
return $http.get(SettingsFactory.APIUrl + 'api/users/current', { withCredentials: true }).then(function (response) {
ViewMatrixService.GenerateHomeViewMatrix(response.data);
return response.data;
});
}
]}
})
.state('app.home', {
url: '/',
templateUrl: 'partials/home.html',
controller: 'HomepageCtrl',
resolve: {
homeData: ['$http', 'SettingsFactory', function($http, SettingsFactory) {
return $http.get(SettingsFactory.APIUrl + 'api/home', { withCredentials: true }).then(function (response) {
return response.data;
});
}]
}
})
.state('app.userProfiles', {
url: '/admin/users',
templateUrl: 'partials/admin/user-profiles.html',
controller: 'UserProfilesCtrl'
})...

$http is getting called multiple times in AngularJS

In my project the $http call is getting called more than once every time. Check the code below.
router.js
angular.module('adminsuite',['ngFileUpload','ui.router','ngCookies','angular-clipboard','ngAnimate', 'ngSanitize', 'ui.bootstrap','ngMessages']).constant("__env",env).config(function($stateProvider, $urlRouterProvider, $locationProvider) {
$urlRouterProvider.otherwise('/');
//$locationProvider.html5Mode(true);
$stateProvider
.state('login', {
url: '/',
views:{
header:{
templateUrl: '',
controller: ''
},
pageContent:{
templateUrl: 'Login/login3.html',
controller: 'loginController'
},
footer:{
templateUrl: 'common/footer3.html',
controller: 'footerController'
}
}
})
// HOME STATES AND NESTED VIEWS ========================================
.state('dashboard', {
url: '/dashboard',
views:{
header:{
templateUrl: 'common/header.html',
controller: 'headerController'
},
pageContent:{
templateUrl: 'dashboard/dashboard.html',
controller: 'dashboardController'
},
footer:{
templateUrl: 'common/innerFooter.html',
controller: 'footerController'
}
}
})
//SURVEY STATES
.state('survey', {
url: '/survey',
views:{
header:{
templateUrl: 'common/headerTool.html',
controller: 'headerController'
},
pageContent:{
templateUrl: 'survey/survey.html',
controller: 'surveyController'
},
footer:{
templateUrl: 'common/innerFooter.html',
controller: ''
}
}
}).state('survey.surveyList', {
url: '/:id',
templateUrl: 'survey/surveyList.html',
controller: ''
}).state('survey.surveyList.details', {
url: '',
templateUrl: 'survey/survey-details/summary.html',
controller: ''
}).state('survey.surveyList.questionare', {
url: '',
templateUrl: 'survey/questionare/questionare.html',
controller: 'questionareController'
}).state('survey.surveyList.sampleManagement', {
url: '',
templateUrl: 'survey/sample-management/sample-management.html',
controller: ''
}).state('survey.surveyList.quotaManagement', {
url: '',
templateUrl: 'survey/quota-management/quota-management.html',
controller: 'quotaController'
}).state('survey.surveyList.scheduling', {
url: '',
templateUrl: 'survey/scheduling/scheduling.html',
controller: ''
}).state('survey.surveyList.notification', {
url: '',
templateUrl: 'survey/notification/notification.html',
controller: ''
}).state('survey.surveyList.reports', {
url: '',
templateUrl: 'survey/reports/reports.html',
controller: ''
}).state('survey.surveyList.location', {
url: '',
templateUrl: 'survey/location/location.html',
controller: ''
});
// ABOUT PAGE AND MULTIPLE NAMED VIEWS =================================
})
loginAuthentication.js
UserService.GetByUsername(requestData)
.then(function (user) {
console.log(user);
if (user.SessionID) {
sessionID = user.SessionID;
userDetails = user.UserProfile;
response = { success: true};
} else {
response = { success: false, message: 'Username or password is incorrect' };
}
callback(response);
});
UserService.js
function GetByUsername(user) {
//console.log(__env.apiUrl);
return $http.post(__env.apiUrl+'/UserAuthentication/login', user, {headers: { 'Content-Type': 'application/json'}}).then(handleSuccess, handleError('Error getting user by username'));
}
When I call this function in the backend it is called more than once. And the same thing is happening for the other APIs.
I removed all the duplicate controllers, but the issue persists.
One particular issue what am getting is sending the token id in the header section of $http call, but this call in back end happening two times and some times more than that. In first time this header token shows empty and second times or more than that time it is giving the result. please check the below code.
api call
$http.get(__env.apiUrl+'/UserSurvey/GetAllSurveys', {
headers: { 'Content-Type': 'application/json','SessionID':$rootScope.token}
})
.then(function(response){
console.log(response);
return response.data;
}, function(error){
console.log("error");
console.log(error);
return error;
});
You might be having a digest cycle issue.
When my AJAX module is located in plain Javascript my data renders on the first server query. However, when I place my AJAX module in an AngularJS module I must query the server twice before my data renders. Note the query is invoked from the UI.
After poking around I realized the data had indeed arrived on the first query and was assigned to all designated objects and variables but only within the scope of the AngularJS code. Of course, the data had arrived asynchronously and the UI had already gone through a digest cycle and had no reason to go again because nothing in the UI had changed. When there’s a change in the UI Angular updates everything but does nothing when the change (arriving data) comes from the server.
Subsequently, the second identical query would force a digest cycle and update all the bindings with the data that was already sitting there from the first query.
Thus the objective is to force a digest cycle from Javascript to update your UI bindings. I now force a digest cycle at the end of my callback function.
To force a digest cycle place the Angular method $scope.$apply([exp]) after your data variable assignments are complete. I found helpful details in the Angular docs at: https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$apply and also a great explanation with working examples at: http://jimhoskins.com/2012/12/17/angularjs-and-apply.html and an important detail about using the “controller as” syntax when forcing a digest cycle at: How to call $scope.$apply() using "controller as" syntax and hopefully this fixes the problem of the double HTTP call.

Angularjs ui-router abstract state resolve with http request

I see this topic but this solution not solve my problem.
Angularjs ui-router abstract state with resolve
I want to describe my problem shortly.
I have to state one of them is abstract (parent) two of them have resolve. two resolve make http but child state dont wait parent resolve result. how child resolve wait parent state resolve
Firstly go parent state it's ok , then go child state but child state dont wait parent http result
My plan something like this. In all state (for page refresh) i check user cookie if exist load user again (make http call)
All child state use this user information and get related data for user.
So i need to first of all complete abstract resolve http call.
.state('app', {
url: '/app',
abstract: true,
templateUrl: Route.base('app.html'),
resolve: {
userExist: ['api', function (api) {
var currentUser = cacheService.getUser();
api.loginExistUser(currentUser).then(function (result) {
api.setUser(result.data, true);
});
}]
}
})
.state('app.myFriends', {
url: '/myLingpals',
templateUrl: "myFriends.html",
controller: "myFriendsController",
controllerAs: "vm",
resolve: {
myFriends: ['api' function (api) {
return api.get("myfriends");
}]
}
})
Just add a return in your function :)
return api.loginExistUser(currentUser).then(function (result) {
api.setUser(result.data, true);
});
Thank for your answer walfrat. but your solition is not enough. i try something to solve problem and with your help i find the solition.
when i add parent resolve data to child state resolve dependency child resolve wait response. thanks again
.state('app', {
url: '/app',
abstract: true,
templateUrl: Route.base('app.html'),
resolve: {
**userExist**: ['api', function (api) {
var currentUser = cacheService.getUser();
api.loginExistUser(currentUser).then(function (result) {
api.setUser(result.data, true);
});
}]
}
}).state('app.myFriends', {
url: '/myLingpals',
templateUrl: "myFriends.html",
controller: "myFriendsController",
controllerAs: "vm",
resolve: {
myFriends: ['api','userExist', function (api,**userExist**) {
return api.get("myfriends");
}]
}
})

ui-router automatically redirects out of state

I've got the following route:
.state('app.base.patient', {
url: '/store/:storeID/patient/:patientID',
template: template,
controller: patientCtrl,
controllerAs: 'vm',
resolve: {
patientObj: ['$stateParams', 'patientResource', function($stateParams, patientResource) {
if ($stateParams.patientID !== '') {
return patientResource.get($stateParams.patientID);
}
return null;
}]
}
});
I'm able to navigate to it through the app perfectly fine (using $state.go)
which gets me to (for example):
'/store/105/patient/8a7f7e785279bc1f015279be7cb40001'
But when I try to refresh the page on with the same url I get redirected to:
'/store/105/patient/'
Any ideas what might be causing this? I've been adding more and more logging but I can't see anything that looks obviously wrong
The "parent" view is the store select view:
$stateProvider
.state('app.base.store', {
url: '/store',
template: template,
controller: storeCtrl,
controllerAs: 'vm'
});
Your URLs indicate the states should be nested, but the state definitions do not reflect that. Your patient state should be nested in your store state. The patient state would change to something like this:
.state('app.base.store.patient', {
url: '/patient/:patientID',
template: template,
controller: patientCtrl,
controllerAs: 'vm',
resolve: {
patientObj: ['$stateParams', 'patientResource', function($stateParams, patientResource) {
if ($stateParams.patientID !== '') {
return patientResource.get($stateParams.patientID);
}
return null;
}]
}
});
Meanwhile, your store state would look like this:
$stateProvider
.state('app.base.store', {
url: '/store/:storeID',
template: template,
controller: storeCtrl,
controllerAs: 'vm'
});
Then, as long as your views are nested properly, you can navigate to the patient state from the store view like so:
ui-sref="app.base.store.patient({patientID: vm.patientID})"
Or in controller code like so:
$state.go("app.base.store.patient", { storeID: vm.storeID, patientID: vm.patientID });
The problem turned out to be a rogue
this.$state.go('app.base.patient', {storeID: value.id, patientID: null});
I originally added the explicit patientID: null as I thought it's needed - it's not and will cause the said problem during load.

Angular ui-router to accomplish a conditional view

I am asking a similar question to this question: UI Router conditional ui views?, but my situation is a little more complex and I cannot seem to get the provided answer to work.
Basically, I have a url that can be rendered two very different ways, depending on the type of entity that the url points to.
Here is what I am currently trying
$stateProvider
.state('home', {
url : '/{id}',
resolve: {
entity: function($stateParams, RestService) {
return RestService.getEntity($stateParams.id);
}
},
template: 'Home Template <ui-view></ui-view>',
onEnter: function($state, entity) {
if (entity.Type == 'first') {
$state.transitionTo('home.first');
} else {
$state.transitionTo('home.second');
}
}
})
.state('home.first', {
url: '',
templateUrl: 'first.html',
controller: 'FirstController'
})
.state('home.second', {
url: '',
templateUrl: 'second.html',
controller: 'SecondController'
});
I set up a Resolve to fetch the actual entity from a restful service.
Every thing seems to be working until I actually get to the transitionTo based on the type.
The transition seems to work, except the resolve re-fires and the getEntity fails because the id is null.
I've tried to send the id to the transitionTo calls, but then it still tries to do a second resolve, meaning the entity is fetched from the rest service twice.
What seems to be happening is that in the onEnter handler, the state hasn't actually changed yet, so when the transition happens, it thinks it is transitioning to a whole new state rather than to a child state. This is further evidenced because when I remove the entity. from the state name in the transitionTo, it believes the current state is root, rather than home. This also prevents me from using 'go' instead of transitionTo.
Any ideas?
The templateUrl can be a function as well so you check the type and return a different view and define the controller in the view rather than as part of the state configuration. You cannot inject parameters to templateUrl so you might have to use templateProvider.
$stateProvider.state('home', {
templateProvider: ['$stateParams', 'restService' , function ($stateParams, restService) {
restService.getEntity($stateParams.id).then(function(entity) {
if (entity.Type == 'first') {
return '<div ng-include="first.html"></div>;
} else {
return '<div ng-include="second.html"></div>';
}
});
}]
})
You can also do the following :
$stateProvider
.state('home', {
url : '/{id}',
resolve: {
entity: function($stateParams, RestService) {
return RestService.getEntity($stateParams.id);
}
},
template: 'Home Template <ui-view></ui-view>',
onEnter: function($state, entity) {
if (entity.Type == 'first') {
$timeout(function() {
$state.go('home.first');
}, 0);
} else {
$timeout(function() {
$state.go('home.second');
}, 0);
}
}
})
.state('home.first', {
url: '',
templateUrl: 'first.html',
controller: 'FirstController'
})
.state('home.second', {
url: '',
templateUrl: 'second.html',
controller: 'SecondController'
});
I ended up making the home controller a sibling of first and second, rather than a parent, and then had the controller of home do a $state.go to first or second depending on the results of the resolve.
Use verified code for conditional view in ui-route
$stateProvider.state('dashboard.home', {
url: '/dashboard',
controller: 'MainCtrl',
// templateUrl: $rootScope.active_admin_template,
templateProvider: ['$stateParams', '$templateRequest','$rootScope', function ($stateParams, templateRequest,$rootScope) {
var templateUrl ='';
if ($rootScope.current_user.role == 'MANAGER'){
templateUrl ='views/manager_portal/dashboard.html';
}else{
templateUrl ='views/dashboard/home.html';
}
return templateRequest(templateUrl);
}]
});

Categories

Resources