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 uiviews 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 uiviews 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 uiviews 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 uiviews are nested inside each other, they can be addressed by chaining their names together.

Every uiview has a name. Unnamed uiviews 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 uiviews, 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)

Updated: