Last hours I have been reading through docs of UI-Router. But I can't find a solution for my problem.
My webapp has two different columns, a list on the left and a detail view on the right. Selecting a element of the list should show detail information on the right.
Which of this two approaches described in the title would you prefer? When to use what?
In fact, the List x Detail scenario is the most suitable for ui-router
. These are in fact two states, the parent/child (i.e. child states to answer the question):
- a
List view
(e.g. the left column). This could be a dynamic view, with paging, sorting and filtering, but still - this will always be a gateway, a parent to:
- a
Detail view
(e.g. the right column). To select a detail (unless navigating via url directly) we simply need a List view
. To select different detail, we can profit from a fact, that the parent/List view
state is not reloading, while iterating among many details...
The best we can do is to observe the example, provided by ui-router
team:
- http://angular-ui.github.io/ui-router/sample/#/contacts
And we can also see its definition, which is part of this states definition:
- https://github.com/angular-ui/ui-router/blob/master/sample/app/contacts/contacts.js
this link, belongs to the best documented pieces of code I do remember... It explains everything and also helps to learn how the ui-router
state definition is working.
Below I tried to show that power by citing the definition of the List and Detail states.
The List state:
/////////////////////
// Contacts > List //
/////////////////////
// Using a '.' within a state name declares a child within a parent.
// So you have a new state 'list' within the parent 'contacts' state.
.state('contacts.list', {
// Using an empty url means that this child state will become active
// when its parent's url is navigated to. Urls of child states are
// automatically appended to the urls of their parent. So this state's
// url is '/contacts' (because '/contacts' + '').
url: '',
// IMPORTANT: Now we have a state that is not a top level state. Its
// template will be inserted into the ui-view within this state's
// parent's template; so the ui-view within contacts.html. This is the
// most important thing to remember about templates.
templateUrl: 'app/contacts/contacts.list.html'
})
the Detail state:
///////////////////////
// Contacts > Detail //
///////////////////////
// You can have unlimited children within a state. Here is a second child
// state within the 'contacts' parent state.
.state('contacts.detail', {
// Urls can have parameters. They can be specified like :param or {param}.
// If {} is used, then you can also specify a regex pattern that the param
// must match. The regex is written after a colon (:). Note: Don't use capture
// groups in your regex patterns, because the whole regex is wrapped again
// behind the scenes. Our pattern below will only match numbers with a length
// between 1 and 4.
// Since this state is also a child of 'contacts' its url is appended as well.
// So its url will end up being '/contacts/{contactId:[0-9]{1,8}}'. When the
// url becomes something like '/contacts/42' then this state becomes active
// and the $stateParams object becomes { contactId: 42 }.
url: '/{contactId:[0-9]{1,4}}',
// If there is more than a single ui-view in the parent template, or you would
// like to target a ui-view from even higher up the state tree, you can use the
// views object to configure multiple views. Each view can get its own template,
// controller, and resolve data.
// View names can be relative or absolute. Relative view names do not use an '@'
// symbol. They always refer to views within this state's parent template.
// Absolute view names use a '@' symbol to distinguish the view and the state.
// So 'foo@bar' means the ui-view named 'foo' within the 'bar' state's template.
views: {
// So this one is targeting the unnamed view within the parent state's template.
'': {
templateUrl: 'app/contacts/contacts.detail.html',
controller: ['$scope', '$stateParams', 'utils',
function ( $scope, $stateParams, utils) {
$scope.contact = utils.findById($scope.contacts, $stateParams.contactId);
}]
},
// This one is targeting the ui-view="hint" within the unnamed root, aka index.html.
// This shows off how you could populate *any* view within *any* ancestor state.
'hint@': {
template: 'This is contacts.detail populating the "hint" ui-view'
},
// This one is targeting the ui-view="menu" within the parent state's template.
'menuTip': {
// templateProvider is the final method for supplying a template.
// There is: template, templateUrl, and templateProvider.
templateProvider: ['$stateParams',
function ( $stateParams) {
// This is just to demonstrate that $stateParams injection works for templateProvider.
// $stateParams are the parameters for the new state we're transitioning to, even
// though the global '$stateParams' has not been updated yet.
return '<hr><small class="muted">Contact ID: ' + $stateParams.contactId + '</small>';
}]
}
}
})
Summary: In these scenarios, do use the parent/child state definition, because the parent will be loaded only once, and keep its data, while we are iterating among its children
Check these links for some more details:
- Angular UI Router Nested State resolve in child states
- why $routeChangeSuccess never gets called?
- How do I prevent reload on named view, when state changes? AngularJS UI-Router