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");
}]
}
})
Related
I'm new to angularjs and bootstrap and I'm recently working on a web app which requires 2 different set of views (public and private).
For the public view, everyone can see it and it has it's own top menu navbar and the corresponding content.
For the private view, only authenticated users are able to see. The private view should have a totally different top menu and its corresponding content. (Maybe a side menu navbar but this is off topic).
I've read the api page for ui-router from here. And I've implemented the navigation like this. (plunker)
app.config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/');
$stateProvider.state('public', {
'abstract': true,
views: {
'mainView#': {
templateUrl: 'public.html'
}
}
})
.state('home', {
parent: 'public',
url: '/',
templateUrl: 'home.html'
})
.state('login', {
parent: 'public',
url: '/login',
templateUrl: 'login.html',
controller: 'LoginController'
})
.state('private', {
'abstract': true,
views: {
'mainView#': {
templateUrl: 'private.html'
}
}
})
.state('dashboard', {
parent: 'private',
url: '/dashboard',
templateUrl: 'dashboard.html'
})
.state('settings', {
parent: 'private',
url: '/settings',
templateUrl: 'settings.html'
})
.state('logout', {
parent: 'private',
url: '/logout',
templateUrl: 'logout.html',
controller: 'LogoutController'
})
});
The example plunker I had above is working, but I'm not sure this is the "best" approach for handling such navigation. I'd very appreciate if someone can help me enhancing my solution.
Thanks in advance.
I like your approach and it looks clean. However, one thing I see it lacking is security. You can quickly add security by adding a resolve dependency.
Resolve
You can use resolve to provide your controller with content or data that is custom to the state. resolve is an optional map of dependencies which should be injected into the controller.
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.
The resolve property is a map object. The map object contains key/value pairs of:
key – {string}: a name of a dependency to be injected into the controller.
factory - {string|function}:
If string, then it is an alias for a service.
Otherwise if function, then it is injected and the return value is treated as the dependency. If the result is a promise, it is resolved before the controller is instantiated and its value is injected into the controller.
.state('dashboard', {
parent: 'private',
url: '/dashboard',
templateUrl: 'dashboard.html',
resolve:{
promiseObj: function($http){
// $http returns a promise for the url data
// returns a promise so the resolve waits for it to complete
// If the promise is rejected, it will throw a $stateChangeError
return $http({method: 'GET', url: '/someUrl'}); // confirm here that the user is logged in
}
})
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
}
});
I'm having problems loading my child states on an abstract true parent state.
This is my parent state
.state('main', {
url: '/main',
templateUrl: 'templates/main.html',
abstract: true
})
This is the child states
.state('main.panels', {
views: {
'ticketsPanel': {
templateUrl: 'templates/ticketsPanel.html'
},
'productsPanel': {
templateUrl: 'templates/productsPanel.html'
},
'categoriesPanel': {
templateUrl: 'templates/categoriesPanel.html'
}
}
})
I have a login page after I login I want to load all 3 child views.
This is the code that process the login.
.controller('loginController', function($scope, Authentication, $log, $state){
$scope.formData = {};
$scope.processForm = function(){
Authentication.login($scope.formData);
var promise = Authentication.getEmployee();
promise.then(function(respond){
localStorage.setItem('employee', JSON.stringify(respond));
$state.go('main.panels');
})
}
})
The $state.go('main.panels') activates the child state of the main state parent, but the problem I'm having is that DOM is showing the element have being loaded but I can only see them partially in my view. It's like they didn't got fully loaded.
My question is how can I wait for all the views in the main.panels to be loaded completely before I transition to that view.
We do have 'resolve' property that can be provided in the definition of each and every state (or view) whatever you want to load. So what angular-ui-router does is that it resolves the 'resolve' property first and only then the HTML template is being rendered on the browser.
You can define the child state in the following way :
.state('main.panels', {
views: {
'ticketsPanel': {
templateUrl: 'templates/ticketsPanel.html',
resolve: function(LoginService){return LoginService};
},
'productsPanel': {
templateUrl: 'templates/productsPanel.html',
resolve: function(LoginService){return LoginService};
},
'categoriesPanel': {
templateUrl: 'templates/categoriesPanel.html',
resolve: function(LoginService){return LoginService};
}
}
})
You can even read the following links for more details :
https://github.com/angular-ui/ui-router/wiki
https://github.com/angular-ui/ui-router/wiki/Nested-States-%26-Nested-Views#inherited-resolved-dependencies
http://www.jvandemo.com/how-to-resolve-angularjs-resources-with-ui-router/
It has been explained in detail. Hope this helps :)
nested states also makes the views nested, meaning the router will look for a named ui-view within the parent template and render it there, and since that's not what you're trying to do you have to implicitly state that it's the parent view (absolute vs relative) like so:
.state('main.panels', {
views: {
'ticketsPanel#': {
templateUrl: 'templates/ticketsPanel.html',
resolve: function(LoginService){return LoginService};
},
'productsPanel#': {
templateUrl: 'templates/productsPanel.html',
resolve: function(LoginService){return LoginService};
},
'categoriesPanel#': {
templateUrl: 'templates/categoriesPanel.html',
resolve: function(LoginService){return LoginService};
}
}
})
'productsPanel#' is like saying 'productsPanel' # nothing which mean the upper parent or root view.
https://github.com/angular-ui/ui-router/wiki/Multiple-Named-Views#view-names---relative-vs-absolute-names
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);
}]
});
I'm using UI-Router in my AngularJS app and I have routes like this:
angular.module('app.messages').config(function($stateProvider, ACCESSLEVELS) {
$stateProvider.state('messages', {
url: '/messages',
templateUrl: 'messages/list.html',
controller: 'MessagesListCtrl as controller',
resolve: {
'conversations': function(Restangular) {
return Restangular.all('conversations').getList();
}
}
});
$stateProvider.state('messages.detail', {
url: '/{conversationId:[0-9]{1,8}}',
templateUrl: 'messages/conversation.html',
controller: 'MessagesDetailCtrl as controller',
resolve: {
'conversation': function(conversations, $stateParams) {
return conversations.get($stateParams.conversationId);
}
}
});
});
So if you go to /messages it'll show a list of your conversations (and it contains a ui-view for the details view). If you go go /messages/1 you view a single conversation.
When you go to /messages you should automatically be redirected to the most recent conversation, because just showing the list and a message like "Select a conversation from the list" is just stupid :)
The problem is that I don't know how to do this. If I have some redirect logic in the MessagesListCtrl then you'll always have a delay where you see the empty landing page and are then redirected. I can't do it a resolve function since those are also used by the child state. Surely others have had this same problem, how did they solve this?
Make messages an abstract state, keep the resolve, and move the templateUrl and controller to a new state, messages.list. Then, create a parent <ui-view /> for your messages state, and give it a templateUrl with whatever you want the user to see while the message is loading. Should work.
Solved with the help of Nate's answer above:
angular.module('app.messages').config(function($stateProvider) {
$stateProvider.state('messages', {
abstract: true,
templateUrl: 'messages/list.html',
controller: 'MessagesListCtrl as controller',
resolve: {
'conversations': function(Restangular) {
return Restangular.all('conversations').getList();
}
}
});
$stateProvider.state('messages.list', {
url: '/messages',
templateUrl: 'messages/conversation.html',
controller: 'MessagesDetailCtrl as controller',
resolve: {
'conversation': function(conversations) {
var lastId = _.last(_.sortBy(conversations, 'lastwrite')).id;
conversations.selectedId = lastId;
return conversations.get(lastId);
}
}
});
$stateProvider.state('messages.detail', {
url: '/messages/{conversationId:[0-9]{1,8}}',
templateUrl: 'messages/conversation.html',
controller: 'MessagesDetailCtrl as controller',
resolve: {
'conversation': function(conversations) {
conversations.selectedId = $stateParams.conversationId;
return conversations.get($stateParams.conversationId);
}
}
});
});
Basically messages.list and messages.detail show the exact same template and use the same controller. The resolve is just a bit different. I'm keeping track of the selectedId on conversations so I can mark the active conversation in the list.