Here is my ui-router configuration:
$stateProvider
.state('search.filter.results', {
url: '/results?profileId&keywords',
views: {
'profiles#': {
templateUrl: 'profile-list/profile-list.html',
controller: 'ProfileListCtrl'
},
'profile-summary#search.filter.results': {
templateUrl: 'profile-list/profile-summary/profile-summary.html'
},
'profile-details#search.filter.results': {
templateUrl: 'profile-list/profile-details/profile-details.html',
controller: 'ProfileDetailsCtrl'
}
}
})
ProfileListCtrl has the following method:
$scope.showProfileDetails = function(profileId) {
$state.go('search.filter.results', { profileId: profileId });
};
The problem is, when this method is executed, ProfileListCtrl is instantiated and the view is reloaded.
I would like the reload to happen only if the keywords query parameter changes, but not when the profileId parameter changes.
What would be the best way to achieve this?
I would suggest: split this state into 2 states.
The "parent" keeping the profileId,
the "child" working with keywords.
Until the profileId is not changed, the parent controller won't be re-instantiated.
I created this example with this simplified states definition:
.state('profile', {
url: '/results/:profileId',
views: {
'profiles#': {
//templateUrl: 'profile-list/profile-list.html',
template: '<div>'
...
'<div ui-view=""></div></div>',
controller: 'ProfileCtrl'
},
}
})
.state('profile.results', {
url: '/?keywords',
views: {
'': {
//templateUrl: 'profile-list/profile-list.html',
template: ...
controller: 'ProfileListCtrl'
},
}
})
NOTE: state defintion here is simplified, to just show the split. Not all the details. Also, the child state target parents unnamed view ui-view="", because these should be nested. Finally, parent could even be abstract
This approach also will require some small adjustments of the url. It is not working well if the child url defintion would start with ? like: url: '?keywords'. So I used different pattern:
/results/profileId-value/?kewords=abc
Related
Templates are not included in ui-views, when transitioning between child states of different abstract parents.
Here is the plunker and link to the editor. Navigate form login (auth.login) state to dashboard (app.dashboard) state, header and footer templates are not included in ui-views.
Example of $stateProvider:
.state('auth', {
abstract: true,
views: {
'app-outer': {
templateUrl: 'auth.html'
}
}
})
.state('auth.login', {
url: '/login',
views: {
'app-inner': {
templateUrl: 'login.html'
}
}
})
.state('app', {
abstract: true,
views: {
'header': {
templateUrl: 'header.html'
},
'app-outer': {
templateUrl: 'app.html'
},
'footer': {
templateUrl: 'footer.html'
}
}
})
.state('app.dashboard', {
url: '/dashboard',
views: {
'app-inner': {
templateUrl: 'dashboard.html'
}
}
});
I've tried passing {reload: true} in the ui-sref-opts and used $state.go() method with {reload: true}.
As a temporary solution I've added header#app, footer#app etc. to the app.dashboard state. But it would be nice to not repeat this everywhere.
'header#app': {
templateUrl: 'header.html'
},
'app-inner': {
templateUrl: 'dashboard.html'
},
'footer#app': {
templateUrl: 'header.html'
}
Your template index.html has app-outer view in it. app.html has header, app-inner, and footer in it. However, you have header and footer as views of app state, which corresponds to index.html. It looks like it attempts to populate those templates when the parent state first becomes active, before the child state (with app.html) is loaded, so it doesn't find the ui-views for the header and footer states.
In other words, it looks like a ui-view element corresponding to a view must exist at the time the state that defines that view becomes active.
See this Plnkr, which insert another level of wrapping state.
I have states that look like this:
$stateProvider.state('base', {
url: '/base',
redirectTo: 'base.all',
template: '<div ui-view></div>'
})
.state('base.all', {
parent: 'base',
url: '?someQuery&another',
params: {
someQuery: null,
another: null
},
views: {
'': {
template: '<div>base.all</div>',
controller: function($stateParams){
console.log($stateParams.someQuery);//undefined
console.log($stateParams.another);//undefined
}
}
//some views
}
})
.state('base.all2', {
parent: 'base',
url: '/subPath?someQuery&another',
params: {
someQuery: null,
another: null
},
views: {
'': {
template: '<div>base.all2</div>',
controller: function($stateParams){
console.log($stateParams.someQuery);//foo
console.log($stateParams.another);//bar
}
}
//some views
}
});
I listen to the event $stateChangeStart and do $state.go when redirectTo is present.
My problem now is if I go to the url /base?someQuery=foo&another=bar
It will correctly go to the base.all-state but the $stateParams of the base.all-state will be empty. I don't want this state to have a subPath. The child state will only get the urlParameters set to $stateParams if it has its own subPath like /base/subPath?someQuery=foo&another=bar then it works fine and everything is set to $stateParams.
Is my attempt to have a subState with no subUrl, but only the queryParameters, possible? If so how?
It was as simple to not use the redirectTo and set parent state to abstract.
I think I was too blinded with the use of redirectTo.
And I'm not getting any warnings when routing /base, it seems it will automatically find the state base.all and everything works as expected.
If I have index.html with:
<body>
<ui-view="home"></home>
</body>
Then inside that home view i render frame.html with the following inside:
<div ui-view="home_content"></div>
In my app.js I've been trying to get the most inner the ui-view inside frame.html to render another html template. I'm able to render the content in the home view just fine but nothing renders inside home_content.
My app.js ui-router code
$stateProvider
.state('user', {
abstract: true,
template: '<ui-view/>',
data: {
access: AccessLevels.user
}
})
.state('user.home', {
url: '/home',
views: {
'home#': {
templateUrl: 'app/partials/home/frame.html',
controller: 'homeCtrl'
},
'home_content#home': {
templateUrl: 'app/partials/home/dashboard/index.html',
controller: 'dashboardCtrl'
}
}
});
Seems like I may be coming at this all wrong, not sure if the above is the right way to go about it.
There is a working plunker
I would expect, that you want to target unnamed view of your parent in the child, so the view should be defined like this:
$stateProvider
// here we define parent
.state('user', {
abstract: true,
// parent has UNNAMED view
template: '<ui-view/>', // this will be injected into index.html
... // ui-view=""
})
// child will partilly target parent
// and also itself
.state('user.home', {
url: '/home',
views: {
// here we target parent UNNAMED view
'': {
...
},
// here we target current state
// so we should use state name
'home_content#user.home': {
...
}
}
});
What would also work is instead of '' : { } we can use '#home' : {} (see the doc below).
In case (as mentioned below in the comment) we have index.html with target named home:
<div ui-view="home"></div>
We can use this plunker, which redefines the parent abstract state like this:
$stateProvider
.state('user', {
abstract: true,
data: {
...
},
views : {
'home' : {
template: '<div ui-view=""></div>',
}
}
})
Check the doc
View Names - Relative vs. Absolute Names
Behind the scenes, every view gets assigned an absolute name that follows a scheme of viewname#statename, where viewname is the name used in the view directive and state name is the state's absolute name, e.g. contact.item. You can also choose to write your view names in the absolute syntax.
For example, the previous example could also be written as:
.state('report',{
views: {
'filters#': { },
'tabledata#': { },
'graph#': { }
}
})
Looks like your child .state has wrong code it should be 'home#home_content' instead of 'home_content#home'
.state('user.home', {
url: '/home',
views: {
'': {
templateUrl: 'app/partials/home/frame.html',
controller: 'homeCtrl'
},
'home#home_content': {
templateUrl: 'app/partials/home/dashboard/index.html',
controller: 'dashboardCtrl'
}
}
});
You are making your state configuration complex. Just change your code to look like this:
$stateProvider.state('user', {
abstract : true,
template : '<ui-view/>',
data : {
access : AccessLevels.user
}
})
.state('user.home', {
url : '/home',
views : {
'home' : {
templateUrl : 'app/partials/home/frame.html',
controller : 'homeCtrl'
},
'home_content' : {
templateUrl : 'app/partials/home/dashboard/index.html',
controller : 'dashboardCtrl'
}
}
});
You are actually not using # properly and which is optional. This should work unless you have your correct view path.
How can I get a param from child?
This is what I do:
$state.go('demo.content', {proces: '10'})
And then in controller 'Main' I need the value of param proces.
.state('demo', {
url: '/',
abstract: true,
views: {
'main': {
templateUrl: './App/main.html',
controller: 'Main'
}
})
.state('demo.content', {
url: '/monitoring',
views: {
'chart': {
templateUrl: './App/chart.html',
controller: 'Chart'
},
'grid': {
templateUrl: './App/grid.html',
controller: 'Grid'
},
params: {proces: '4'}
})
The point here is, that any call to 'demo.content' with different param process should re-call/re-init the controller of such state view.
Other words, only controllers which are part of this state, could consume the process as a part of injected params.
So, if there is really any part in main controller, which needs handle (changing) process id, it should be moved to the demo.content state, as another view
If this is the true, the best we can do is to move that view from parent into state:
.state('demo.content', {
url: '/monitoring',
views: {
'chart': {
templateUrl: './App/chart.html',
controller: 'Chart'
},
'grid': {
templateUrl: './App/grid.html',
controller: 'Grid'
},
// this would do the stuff fro main controller
// based on changing params
'mainProcessor': {
templateUrl: './App/mainProcessor.html',
controller: 'mainProcessor'
}
This, and only this way, you can be sure, that different param value will trigger the "mainProcessor" controller and allow him to do the job
In story form:
What I am looking for here is a master-detail setup. The master is in list form and when I click on a link (relative to a particular row/record (or Account in this case)) I want to see the details in the main view (literally, the "main" view: <div class="container" ui-view="main"></div>).
I want to do this and maintain my URL structure (/accounts for the list of Accounts; /accounts/:id for the detailed version) but I want the detail view to use the view that the list was using.
What I currently have
index.html
...
<div class="container" ui-view="main"></div>
...
accounts.js
$stateProvider
.state ('accounts', {
url: '/accounts',
views: {
'main': {
controller: 'AccountsCtrl',
templateUrl: 'accounts/accounts.tpl.html'
}
},
data: { pageTitle: 'Account' }
})
.state ('accounts.detail', {
url: '/:id',
views: {
'main': {
controller: 'AccountDetailCtrl',
templateUrl: 'accounts/detail.tpl.html'
}
},
data: { pageTitle: 'Account Detail' }
});
At this point, the /accounts route works as expected. It displays accounts/accounts.tpl.html correctly in the main view. In that html each line in the repeater links it to its appropriate /accounts/:id URL, which I am handling with the nested state accounts.detail.
What is probably obvious to the majority of you who know more than me about this, my accounts.detail will render to the view main if that named view exists in the template accounts/accounts.tpl.html. That is indeed true.
But that is not what I want. I want the accounts.detail stuff to render in the parent main view; I want the html of accounts/detail.tpl.html to replace the html of accounts/accounts.tpl.html found in index.html: <div class="container" ui-view="main"></div>.
So how could I accomplish this?
MY SOLUTION IN CONTEXT
The trick is, as the answer says, to set up the URL scheme to identify which child state is "default". The way I interpret this code in plain English is that the parent class is abstract with the proper URL and the "default" child class has the "same" URL (indicated by '').
If you need further clarity, just post a comment and I'll share any more guidance.
.config(function config( $stateProvider ) { $stateProvider
.state ('accounts', {
abstract: true,
url: '/accounts',
views: {
'main': {
templateUrl: 'accounts/accounts.tpl.html',
controller: 'AccountsCtrl'
}
},
data: { pageTitle: 'Accounts' }
})
.state ('accounts.list', {
url: '',
views: {
'main': {
templateUrl: 'accounts/list.tpl.html',
controller: 'AccountsListCtrl'
}
},
data: { pageTitle: 'Accounts List' }
})
.state ('accounts.detail', {
url: '/:id',
views: {
'main': {
templateUrl: 'accounts/detail.tpl.html',
controller: 'AccountDetailCtrl'
}
},
data: { pageTitle: 'Account Detail' }
});
Sounds like you simply don't want the views to be hierarchical. To do this, simply change the name of the second state to detail.
Note however, that in doing so you will lose any hierarchical properties of the state tree (the controller code state of accounts for example).
If you want to keep the controllers hierarchical, but perform a replace of the html, I would create another parent above both others that takes care of the controller logic, but only has an extremely simple view <div ui-view=""></div>.
For example:
$stateProvider
.state('app', { url: '', abstract: true, template: 'parent.html', controller: 'ParentCtrl' })
.state('app.accounts', { url: '/accounts', templateUrl: 'accounts.tpl.html', controller: 'AccountsCtrl' })
.state('app.detail', { url: '/accounts/:id', templateUrl: 'detail.tpl.html', controller: 'AccountDetailCtrl' });
You can use '#' to define an absolute path to the ui-view of your choice. For example: "detail#contacts" : { }, where this absolutely targets the 'detail' view in the 'contacts' state. within contacts.html
Source: https://github.com/angular-ui/ui-router/wiki/Multiple-Named-Views