|
| 1 | +--- |
| 2 | +title: "UI-Router for React - Hello Galaxy!" |
| 3 | +excerpt: "Learn about Nested States and Nested Views" |
| 4 | +--- |
| 5 | +{% include toc icon="columns" title="Hello Galaxy!" %} |
| 6 | + |
| 7 | +In the ["Hello Solar System!"](hellosolarsystem) app, we created a list/detail interface. |
| 8 | +We showed a list of `people` and allowed the user to drill down to view a single `person`'s details. |
| 9 | +We implemented both the `people` and `person` states as siblings (peers to each other). |
| 10 | +When `people` was active, `person` could not be active, and vice versa. |
| 11 | + |
| 12 | +In this section, we will create a parent-child state relationship by nesting the `person` state _inside_ the `people` state. |
| 13 | +We will also nest their views, showing `person` details inside the `people` component. |
| 14 | + |
| 15 | +## Live demo |
| 16 | + |
| 17 | +As usual, let's first look at a live demo of the finished "Hello Galaxy" app. |
| 18 | + |
| 19 | +Click the "People" tab, then click on a person. |
| 20 | + |
| 21 | +<iframe style="width: 100%; height: 500px;" |
| 22 | + src="//embed.plnkr.co/NfIhgwegG7YF0kfbBewj/?show=preview" |
| 23 | + frameborder="1" allowfullscren="allowfullscren"></iframe> |
| 24 | + |
| 25 | +When you switch from one state to the another, it is called a Transition. |
| 26 | +On the bottom of the plunker, the [UI-Router Transition Visualizer](https://github.com/ui-router/visualizer) |
| 27 | +shows each transition visually, and provides transition details when hovered and/or clicked. |
| 28 | +{: .notice--info} |
| 29 | + |
| 30 | +# Nesting States |
| 31 | + |
| 32 | +UI-Router states form a tree, starting from a single root state. |
| 33 | +The root state is implicit and has no name. |
| 34 | +The top-level application states (such as `about` and `people`) are children of the implicit root state. |
| 35 | + |
| 36 | +The "Hello Solar System" app had four top-level states, while the |
| 37 | +"Hello Galaxy" app has three top-level states and one nested state. |
| 38 | + |
| 39 | +<figure class="half"> |
| 40 | + <img src="/assets/tutorial/hellosolarsystem.png"> |
| 41 | + <img src="/assets/tutorial/hellogalaxy.png"> |
| 42 | +</figure> |
| 43 | + |
| 44 | +The `person` state is now a child of the `people` state. |
| 45 | +{: .notice--info} |
| 46 | + |
| 47 | +The new `person` state definition: |
| 48 | + |
| 49 | +```js |
| 50 | +const person = { |
| 51 | + name: 'people.person', |
| 52 | + url: '/:personId', |
| 53 | + component: Person, |
| 54 | + resolve: [{ |
| 55 | + token: 'person', |
| 56 | + deps: ['$transition$', 'people'], |
| 57 | + resolveFn: (trans, people) => |
| 58 | + people.find(person => person.id === trans.params().personId) |
| 59 | + }] |
| 60 | +} |
| 61 | +``` |
| 62 | + |
| 63 | + |
| 64 | + |
| 65 | + |
| 66 | +We made some changes to the `person` state properties to make it a nested state... let's go over each change. |
| 67 | + |
| 68 | + |
| 69 | + |
| 70 | +## State name |
| 71 | + |
| 72 | +We changed the `name:` from `person` to `people.person`. |
| 73 | + |
| 74 | +When naming a state, prepending another state's name (and a dot) creates a parent/child relationship. |
| 75 | +In this case, the `people.person` state is a child of the `people` state. |
| 76 | + |
| 77 | +Another way to create a parent/child relationship is with the `parent:` property of a state definition. |
| 78 | +{: .notice--info} |
| 79 | + |
| 80 | +## URL |
| 81 | + |
| 82 | +We changed the `url:` from `/people/{personId}` to the url fragment `/{personId}`. |
| 83 | + |
| 84 | +The child state's `url:` property is a url fragment (a partial url). |
| 85 | +The full url for a child state is built by appending the child state's url fragment to the parent state's url. |
| 86 | + |
| 87 | +The url for the parent state (`people`) is still `/people`. |
| 88 | +Appending `/{personId}` to `/people` results in `/people/{personId}` (which is the same as our previous `person` url). |
| 89 | + |
| 90 | +The router will map a browser url of `/people` to the `people` state. |
| 91 | +It will map a browser url of `/people/123` to the `person` state, with a `peopleId` parameter value of "123". |
| 92 | + |
| 93 | +## Resolve |
| 94 | + |
| 95 | +We changed the `resolve:` to use the data from the parent resolve, instead of fetching it from the server. |
| 96 | + |
| 97 | +As before, when you click the "People" tab the router fetches the `people` resolve from the server API, |
| 98 | +then activates the `people` state and renders the view. |
| 99 | + |
| 100 | +With our new nested state setup, the `person` resolve is a bit different. |
| 101 | +When we click a specific person, the router still invokes the `person` resolve before activating the `person` state. |
| 102 | +However, this resolve is a bit different. |
| 103 | +Instead of fetching the person from the server, the `person` resolve injects the parent state's `people` resolve. |
| 104 | +Since the list of people is already loaded in the parent resolve, no additional fetching occurs. |
| 105 | + |
| 106 | +A resolve function may inject the results of other resolves from ancestor states, or from other resolves on the same state. |
| 107 | +{: .notice--info} |
| 108 | + |
| 109 | + |
| 110 | +## Views |
| 111 | + |
| 112 | +The view for `person` hasn't changed. |
| 113 | +It is still a component named `Person`, which has a `resolves` attribute in its `props`. |
| 114 | +The resolve data named `person` is still passed via the component props. |
| 115 | + |
| 116 | +However, the parent state's `People` component has changed a bit. |
| 117 | + |
| 118 | +{% raw %} |
| 119 | +```js |
| 120 | +render () { |
| 121 | + let {people} = this.props.resolves; |
| 122 | + |
| 123 | + let list = people.map((person, index) => ( |
| 124 | + <li key={index}> |
| 125 | + <UISref to=".person" params={{personId:person.id}}> |
| 126 | + <a>{person.name}</a> |
| 127 | + </UISref> |
| 128 | + </li> |
| 129 | + )); |
| 130 | + |
| 131 | + return ( |
| 132 | + <div className={this.props.className}> |
| 133 | + <div className="flex-h"> |
| 134 | + <div className="people"> |
| 135 | + <h3>Some people:</h3> |
| 136 | + <ul> |
| 137 | + {list} |
| 138 | + </ul> |
| 139 | + </div> |
| 140 | + <UIView className="debug__UIView" /> |
| 141 | + </div> |
| 142 | + </div> |
| 143 | + ); |
| 144 | +} |
| 145 | +``` |
| 146 | +{% endraw %} |
| 147 | + |
| 148 | +We've added some container `div`s for layout and embedded a nested `<UIView>` viewport. |
| 149 | +When a child state of `people` is activated, its view is put into the viewport. |
| 150 | +We also added some styling to create a side by side layout, so the nested viewport appears on the right. |
| 151 | + |
| 152 | + |
| 153 | +<video controls="controls" autoplay loop> |
| 154 | + <source src="/assets/tutorial/nested view.mov.mp4" type="video/mp4"> |
| 155 | + <source src="/assets/tutorial/nested view.mov.webm" type="video/webm"> |
| 156 | +</video> |
| 157 | + |
| 158 | +Note the nested `<UIView>` when "People" is activated. |
| 159 | +That `<UIView>` is filled with the `person` view when the `person` state is active. |
| 160 | +{: .notice--info} |
| 161 | + |
| 162 | +## Links |
| 163 | + |
| 164 | +We also changed the `UISref` links in the `people` state which drill down to a specific person. |
| 165 | +{% raw %} |
| 166 | +Previously, these links were `<UISref to="person" params={{personId:person.id}}><a>{person.name}</a></UISref>`. |
| 167 | +Since the `person` state is now named `people.person`, we changed the links to `<UISref to="people.person" params={{personId:person.id}}><a>{person.name}</a></UISref>`. |
| 168 | + |
| 169 | +We could also have used relative addressing: `<UISref to=".person" params={{personId:person.id}}><a>{person.name}</a></UISref>`. |
| 170 | +Since the `UISref` was created in the `people` state's view and it relatively targets `.person`, the final target state is `people.person`. |
| 171 | +{: .notice--info} |
| 172 | +{% endraw %} |
| 173 | + |
| 174 | +# Transitions |
| 175 | + |
| 176 | +TODO |
0 commit comments