Angular ui-router to accomplish a conditional view - angularjs

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);
}]
});

Related

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.

controller doesn't work in ui-router

I have this home state in my router config,
.state('home', {
url: '',
controller: 'addMovieCtrl',
views: {
"frontpage": {
templateUrl: '../assets/angular-app/templates/_frontpage.html',
// controller: 'addMovieCtrl'
},
"movieoverview": {
templateUrl: '../assets/angular-app/templates/_movieTemplate.html',
// controller: 'addMovieCtrl'
}
}
})
I want both my views to work with the addMovieCtrl controller, but if I assign the controller in the state (like in the example above) the controller doesn't get called.
When I assign the controller in the view (which I now have commented out) it does get called, but I'm pretty sure I should be able to asign a controller to a state and that adds the controller to all the views. Right?
No, you need to assign a controller to each view. So, your commented out code is the right way to do it.
views: {
"frontpage": {
templateUrl: '../assets/angular-app/templates/_frontpage.html',
controller: 'addMovieCtrl'
},
"movieoverview": {
templateUrl: '../assets/angular-app/templates/_movieTemplate.html',
controller: 'addMovieCtrl'
}
}

How To Autoload ui-view on nested views?

I have code like this
<a ui-sref="nested.something">something</a>
<div ui-view="nested.something"></div>
how to load ui-view without click ui-sref ?
EXTEND - related to this plunker provided by OP in the comments above
The state definition is:
.state('store', {
views: {
'store': {
templateUrl: 'store.html'
}
}
})
.state('store.detail', {
views: {
'store_detail': {
templateUrl: 'store_detail.html'
}
}
})
Then in this updated plunker we can see that this would do the job
//$urlRouterProvider.otherwise('/store');
$urlRouterProvider.otherwise(function($injector, $location){
var state = $injector.get('$state');
state.go('store.detail');
return $location.path();
});
Reason? states do not have defined url. Which is a bit weird. So, I would honestly rather suggested to do it like this (the link to such plunker):
$urlRouterProvider.otherwise('/store/detail');
//$urlRouterProvider.otherwise(function($injector, $location){
// var state = $injector.get('$state');
// state.go('store.detail');
// return $location.path();
//});
$stateProvider
.state('store', {
url: '/store',
views: {
'store': {
templateUrl: 'store.html'
}
}
})
.state('store.detail', {
url: '/detail',
views: {
'store_detail': {
templateUrl: 'store_detail.html'
}
}
})
There is a working plunker
ORIGINAL
We can use the .otherwise(rule) of $urlRouterProvider, documented here
$urlRouterProvider.otherwise('/parent/child');
As the doc says:
otherwise(rule)
Defines a path that is used when an invalid route is requested.
So, this could be used for some default - start up "redirection"
The .otherwise() could be even a function, like shown here:
How not to change url when show 404 error page with ui-router
which takes '$injector', '$location' and can do even much more magic (on invalid or startup path)
$urlRouterProvider.otherwise(function($injector, $location){
var state = $injector.get('$state');
state.go('404');
return $location.path();
});
ALSO, if we want to fill in some more details into some nested viesw, we can do it by defining multi-named views:
.state('parent.child', {
url: "/child",
views: {
'' : {
templateUrl: 'tpl.child.html',
controller: 'ChildCtrl',
},
'nested.something#parent.child' : {
templateUrl: 'tpl.something.html',
},
}
})
So, if the tpl.child.html will have this anchor/target:
<i>place for nested something:</i>
<div ui-view="nested.something"></div>
it will be filled with the tpl.something.html content
Check it in action here

Access scope variable from the other controller within the nested state

How to access other state scope variable from another state ctrl?
If in 'session.index.detail' state, I want to access leftPanel scope variable from rightPanel Ctrl is it possible? What I want to do is I have a form in rightPanel, when I click save, I want to add the new data object back to leftPanel list and refresh it after it is successfully added to the database.
.state('session', {
abstract: true,
templateUrl: '/schedule/_session.html'
})
.state('session.index', {
url: '/sessionmang',
views: {
'leftPanel#session': {
controller: 'SessionCtrl',
controllerAs: 'vm',
templateUrl: '/schedule/_sessionPanel.html'
},
'rightPanel#session': {
templateUrl: '/schedule/_sessionDetailPanel.html'
}
}
})
.state('session.index.detail', {
params: {
sessionObj: null,
sessionTypeObj: null
},
views: {
'rightPanel#session': {
controller: 'SessionDetailCtrl',
controllerAs: 'vm',
templateUrl: '/schedule/_sessionDetailPanel.html',
resolve: {
sessionObj: ['$stateParams', function ($stateParams) {
return $stateParams.sessionObj;
}],
sessionTypeObj: ['$stateParams', function ($stateParams) {
return $stateParams.sessionTypeObj;
}]
}
}
}
});
I think the best way is to create a service that holds the data you want to share.
And share it between the controllers.
Or use broadcasting to talk between the controllers and pass data over that way
the simplest way is, you could use events like this.
when you click save, you will do
$scope.$broadcast('myevent', data);
from the rightCtrl and from the leftCtrl you could do
$scope.$on('myevent', function(event,data) {
//do your updates here
})

One controller to another scopes and sharing in Angular

Angular started off easy to understand but today I'm completely lost and would appreciated any help that is offered here for this problem I've come across.
So I have a two column page, one side the filters and the other the list of things to be filtered. This page is created using the bit of html injected in to the DOM. I have a filter template and a list template and they both have their own controllers (filterCtrl and filterCtrl).
I understood that I needed to seperate the http request for the controllers in to factories, so at the moment an example factory and controller in my app look like this.
fpApp.controller('listController', ['$scope', 'eventsFactory',
function($scope, eventsFactory) {
eventsFactory.eventList().then(
function(data){
$scope.events = data;
}
);
}
]);
and
fpApp.factory('eventsFactory', function ( $http ) {
return {
eventList: function() {
return $http.get('/js/db/events.json')
.then(
function(result) {
return result.data;
});
}
};
});
at the moment every thing is grand, I have a interface built up from partials and everything is outputting what I need. My problem is that I need to apply those filters to the list but have no idea how to get the information out of one controller in to the other controller or even one template to the other.. Once I'm able to get data from one to the other ill be good.
fpApp.config(function($stateProvider, $urlRouterProvider) {
//
// For any unmatched url, redirect to /state1
$urlRouterProvider.otherwise("/grid");
//
// Now set up the states
$stateProvider
.state('grid', {
url: '/grid',
views: {
// the main template will be placed here (relatively named)
'': { templateUrl: '/templates/grid.html' },
'filerList#grid': {
templateUrl: '/templates/partials/filterList.html',
controller: 'filterCtrl'
},
'viewSwitch#grid': {
templateUrl: '/templates/partials/viewSwitch.html'
},
'eventList#grid': {
templateUrl: '/templates/partials/eventList.html',
controller: 'listController'
}
}
})
.state('list', {
url: '/list',
views: {
// the main template will be placed here (relatively named)
'': { templateUrl: '/templates/list.html' },
'filerList#list': {
templateUrl: '/templates/partials/filterList.html',
controller: 'filterCtrl'
},
'viewSwitch#list': {
templateUrl: '/templates/partials/viewSwitch.html'
},
'eventList#list': {
templateUrl: '/templates/partials/eventList.html',
controller: 'listController'
}
}
});
});
So in summary how do I affect the list controller when the user is clicking filters in the filterController?
Thank you for your time in advance.

Resources