UI-Router for Angular - Hello Galaxy!
In the “Hello Solar System!” app, we created a list/detail interface.
We showed a list of people
and allowed the user to drill down to view a single person
’s details.
We implemented both the people
and person
states as siblings (peers to each other).
When people
was active, person
could not be active, and vice versa.
In this tutorial, we will learn about Nested States:
We will create a parent-child state relationship by nesting the person
state inside the people
state.
We will also nest their views, showing Person
details inside the People
component.
Live demo
As usual, let’s first look at a live demo of the finished “Hello Galaxy” app.
Click the “People” tab, then click on a person.
When you switch from one state to the another, it is called a Transition. On the bottom of the stackblitz, the UI-Router Transition Visualizer shows each transition visually, and provides transition details when hovered and/or clicked.
Nesting States
UI-Router states form a tree, starting from a single root state.
The root state is implicit and has no name.
The top-level application states (such as about
and people
) are children of the implicit root state.
The “Hello Solar System” app had four top-level states, while the “Hello Galaxy” app has three top-level states and one nested state.
The person
state is nested inside the people
state.
We say people
is the parent state and person
is the child state.
The new person
state definition:
export const personState = {
name: "people.person",
url: "/:personId",
component: Person,
resolve: [
{
token: "person",
deps: [Transition, 'people'],
resolveFn: (trans: Transition, people: any[]) =>
people.find(person => person.id === trans.params().personId)
}
]
};
We made some changes to the person
state properties to make it a nested state… let’s go over each change.
State name
We changed the name:
from person
to people.person
.
When naming a state, prepending another state’s name (and a dot) creates a parent/child relationship.
In this case, the people.person
state is a child of the people
state.
Another way to create a parent/child relationship is with the parent:
property of a state definition.
URL
We changed the url:
from /people/:personId
to the url fragment /:personId
.
The child state’s url:
property is a url fragment (a partial url).
The full url for a child state is built by appending the child state’s url fragment to the parent state’s url.
The url for the parent state (people
) is still /people
.
Appending /:personId
to /people
results in /people/:personId
(which is the same as our previous person
url).
The router will map a browser url of /people
to the people
state.
It will map a browser url of /people/123
to the people.person
state, with a peopleId
parameter value of "123"
.
Resolve
We changed the resolve:
to use the data from the parent resolve, instead of re-fetching it from the server.
As before, when you click the “People” tab the router fetches the people
resolve from the server API,
then activates the people
state and renders the view.
With our new nested state setup, the person
resolve is a bit different.
When we click a specific person, the router still invokes the person
resolve before activating the person
state.
Instead of fetching the person from the server, the person
resolve injects the parent state’s people
resolve.
Since the list of people is already loaded in the parent resolve, no additional fetching occurs.
A resolve function may inject the results of other resolves from ancestor states, or from other resolves on the same state.
Views
The view for person
hasn’t changed.
It is still a component named person
, which has an @Input() person
.
The resolve data named person
is still fed into the @Input()
binding.
However, the parent state’s people
component has changed a bit.
<!-- outer container -->
<div class="flex-h">
<!-- inner container for people -->
<div class="people">
<h3>Some people:</h3>
<ul>
<li *ngFor="let person of people">
<a
uiSref=".person"
[uiParams]="{ personId: person.id }"
uiSrefActive="active"
>
{{ person.name }}
</a>
</li>
</ul>
</div>
<!-- viewport for child view -->
<ui-view></ui-view>
</div>
We’ve added some container div
s for layout and embedded a nested <ui-view></ui-view>
viewport.
When a child state of people
is activated, the child state’s view is loaded into the viewport.
We also added some styling to create a side by side layout, so the nested viewport appears on the right.
Note the nested <ui-view>
when “People” is activated.
That <ui-view>
is filled with the person
view when the person
state is active.
Links
We also changed the ui-sref
links in the people
state which drill down to a specific person.
Previously, these links were <a uiSref="person" [uiParams]="{ personId: person.id }">
.
Since the person
state is now named people.person
, we changed the links to <a uiSref="people.person" [uiParams]="{ personId: person.id }">
.
We could also have used relative addressing: <a uiSref=".person" [uiParams]="{ personId: person.id }">
.
Since the uiSref
was created in the people
state’s view and it relatively targets .person
, the final target state is people.person
.