forked from angular/angular.io
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrouter.jade
2328 lines (1769 loc) · 105 KB
/
router.jade
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
include ../_util-fns
:marked
The Angular ***Router*** enables navigation from one [view](./glossary.html#view) to the next
as users perform application tasks.
We cover the router's primary features in this chapter, illustrating them through the evolution
of a small application that we can <live-example>run live</live-example>.
.l-sub-section
img(src='/resources/images/devguide/plunker-separate-window-button.png' alt="pop out the window" align="right" style="margin-right:-20px")
:marked
To see the URL changes in the browser address bar,
pop out the preview window by clicking the blue 'X' button in the upper right corner.
.l-main-section
:marked
## Overview
The browser is a familiar model of application navigation.
We enter a URL in the address bar and the browser navigates to a corresponding page.
We click links on the page and the browser navigates to a new page.
We click the browser's back and forward buttons and the browser navigates
backward and forward through the history of pages we've seen.
The Angular ***Router*** ("the router") borrows from this model.
It can interpret a browser URL as an instruction
to navigate to a client-generated view and pass optional parameters along to the supporting view component
to help it decide what specific content to present.
We can bind the router to links on a page and it will navigate to
the appropriate application view when the user clicks a link.
We can navigate imperatively when the user clicks a button, selects from a drop box,
or in response to some other stimulus from any source. And the router logs activity
in the browser's history journal so the back and forward buttons work as well.
We'll learn many router details in this chapter which covers
* Setting the [base href](#base-href)
* Importing from the [router library](#import)
* [configuring the router](#route-config)
* the [link parameters array](#link-parameters-array) that propels router navigation
* navigating when the user clicks a data-bound [RouterLink](#router-link)
* navigating under [program control](#navigate)
* retrieving information from the [route](#activated-route)
* [animating](#route-animation) transitions for route components
* navigating [relative](#relative-navigation) to our current URL
* toggling css classes for the [active router link](#router-link-active)
* embedding critical information in the URL with [route parameters](#route-parameters)
* providing non-critical information in [optional route parameters](#optional-route-parameters)
* add [child routes](#child-routing-component) under a feature section
* [grouping child routes](#component-less-route) without a component
* [redirecting](#redirect) from one route to another
* confirming or canceling navigation with [guards](#guards)
* [CanActivate](#can-activate-guard) to prevent navigation to a route
* [CanActivateChild](#can-activate-child-guard) to prevent navigation to a child route
* [CanDeactivate](#can-deactivate-guard) to prevent navigation away from the current route
* [Resolve](#resolve-guard) to pre-fetch data before activating a route
* [CanLoad](#can-load-guard) to prevent asynchronous routing
* providing optional information across routes with [query parameters](#query-parameters)
* jumping to anchor elements using a [fragment](#fragment)
* loading feature areas [asynchronously](#asynchronous-routing)
* choosing the "HTML5" or "hash" [URL style](#browser-url-styles)
We proceed in phases marked by milestones building from a simple two-pager with placeholder views
up to a modular, multi-view design with child routes.
But first, an overview of router basics.
.l-main-section
:marked
## The Basics
Let's begin with a few core concepts of the Router.
Then we can explore the details through a sequence of examples.
:marked
### *<base href>*
Most routing applications should add a `<base>` element to the **`index.html`** as the first child in the `<head>` tag
to tell the router how to compose navigation URLs.
If the `app` folder is the application root, as it is for our sample application,
set the `href` value *exactly* as shown here.
+makeExcerpt('index.1.html', 'base-href')
:marked
### Router imports
The Angular Router is an optional service that presents a particular component view for a given URL.
It is not part of the Angular 2 core. It is in its own library package, `@angular/router`.
We import what we need from it as we would from any other Angular package.
+makeExcerpt('app/app.routing.ts (import)', 'import-router')
.l-sub-section
:marked
We cover other options in the [details below](#browser-url-styles).
:marked
### Configuration
The application will have one *`router`*. When the browser's URL changes, the router looks for a corresponding **`Route`**
from which it can determine the component to display.
A router has no routes until we configure it.
We bootstrap our application with an array of routes that we'll provide to our **`RouterModule.forRoot`** function.
In the following example, we configure our application with four route definitions.
+makeExcerpt('app/app.routing.1.ts (excerpt)', 'route-config')
.l-sub-section
:marked
The `Routes` is an array of *routes* that describe how to navigate.
Each *Route* maps a URL `path` to a component.
There are no **leading slashes** in our **path**. The router parses and builds the URL for us,
allowing us to use relative and absolute paths when navigating between application views.
The `:id` in the first route is a token for a route parameter. In a URL such as `/hero/42`, "42"
is the value of the `id` parameter. The corresponding `HeroDetailComponent`
will use that value to find and present the hero whose `id` is 42.
We'll learn more about route parameters later in this chapter.
The `data` property in the third route is a place to store arbitrary data associated with each
specific route. This data is accessible within each activated route and can be used to store
items such as page titles, breadcrumb text and other read-only data. We'll use the [resolve guard](#resolve-guard)
to retrieve additional data later in the chapter.
The `empty path` in the fourth route matches as the default path for each level of routing. It
also allows for adding routes without extending the URL path.
The `**` in the last route denotes a **wildcard** path for our route. The router will match this route
if the URL requested doesn't match any paths for routes defined in our configuration. This is useful for
displaying a 404 page or redirecting to another route.
**The order of the routes in the configuration matters** and this is by design. The router uses a **first-match wins**
strategy when matching routes, so more specific routes should be placed above less specific routes. In our
configuration above, the routes with a static path are listed first, followed by an empty path route,
that matches as the default route. The wildcard route is listed last as it's the most generic route and should be
matched **only** if no other routes are matched first.
:marked
We export the `routing` constant so we can import it into our `app.module.ts` file where we'll add
a configured *Router* module to our `AppModule` imports.
:marked
Next we open `app.module.ts` where we must register our routing, routing providers, and declare our two route components.
+makeExcerpt('app/app.module.1.ts (basic setup)', 'router-basics')
:marked
### Router Outlet
Given this configuration, when the browser URL for this application becomes `/heroes`,
the router matches that URL to the `Route` path `/heroes` and displays the `HeroListComponent`
in a **`RouterOutlet`** that we've placed in the host view's HTML.
code-example(language="html").
<!-- Routed views go here -->
<router-outlet></router-outlet>
:marked
### Router Links
Now we have routes configured and a place to render them, but
how do we navigate? The URL could arrive directly from the browser address bar.
But most of the time we navigate as a result of some user action such as the click of
an anchor tag.
We add a **`RouterLink`** directive to the anchor tag. Since
we know our link doesn't contain any dynamic information, we can use a one-time binding to our route *path*.
If our `RouterLink` needed to be more dynamic we could bind to a template expression that
returns an array of route link parameters (the **link parameters array**). The router ultimately resolves that array
into a URL and a component view.
We also add a **`RouterLinkActive`** directive to each anchor tag to add or remove CSS classes to the
element when the associated *RouterLink* becomes active. The directive can be added directly on the element
or on its parent element.
We see such bindings in the following `AppComponent` template:
+makeExcerpt('app/app.component.1.ts', 'template', '')
.l-sub-section
:marked
We're adding two anchor tags with `RouterLink` and `RouterLinkActive` directives.
We bind each `RouterLink` to a string containing the path of a route.
'/crisis-center' and '/heroes' are the paths of the `Routes` we configured above.
We'll learn to write link expressions — and why they are arrays —
[later](#link-parameters-array) in the chapter.
We define `active` as the CSS class we want toggled to each `RouterLink` when they become
the current route using the `RouterLinkActive ` directive. We could add multiple classes to
the `RouterLink` if we so desired.
:marked
### Router State
After the end of each successful navigation lifecycle, the router builds a tree of `ActivatedRoute` objects
that make up the current state of the router. We can access the current `RouterState` from anywhere in our
application using the `Router` service and the `routerState` property.
The router state provides us with methods to traverse up and down the route tree from any activated route
to get information we may need from parent, child and sibling routes.
:marked
### Let's summarize
The application is provided with a configured router.
The component has a `RouterOutlet` where it can display views produced by the router.
It has `RouterLink`s that users can click to navigate via the router.
Here are the key *Router* terms and their meanings:
table
tr
th Router Part
th Meaning
tr
td <code>Router</code>
td.
Displays the application component for the active URL.
Manages navigation from one component to the next.
tr
td <code>RouterModule</code>
td.
A separate Angular module that provides the necessary service providers
and directives for navigating through application views.
tr
td <code>Routes</code>
td.
Defines an array of Routes, each mapping a URL path to a component.
tr
td <code>Route</code>
td.
Defines how the router should navigate to a component based on a URL pattern.
Most routes consist of a path and a component type.
tr
td <code>RouterOutlet</code>
td.
The directive (<code><router-outlet></code>) that marks where the router should display a view.
tr
td <code>RouterLink</code>
td.
The directive for binding a clickable HTML element to
a route. Clicking an anchor tag with a <code>routerLink</code> directive
that is bound to a <i>string</i> or a <i>Link Parameters Array</i> triggers a navigation.
tr
td <code>RouterLinkActive</code>
td.
The directive for adding/removing classes from an HTML element when an associated
routerLink contained on or inside the element becomes active/inactive.
tr
td <code>ActivatedRoute</code>
td.
A service that is provided to each route component that contains route specific
information such as route parameters, static data, resolve data, global query params and the global fragment.
tr
td <code>RouterState</code>
td.
The current state of the router including a tree of the currently activated
routes in our application along convenience methods for traversing the route tree.
tr
td <code><i>Link Parameters Array</i></code>
td.
An array that the router interprets into a routing instruction.
We can bind a <code>RouterLink</code> to that array or pass the array as an argument to
the <code>Router.navigate</code> method.
tr
td <code><i>Routing Component</i></code>
td.
An Angular component with a <code>RouterOutlet</code> that displays views based on router navigations.
:marked
We've barely touched the surface of the router and its capabilities.
The following detail sections describe a sample routing application
as it evolves over a sequence of milestones.
We strongly recommend taking the time to read and understand this story.
.l-main-section
:marked
## The Sample Application
We have an application in mind as we move from milestone to milestone.
.l-sub-section
:marked
While we make incremental progress toward the ultimate sample application, this chapter is not a tutorial.
We discuss code and design decisions pertinent to routing and application design.
We gloss over everything in between.
The full source is available in the <live-example></live-example>.
:marked
Our client is the Hero Employment Agency.
Heroes need work and The Agency finds Crises for them to solve.
The application has three main feature areas:
1. A *Crisis Center* where we maintain the list of crises for assignment to heroes.
1. A *Heroes* area where we maintain the list of heroes employed by The Agency.
1. An *Admin* area where we manage the list of crises and heroes displayed.
Run the <live-example></live-example>.
It opens in the *Crisis Center*. We'll come back to that.
Click the *Heroes* link. We're presented with a list of Heroes.
figure.image-display
img(src='/resources/images/devguide/router/hero-list.png' alt="Hero List" width="250")
:marked
We select one and the application takes us to a hero editing screen.
figure.image-display
img(src='/resources/images/devguide/router/hero-detail.png' alt="Crisis Center Detail" width="250")
:marked
Our changes take effect immediately. We click the "Back" button and the
app returns us to the Heroes list.
We could have clicked the browser's back button instead.
That would have returned us to the Heroes List as well.
Angular app navigation updates the browser history as normal web navigation does.
Now click the *Crisis Center* link. We go to the *Crisis Center* and its list of ongoing crises.
figure.image-display
img(src='/resources/images/devguide/router/crisis-center-list.png' alt="Crisis Center List" )
:marked
We select one and the application takes us to a crisis editing screen.
figure.image-display
img(src='/resources/images/devguide/router/crisis-center-detail.png' alt="Crisis Center Detail")
:marked
This is a bit different from the *Hero Detail*. *Hero Detail* saves the changes as we type.
In *Crisis Detail* our changes are temporary until we either save or discard them by pressing the "Save" or "Cancel" buttons.
Both buttons navigate back to the *Crisis Center* and its list of crises.
Suppose we click a crisis, make a change, but ***do not click either button***.
Maybe we click the browser back button instead. Maybe we click the "Heroes" link.
Do either. Up pops a dialog box.
figure.image-display
img(src='/resources/images/devguide/router/confirm-dialog.png' alt="Confirm Dialog" width="300")
:marked
We can say "OK" and lose our changes or click "Cancel" and continue editing.
The router supports a `CanDeactivate` guard that gives us a chance to clean-up
or ask the user's permission before navigating away from the current view.
Here we see an entire user session that touches all of these features.
<a id="full-app-demo"></a>
figure.image-display
img(src='/resources/images/devguide/router/router-anim.gif' alt="App in action" )
:marked
Here's a diagram of all application routing options:
figure.image-display
img(src='/resources/images/devguide/router/complete-nav.png' alt="Navigation diagram" )
:marked
This app illustrates the router features we'll cover in this chapter
* organizing the application features into modules
* navigating to a component (*Heroes* link to "Heroes List")
* including a route parameter (passing the Hero `id` while routing to the "Hero Detail")
* child routes (the *Crisis Center* has its own routes)
* the `CanActivate` guard (checking route access)
* the `CanActivateChild` guard (checking child route access)
* the `CanDeactivate` guard (ask permission to discard unsaved changes)
* the `Resolve` guard (pre-fetching route data)
* lazy loading feature modules
* the `CanLoad` guard (check before loading feature module assets)
<a id="getting-started"></a>
.l-main-section
:marked
## Milestone #1: Getting Started with the Router
Let's begin with a simple version of the app that navigates between two empty views.
figure.image-display
img(src='/resources/images/devguide/router/router-1-anim.gif' alt="App in action" )
a#base-href
:marked
### Set the *<base href>*
The Router uses the browser's
[history.pushState](https://developer.mozilla.org/en-US/docs/Web/API/History_API#Adding_and_modifying_history_entries)
for navigation. Thanks to `pushState`, we can make our in-app URL paths look the way we want them to
look, e.g. `localhost:3000/crisis-center`. Our in-app URLs can be indistinguishable from server URLs.
Modern HTML 5 browsers were the first to support `pushState` which is why many people refer to these URLs as
"HTML 5 style" URLs.
We must **add a [<base href> element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base) tag**
to the `index.html` to make `pushState` routing work.
The browser also needs the base `href` value to prefix *relative* URLs when downloading and linking to
css files, scripts, and images.
Add the base element just after the `<head>` tag.
If the `app` folder is the application root, as it is for our application,
set the `href` value in **`index.html`** *exactly* as shown here.
+makeExcerpt('index.1.html', 'base-href')
.l-sub-section
:marked
HTML 5 style navigation is the Router default.
Learn why "HTML 5" style is preferred, how to adjust its behavior, and how to switch to the
older hash (#) style if necessary in the [Browser URL Styles](#browser-url-styles) appendix below.
:marked
.l-sub-section
:marked
#### Live example note
We have to get tricky when we run the live example because the host service sets
the application base address dynamically. That's why we replace the `<base href...>` with a
script that writes a `<base>` tag on the fly to match.
code-example(format="")
<script>document.write('<base href="' + document.location + '" />');</script>
:marked
We should only need this trick for the live example, not production code.
:marked
### Configure the routes for the Router
We begin by importing some symbols from the router library.
The Router is in its own `@angular/router` package.
It's not part of the Angular 2 core. The router is an optional service because not all applications
need routing and, depending on your requirements, you may need a different routing library.
We teach our router how to navigate by configuring it with routes.
We recommend creating a separate `app.routing.ts` file dedicated to this purpose.
:marked
Here is our first configuration. We pass the array of routes to the `RouterModule.forRoot` method
which returns a module containing the configured `Router` service provider ... and some other,
unseen providers that the routing library requires. We export this as the `routing` token.
+makeExcerpt('app/app.routing.2.ts')
.l-sub-section
:marked
We also export an empty `appRoutingProviders` array
so we can simplify registration of router dependencies later in `app.module.ts`.
We don't have any providers to register right now. But we will.
a#route-config
h4#define-routes Define routes
:marked
A router must be configured with a list of route definitions.
Our first configuration defines an array of two routes with simple paths leading to the
`CrisisListComponent` and `HeroListComponent` components.
Each definition translates to a [Route](../api/router/index/Route-interface.html) object which has a
`path`, the URL path segment for this route, and a
`component`, the component associated with this route.
The router draws upon its registry of such route definitions when the browser URL changes
or when our code tells the router to navigate along a route path.
In plain English, we might say of the first route:
* *When the browser's location URL changes to match the path segment `/crisis-center`, create or retrieve an instance of
the `CrisisListComponent` and display its view.*
* *When the application requests navigation to the path `/crisis-center`, create or retrieve an instance of
the `CrisisListComponent`, display its view, and update the browser's address location and history with the URL
for that path.*
.l-sub-section
:marked
Learn about *providers* in the [Dependency Injection](dependency-injection.html#!#injector-providers) chapter.
h4#register-providers Register routing in the AppModule
:marked
Our app launches from the `app.module.ts` file in the `/app` folder.
We import the `routing` token we exported from the `app.routing.ts` file and add it to the `imports` array.
We import our `CrisisListComponent` and `HeroListComponent` components and add them to our *declarations*
so they will be registered within our `AppModule`.
We also import the `appRoutingProviders` array and add it to the `providers` array.
+makeExcerpt('app/app.module.1.ts')
:marked
Providing the router module in our `AppModule` makes the Router available everywhere in our application.
h3#shell The <i>AppComponent</i> shell
:marked
The root `AppComponent` is the application shell. It has a title at the top, a navigation bar with two links,
and a *Router Outlet* at the bottom where the router swaps views on and off the page. Here's what we mean:
figure.image-display
img(src='/resources/images/devguide/router/shell-and-outlet.png' alt="Shell" width="300" )
a#shell-template
:marked
The corresponding component template looks like this:
+makeExcerpt('app/app.component.1.ts', 'template', '')
a#router-outlet
:marked
### *RouterOutlet*
`RouterOutlet` is a component from the router library.
The router displays views within the bounds of the `<router-outlet>` tags.
.l-sub-section
:marked
A template may hold exactly one ***unnamed*** `<router-outlet>`.
The router supports multiple *named* outlets, a feature we'll cover in future.
a#router-link
:marked
### *RouterLink* binding
Above the outlet, within the anchor tags, we see [Property Bindings](template-syntax.html#property-binding) to
the `RouterLink` directive that look like `routerLink="..."`. We use the `RouterLink` from the router library.
The links in this example each have a string path, the path of a route that
we configured earlier. We don't have route parameters yet.
We can also add more contextual information to our `RouterLink` by providing query string parameters
or a URL fragment for jumping to different areas on our page. Query string parameters
are provided through the `[queryParams]` binding which takes an object (e.g. `{ name: 'value' }`), while the URL fragment
takes a single value bound to the `[fragment]` input binding.
.l-sub-section
:marked
Learn about the how we can also use the **link parameters array** in the [appendix below](#link-parameters-array).
a#router-link-active
h3#router-link <i>RouterLinkActive</i> binding
:marked
On each anchor tag, we also see [Property Bindings](template-syntax.html#property-binding) to
the `RouterLinkActive` directive that look like `routerLinkActive="..."`.
The template expression to the right of the equals (=) contains our space-delimited string of CSS classes.
We can also bind to the `RouterLinkActive` directive using an array of classes
such as `[routerLinkActive]="['...']"`.
The `RouterLinkActive` directive toggles css classes for active `RouterLink`s based on the current `RouterState`.
This cascades down through each level in our route tree, so parent and child router links can be active at the same time.
To override this behavior, we can bind to the `[routerLinkActiveOptions]` input binding with the `{ exact: true }` expression.
By using `{ exact: true }`, a given `RouterLink` will only be active if its URL is an exact match to the current URL.
h3#router-directives <i>Router Directives</i>
:marked
`RouterLink`, `RouterLinkActive` and `RouterOutlet` are directives provided by the Angular `RouterModule` package.
They are readily available for us to use in our template.
:marked
The current state of `app.component.ts` looks like this:
+makeExcerpt('app/app.component.1.ts')
:marked
### "Getting Started" wrap-up
We've got a very basic, navigating app, one that can switch between two views
when the user clicks a link.
We've learned how to
* load the router library
* add a nav bar to the shell template with anchor tags, `routerLink` and `routerLinkActive` directives
* add a `router-outlet` to the shell template where views will be displayed
* configure the router module with `RouterModule.forRoot`
* set the router to compose "HTML 5" browser URLs.
The rest of the starter app is mundane, with little interest from a router perspective.
Here are the details for readers inclined to build the sample through to this milestone.
Our starter app's structure looks like this:
.filetree
.file router-sample
.children
.file app
.children
.file app.component.ts
.file app.module.ts
.file app.routing.ts
.file crisis-list.component.ts
.file hero-list.component.ts
.file main.ts
.file node_modules ...
.file typings ...
.file index.html
.file package.json
.file styles.css
.file tsconfig.json
.file typings.json
:marked
Here are the files discussed in this milestone
+makeTabs(
`router/ts/app/app.component.1.ts,
router/ts/app/app.module.1.ts,
router/ts/app/app.routing.2.ts,
router/ts/app/main.ts,
router/ts/app/hero-list.component.ts,
router/ts/app/crisis-list.component.ts,
router/ts/index.html`,
',,,,',
`app.component.ts,
app.module.ts,
app.routing.ts,
main.ts,
hero-list.component.ts,
crisis-list.component.ts,
index.html`)
.l-main-section#heroes-feature
:marked
## Milestone #2: The Heroes Feature
We've seen how to navigate using the `RouterLink` directive.
Now we'll learn some new tricks such as how to
* organize our app and routes into *feature areas* using modules
* navigate imperatively from one component to another
* pass required and optional information in route parameters
To demonstrate, we'll build out the *Heroes* feature.
### The Heroes "feature area"
A typical application has multiple *feature areas*, each an island of functionality
with its own workflow(s), dedicated to a particular business purpose.
We could continue to add files to the `app/` folder.
That's unrealistic and ultimately not maintainable.
We think it's better to put each feature area in its own folder.
Our first step is to **create a separate `app/heroes/` folder**
and add *Hero Management* feature files there.
We won't be creative about it. Our example is pretty much a
copy of the code and capabilities in the "[Tutorial: Tour of Heroes](../tutorial/index.html)".
Here's how the user will experience this version of the app
figure.image-display
img(src='/resources/images/devguide/router/router-2-anim.gif' alt="App in action" )
:marked
### Add Heroes functionality
We want to break our app out into different *feature modules* that we then import
into our main module so it can make use of them. First, we'll create a `heroes.module.ts`
in our heroes folder.
We delete the placeholder `hero-list.component.ts` that's in
the `app/` folder.
We create a new `hero-list.component.ts` in the `app/heroes/`
folder and copy over the contents of the final `heroes.component.ts` from the tutorial.
We copy the `hero-detail.component.ts` and the `hero.service.ts` files
into the `heroes/` folder.
We provide the `HeroService` in the `providers` array of our `Heroes` module
so its available to all components within our module.
Our `Heroes` module is ready for routing.
+makeExcerpt('app/heroes/heroes.module.1.ts')
:marked
When we're done organizing, we have four *Hero Management* files:
.filetree
.file app/heroes
.children
.file hero-detail.component.ts
.file hero-list.component.ts
.file hero.service.ts
.file heroes.module.ts
:marked
Now it's time for some surgery to bring these files and the rest of the app
into alignment with our application router.
### *Hero* feature routing requirements
The new Heroes feature has two interacting components, the list and the detail.
The list view is self-sufficient; we navigate to it, it gets a list of heroes and displays them.
It doesn't need any outside information.
The detail view is different. It displays a particular hero. It can't know which hero on its own.
That information must come from outside.
In our example, when the user selects a hero from the list, we navigate to the detail view to show that hero.
We'll tell the detail view which hero to display by including the selected hero's id in the route URL.
### *Hero* feature route configuration
We recommend giving each feature area its own route configuration file.
Create a new `heroes.routing.ts` in the `heroes` folder like this:
+makeExcerpt('app/heroes/heroes.routing.ts')
:marked
We use the same techniques we learned for `app.routing.ts`.
We import the two components from their new locations in the `app/heroes/` folder, define the two hero routes.
and add export our `heroesRouting` that returns configured `RouterModule` for our feature module.
:marked
Now that we have routes for our `Heroes` module, we'll need to register them with the *Router*.
We'll import the *RouterModule* like we did in the `app.routing.ts`, but there is a slight difference here.
In our `app.routing.ts`, we used the static **forRoot** method to register our routes and application level
service providers. In a feature module we use static **forChild** method.
.l-sub-section
:marked
The **RouterModule.forRoot** should only be provided for the `AppModule`. Since we are in a feature
module, we'll use **RouterModule.forChild** method to only register additional routes.
:marked
We import our `heroesRouting` token from `heroes.routing.ts` into our `Heroes` module and register the routing.
+makeExcerpt('app/heroes/heroes.module.ts (heroes routing)', 'heroes-routes')
:marked
### Route definition with a parameter
The route to `HeroDetailComponent` has a twist.
+makeExcerpt('app/heroes/heroes.routing.ts (excerpt)', 'hero-detail-route')
:marked
Notice the `:id` token in the path. That creates a slot in the path for a **Route Parameter**.
In this case, we're expecting the router to insert the `id` of a hero into that slot.
If we tell the router to navigate to the detail component and display "Magneta", we expect hero `id` (15) to appear in the
browser URL like this:
code-example(format="." language="bash").
localhost:3000/hero/15
:marked
If a user enters that URL into the browser address bar, the router should recognize the
pattern and go to the same "Magneta" detail view.
.l-sub-section
:marked
#### Route parameter: Required or optional?
Embedding the route parameter token, `:id`, in the route definition path is a good choice for our scenario
because the `id` is *required* by the `HeroDetailComponent` and because
the value `15` in the path clearly distinguishes the route to "Magneta" from
a route for some other hero.
An [optional-route-parameter](#optional-route-parameters) might be a better choice if we were passing an *optional* value to `HeroDetailComponent`.
a#navigate
:marked
### Navigate to hero detail imperatively
*We won't navigate to the detail component by clicking a link*
so we won't be adding a new `RouterLink` anchor tag to the shell.
Instead, when the user *clicks* a hero in the list, we'll *command* the router
to navigate to the hero detail view for the selected hero.
We'll adjust the `HeroListComponent` to implement these tasks, beginning with its constructor
which acquires the router service and the `HeroService` by dependency injection:
+makeExcerpt('app/heroes/hero-list.component.1.ts (constructor)', 'ctor')
:marked
We make a few changes to the template:
+makeExcerpt('app/heroes/hero-list.component.1.ts', 'template', '')
:marked
The template defines an `*ngFor` repeater such as [we've seen before](displaying-data.html#ngFor).
There's a `(click)` [EventBinding](template-syntax.html#event-binding) to the component's `onSelect` method
which we implement as follows:
+makeExcerpt('app/heroes/hero-list.component.1.ts', 'select')
:marked
It calls the router's **`navigate`** method with a **Link Parameters Array**. We can use this same syntax
with a `RouterLink` if we want to use it in HTML rather than code.
h3#route-parameters Setting the route parameters in the list view
:marked
We're navigating to the `HeroDetailComponent` where we expect to see the details of the selected hero.
We'll need *two* pieces of information: the destination and the hero's `id`.
Accordingly, the *link parameters array* has *two* items: the **path** of the destination route and a **route parameter** that specifies the
`id` of the selected hero.
+makeExcerpt('app/heroes/hero-list.component.1.ts', 'link-parameters-array')
:marked
The router composes the appropriate two-part destination URL from this array:
code-example(language="bash").
localhost:3000/hero/15
a#get-route-parameter
:marked
### Getting the route parameter in the details view
How does the target `HeroDetailComponent` learn about that `id`?
Certainly not by analyzing the URL! That's the router's job.
The router extracts the route parameter (`id:15`) from the URL and supplies it to
the `HeroDetailComponent` via the **ActivatedRoute** service.
<a id="activated-route"></a>
h3#activated-route ActivatedRoute: the one-stop-shop for route information
:marked
Each route contains information about its path, data parameters, URL segment and much more.
All of this information is available in an injected service provided by the router called the [ActivatedRoute](../api/router/index/ActivatedRoute-interface.html).
The `ActivatedRoute` contains all the information you need from the current route component as well as ways to get information
about other activated routes in the `RouterState`.
.l-sub-section
:marked
**`url`**: An `Observable` of the route path(s). The value is provided as an array of strings for each part of the route path.
**`data`**: An `Observable` that contains the `data` object provided for the route. Also contains any resolved values from the [resolve guard](#resolve-guard).
**`params`**: An `Observable` that contains the required and [optional parameters](#optional-route-parameters) specific to the route.
**`queryParams`**: An `Observable` that contains the [query parameters](#query-parameters) available to all routes.
**`fragment`**: An `Observable` of the URL [fragment](#fragment) available to all routes.
**`outlet`**: The name of the `RouterOutlet` used to render the route. For an unnamed outlet, the outlet name is **primary**.
**`routeConfig`**: The route configuration used for the route that contains the origin path.
**`parent`**: an `ActivatedRoute` that contains the information from the parent route when using [child routes](#child-routing-component).
**`firstChild`**: contains the first `ActivatedRoute` in the list of child routes.
**`children`**: contains all the [child routes](#child-routing-component) activated under the current route.
:marked
We import the `Router`, `ActivatedRoute`, and `Params` tokens from the router package.
+makeExcerpt('app/heroes/hero-detail.component.1.ts (activated route)', 'imports')
a#hero-detail-ctor
:marked
As usual, we write a constructor that asks Angular to inject services
that the component requires and reference them as private variables.
+makeExcerpt('app/heroes/hero-detail.component.ts (constructor)', 'ctor')
:marked
Later, in the `ngOnInit` method,
we use the `ActivatedRoute` service to retrieve the parameters for our route.
Since our parameters are provided as an `Observable`, we use the _forEach_ method to retrieve them for the `id` parameter by name and
tell the `HeroService` to fetch the hero with that `id`.
+makeExcerpt('app/heroes/hero-detail.component.ts (ngOnInit)', 'ngOnInit')
.l-sub-section
:marked
Angular calls the `ngOnInit` method shortly after creating an instance of the `HeroDetailComponent`.
We put the data access logic in the `ngOnInit` method rather than inside the constructor
to improve the component's testability.
We explore this point in greater detail in the [OnInit appendix](#onInit) below.
.l-sub-section
:marked
Learn about the `ngOnInit` method in the
[Lifecycle Hooks](lifecycle-hooks.html) chapter.
h4#reuse Observable <i>params</i> and component re-use
:marked
In this example, we retrieve the route params from an `Observable`.
That implies that the route params can change during the lifetime of this component.
They might. By default, the router reuses a component instance when it re-navigates to the same component type
without visiting a different component first. The parameters can change between each re-use.
Suppose a parent component navigation bar had "forward" and "back" buttons
that scrolled through the list of heroes.
Each click navigated imperatively to the `HeroDetailComponent` with the next or previous `id`.
We don't want the router to remove the current `HeroDetailComponent` instance from the
DOM only to re-create it for the next `id`.
That could be visibly jarring.
Better to simply re-use the same component instance and update the parameter.
But `ngOnInit` is only called once per instantiation.
We need a way to detect when the route parameters change from _within the same instance_.
The observable `params` property handles that beautifully.
h4#snapshot <i>Snapshot</i>: the no-observable alternative
:marked
This application won't reuse the `HeroDetailComponent`.
We always return to the hero list to select another hero to view.
There's no way to navigate from hero detail to hero detail
without visiting the list component in between.
That means we get a new `HeroDetailComponent` instance every time.
Suppose we know for certain that `HeroDetailComponent` will *never, never, ever*
be re-used. We'll always re-create the component each time we navigate to it.
The router offers a *Snapshot* alternative that gives us the initial value of the route parameters.
We don't need to subscribe or unsubscribe.
It's much simpler to write and read:
+makeExcerpt('app/heroes/hero-detail.component.2.ts (ngOnInit snapshot)', 'snapshot')
.l-sub-section
:marked
**Remember:** we only get the _initial_ value of the parameters with this technique.
Stick with the observable `params` approach if there's even a chance that we might navigate
to this component multiple times in a row.
We are leaving the observable `params` strategy in place just in case.
a#nav-to-list
:marked
### Navigating back to the list component
The `HeroDetailComponent` has a "Back" button wired to its `gotoHeroes` method that navigates imperatively
back to the `HeroListComponent`.
The router `navigate` method takes the same one-item *link parameters array*
that we can bind to a `[routerLink]` directive.
It holds the **path to the `HeroListComponent`**:
+makeExcerpt('app/heroes/hero-detail.component.1.ts (excerpt)', 'gotoHeroes')
.l-main-section#optional-route-parameters
:marked
### Route Parameters
We use [*route parameters*](#route-parameters) to specify a *required* parameter value *within* the route URL
as we do when navigating to the `HeroDetailComponent` in order to view-and-edit the hero with *id:15*.
code-example(format="." language="bash").
localhost:3000/hero/15
:marked
Sometimes we wish to add *optional* information to a route request.
For example, the `HeroListComponent` doesn't need help to display a list of heroes.
But it might be nice if the previously-viewed hero were pre-selected when returning from the `HeroDetailComponent`.
figure.image-display
img(src='/resources/images/devguide/router/selected-hero.png' alt="Selected hero")
:marked
That becomes possible if we can include hero Magneta's `id` in the URL when we
return from the `HeroDetailComponent`, a scenario we'll pursue in a moment.
Optional information takes other forms. Search criteria are often loosely structured, e.g., `name='wind*'`.
Multiple values are common — `after='12/31/2015' & before='1/1/2017'` — in no particular order —
`before='1/1/2017' & after='12/31/2015'` — in a variety of formats — `during='currentYear'` .
These kinds of parameters don't fit easily in a URL *path*. Even if we could define a suitable URL token scheme,
doing so greatly complicates the pattern matching required to translate an incoming URL to a named route.
Optional parameters are the ideal vehicle for conveying arbitrarily complex information during navigation.
Optional parameters aren't involved in pattern matching and afford enormous flexibility of expression.
The Router supports navigation with optional parameters as well as required route parameters.
We define _optional_ parameters in an *object* after we define our required route parameters.
### Route Parameters: Required or Optional?
There is no hard-and-fast rule. In general,
*prefer a required route parameter when*
* the value is required.
* the value is necessary to distinguish one route path from another.
*prefer an optional parameter when*
* the value is optional, complex, and/or multi-variate.
<a id="route-parameters-object"></a>
### Route parameter
When navigating to the `HeroDetailComponent` we specified the _required_ `id` of the hero-to-edit in the
*route parameter* and made it the second item of the [*link parameters array*](#link-parameters-array).
+makeExcerpt('app/heroes/hero-list.component.1.ts', 'link-parameters-array')
:marked
The router embedded the `id` value in the navigation URL because we had defined it
as a route parameter with an `:id` placeholder token in the route `path`:
+makeExcerpt('app/heroes/heroes.routing.ts', 'hero-detail-route')
:marked
When the user clicks the back button, the `HeroDetailComponent` constructs another *link parameters array*
which it uses to navigate back to the `HeroListComponent`.
+makeExcerpt('app/heroes/hero-detail.component.1.ts', 'gotoHeroes')
:marked
This array lacks a route parameter because we had no reason to send information to the `HeroListComponent`.
Now we have a reason. We'd like to send the id of the current hero with the navigation request so that the
`HeroListComponent` can highlight that hero in its list.
This is a _nice-to-have_ feature; the list will display perfectly well without it.
We do that with an object that contains an _optional_ `id` parameter.
For demonstration purposes, we also defined a junk parameter (`foo`) that the `HeroListComponent` should ignore.
Here's the revised navigation statement:
+makeExcerpt('app/heroes/hero-detail.component.ts (go to heroes)', 'gotoHeroes-navigate')
:marked
The application still works. Clicking "back" returns to the hero list view.
Look at the browser address bar.
.l-sub-section
img(src='/resources/images/devguide/plunker-separate-window-button.png' alt="pop out the window" align="right" style="margin-right:-20px")
:marked
When running in plunker, pop out the preview window by clicking the blue 'X' button in the upper right corner.
:marked
It should look something like this, depending on where you run it:
code-example(language="bash").
localhost:3000/heroes;id=15;foo=foo