Views
This guide provides a conceptual overview to UI-Router views, nested views, and named views.
Note: the concepts discussed in this guide apply to all UI frameworks supported by UI-Router (e.g., React, Angular, AngularJS). However, the exact syntax for views varies. Likewise, each framework’s component structure and DOM output also varies. In this guide, we will use pseudocode and pseudo DOM output to demonstrate the concepts.
View Declaration
UI-Router applications are modeled as a tree of states. Each application state (generally) has a view declared on it. The view is the component which provides the user interface for that state.
When an application state is activated, the view (component) for that state is rendered.
var state = {
name: 'home',
component: HomeComponent
}
When the home
state is activated, the HomeComponent
is rendered.
UI-View Viewport
A uiview
is a viewport component provided by UI-Router.
When a state is activated, the state’s view (component) is rendered into the appropriate uiview
viewport.
When an app renders a uiview
, it becomes a viewport for views (components) to be loaded into.
Typically, an application will render a single top-level uiview
at the root of the component tree, near the body
tag.
The uiview
will be filled by the component of a top-level state.
<html>
<body>
<uiview>
<!-- When 'home' state is activated, the
'HomeComponent' is rendered here -->
</uiview>
</body>
</html>
Nested views
Application UIs often drill down from more general information to more specific information.
Take for example an imagined email application. The email client displays folders on the left, shows a list of messages, and has a message preview below.
+---------+---------------------------------+
|*INBOX* | MESSAGE1 |
| SPAM | *MESSAGE2* |
| DELETED | MESSAGE3 |
| SENT +---------------------------------+
| | MESSAGE 2 CONTENT |
| | |
| | |
| | |
+---------+---------------------------------+
This UI drills down from more general to more specific: folder list -> message list -> message content
.
Assume we modeled our application states in the following hierarchy:
var states = [
{
name: 'folderlist',
component: FolderListComponent
},
{
name: 'folderlist.messagelist',
component: MessageListComponent
},
{
name: 'folderlist.messagelist.message',
component: MessageContentComponent
}
];
These three states form a nested parent/child state hierarchy.
The folderlist
state is the parent of the nested folderlist.messagelist
state.
Likewise, folderlist.messagelist
is the parent of the nested folderlist.messagelist.message
state.
When the folderlist
parent state is active, its FolderListComponent
renders the list of folders and a nested uiview
viewport.
As long as no child state is active, the nested uiview
is empty.
<FolderListComponent>
<ul class="left">
<li>INBOX</li>
<li>SPAM</li>
<li>DELETED</li>
<li>SENT</li>
</ul>
<div class="right">
<uiview></uiview>
</div>
</FolderListComponent>
When a user selects a folder, the nested folderlist.messagelist
state is activated.
The state’s MessageListComponent
component fills the nested uiview
viewport, which is created in the folderlist
state.
The MessageListComponent
component renders a list of message summaries for the selected folder, i.e., message sender, title, and date/time received.
It also renders another nested uiview
viewport.
<FolderListComponent>
<ul class="left">
<li>INBOX</li>
<li>SPAM</li>
<li>DELETED</li>
<li>SENT</li>
</ul>
<div class="right">
<uiview>
<MessageListComponent class="top">
<ul>
<li>Message 1</li>
<li>Message 2</li>
<li>Message 3</li>
</ul>
<div class="bottom">
<uiview></uiview>
</div>
</MessageListComponent>
</uiview>
</div>
</FolderListComponent>
When the user selects a specific message from the message list, the nested folderlist.messagelist.message
state is activated.
The MessageContentComponent
is then rendered inside the nested uiview
viewport (from the folderlist.messagelist
state).
<FolderListComponent>
<ul class="left">
<li>INBOX</li>
<li>SPAM</li>
<li>DELETED</li>
<li>SENT</li>
</ul>
<div class="right">
<uiview>
<MessageListComponent class="top">
<ul>
<li>Message 1</li>
<li>Message 2</li>
<li>Message 3</li>
</ul>
<div class="bottom">
<uiview>
<MessageContentComponent>
<p>Date: 2017-08-01</p>
<p>Sender: GlobalCorp Bank</p>
<p>Subject: Need a loan?</p>
<div class="body">
<p>Hey you! We have the best loans.</p>
<p>You should really get a loan.</p>
<p>Loans are awesome.</p>
</div>
</MessageContentComponent>
</uiview>
</div>
</MessageListComponent>
</uiview>
</div>
</FolderListComponent>
As each nested state is activated, its view is rendered into the nested uiview
viewport from its parent state.
Multiple named uiviews
So far we’ve only shown states which have a single view (component) defined.
However, UI-Router allows multiple views to be defined on a single state.
These views can be rendered into multiple uiview
viewports throughout the page.
Layouts
Using this approach, you can define a layout with named sections. For instance, you may define a layout with a header, navigation and main content area.
+-------------------------------------------+
| HEADER |
|------+------------------------------------|
| | |
| | |
| NAV | MAIN CONTENT AREA |
| | |
| | |
+------+------------------------------------+
To create a layout, render multiple uiview
components from a parent component.
Give each uiview
a name.
<ParentComponent>
<uiview class="top"></uiview>
<div class="bottom">
<uiview class="left"></uiview>
<uiview class="right"></uiview>
</div>
</ParentComponent>
Defining Named Views
To use multiple views, a state should target named uiview
s with a component.
Add a views:
property to your state definition instead of a component:
.
The views
property should be an object.
The keys of the object are the names of the views being targeted and the values are the component to render.
var mainState = {
name: 'main',
views: {
header: HeaderComponent,
nav: NavComponent,
content: MainComponent,
}
}
Consult the framework specific documentation for exact syntax.
When the main
state is activated, each of the three components are rendered into the matching named uiview
.
Targeting a named uiview
The keys on the views
object determine which uiview
will render the named view’s component.
There are multiple ways to target a named uiview
.
View Name Only
The most common mechanism to target named uiview
s is to use the uiview name only.
When the views
key is a simple name such as nav
, UI-Router targets the uiview
named nav
which was created by the parent state.
var parent = {
name: 'parent',
component: ParentComponent
}
var child = {
name: 'parent.child',
views: {
// targets uiview name='nav' created by ParentComponent
'nav': NavComponent,
// targets uiview name='header' created by ParentComponent
'header': HeaderComponent,
}
}
View Name + State Name
Another mechanism to target named uiview
s is to use the uiview name and also the state that created it.
This allows you to target a uiview
created by some grandparent state.
To use this targeting approach, the views
key should be formatted as <viewname>@<statename>
.
var grandparent = {
name: 'grandparent',
component: GrandParentComponent
}
var parent = {
name: 'grandparent.parent',
component: ParentComponent
}
var child = {
name: 'grandparent.parent.child',
views: {
// targets uiview name='nav' created in 'grandparent' state
'nav@grandparent': NavComponent,
// targets uiview name='header' created in 'grandparent' state
'header@grandparent': HeaderComponent
}
}
Re-targeting a uiview from a nested state
An ancestor state can render a uiview
which is then filled by a child state.
If the child state itself has children, those children can re-target the uiview
with a new view.
When the grandchild state is activated, the view from its parent is replaced by the view from the grandchild.
var parent = {
name: 'parent',
// renders a nested uiview named 'content'
component: ParentComponent
}
var child = {
name: 'parent.child',
views: {
// targets uiview name='content' created by 'parent' state
'content@parent': ChildComponent,
}
}
var grandchild = {
name: 'parent.child.grandchild',
views: {
// targets uiview name='content' created by 'parent' state
// overrides the view from 'child' and replaces it with this component
'content@grandparent': GrandChildComponent,
}
}
When parent.child
is active, the ChildComponent
fills the uiview
named content
.
When parent.child.grandchild
is active, the GrandChildComponent
fills the uiview
named content
.
The ChildComponent
from parent.child
is no longer rendered.
Advanced view targeting
Although two view targeting mechanisms already covered are by far the most common, there are more advanced mechanisms that may be useful in certain cases.
Relative Parent State
This mechanism is very similar to View Name + State Name. However, instead of targeting a state using its exact name, the state can be targeted relatively, up the hierarchy.
When targeting relatively, the caret ('^'
) is used to go up one state in the hierarchy.
To target a view in the parent state, use '^'
.
To target a view in the grandparent state, use '^.^'
.
To target a view in the great-grandparent state, use '^.^.^'
(and so on).
var grandparent = {
name: 'grandparent',
component: GrandParentComponent
}
var parent = {
name: 'grandparent.parent',
component: ParentComponent
}
var child = {
name: 'grandparent.parent.child',
views: {
// targets uiview name='nav' created in 'grandparent' state
'nav@^.^': NavComponent,
}
}
Absolute UIView Addressing
In some cases, it may be easier to target a named uiview
by following its nesting path in the DOM.
When uiview
s are nested inside each other, they can be addressed by chaining their names together.
Every uiview
has a name. Unnamed uiview
s are implicitly named $default
.
When using absolute addressing, add a key to the views
object.
Prefix it with an exclamation point (!
) and chain the uiview
names together using a period (.
).
For example, given the following nested uiview
structure…
<uiview> <!-- unnamed -->
<uiview name="master">
<uiview name="detail">
</uiview>
</uiview>
</uiview>
… to absolutely target the uiview
named detail
, use: !$default.master.detail
.
var state = {
name: 'foo.bar.baz',
views: {
'!$default.master.detail': DetailsComponent
}
}
Generalized Addressing
There are numerous ways to address a named uiview
at a particular state.
However, all these forms of addressing are specialized variations of the generalized addressing scheme.
Generalized addressing is [uiview path]@[state anchor]
.
The [uiview path]
is the names of nested uiview
s, joined together using a dot (.
).
The [state anchor]
is the name of the state which rendered the first uiview
in the uiview path
.
- Generalized Address Example 1:
main.nestedview@home.child
This example Targets the uiview named nestedview
, which is nested inside the uiview named main
, which was created by the home.child
state.
- Generalized Address Example 2:
$default.$default@^.^
This example Targets the unnamed uiview, which is nested inside another unnamed uiview, which was created by the grandparent state.
All the other forms of view addressing are derived from these two forms of generalized addressing.
Type | Example | Comment |
---|---|---|
View Name Only | header |
Shorthand for header@^ , uiview path: header , state anchor: parent state |
View Name + State Name | nav@home.main |
uiview path: nav , state anchor: home.main |
Relative Parent State | nav@^.^ |
uiview path: nav , state anchor: grandparent state |
Absolute UIView | !content.detail |
Shorthand for content.detail@ , uiview path: content.detail , state anchor: the empty string (e.g., the root state) |