Skip to main content

Angular - Routing and Navigation Part 4

Milestone 3: Heroes feature

You've seen how to navigate using the RouterLink directive. Now you'll learn the following:
  • Organize the app and routes into feature areas using modules.
  • Navigate imperatively from one component to another.
  • Pass required and optional information in route parameters.
This example recreates the heroes feature in the "Services" episode of the Tour of Heroes tutorial, and you'll be copying much of the code from the Tour of Heroes: Services example code / download example.
Here's how the user will experience this version of the app:
App in action
A typical application has multiple feature areas, each dedicated to a particular business purpose.
While you could continue to add files to the src/app/ folder, that is unrealistic and ultimately not maintainable. Most developers prefer to put each feature area in its own folder.
You are about to break up the app into different feature modules, each with its own concerns. Then you'll import into the main module and navigate among them.

Add heroes functionality

Follow these steps:
  • Create the src/app/heroes folder; you'll be adding files implementing hero management there.
  • Delete the placeholder hero-list.component.ts that's in the app folder.
  • Create a new hero-list.component.ts under src/app/heroes.
  • Copy into it the contents of the app.component.ts from the Tour of Heroes: Services example code / download example.
  • Make a few minor but necessary changes:
    • Delete the selector (routed components don't need them).
    • Delete the 

      .
    • Relabel the 

       to 

      HEROES

      .
    • Delete the  at the bottom of the template.
    • Rename the AppComponent class to HeroListComponent.
  • Copy the hero-detail.component.ts and the hero.service.ts files into the heroes subfolder.
  • Create a (pre-routing) heroes.module.ts in the heroes folder that looks like this:
src/app/heroes/heroes.module.ts (pre-routing)
  1. import { NgModule } from '@angular/core';
  2. import { CommonModule } from '@angular/common';
  3. import { FormsModule } from '@angular/forms';
  4.  
  5. import { HeroListComponent } from './hero-list.component';
  6. import { HeroDetailComponent } from './hero-detail.component';
  7.  
  8. import { HeroService } from './hero.service';
  9.  
  10. @NgModule({
  11. imports: [
  12. CommonModule,
  13. FormsModule,
  14. ],
  15. declarations: [
  16. HeroListComponent,
  17. HeroDetailComponent
  18. ],
  19. providers: [ HeroService ]
  20. })
  21. export class HeroesModule {}
When you're done, you'll have these hero management files:
src/app/heroes
hero-detail.component.ts
hero-list.component.ts
hero.service.ts
heroes.module.ts

Hero feature routing requirements

The heroes feature has two interacting components, the hero list and the hero detail. The list view is self-sufficient; you navigate to it, it gets a list of heroes and displays them.
The detail view is different. It displays a particular hero. It can't know which hero to show on its own. That information must come from outside.
When the user selects a hero from the list, the app should navigate to the detail view and show that hero. You tell the detail view which hero to display by including the selected hero's id in the route URL.

Hero feature route configuration

Create a new heroes-routing.module.ts in the heroes folder using the same techniques you learned while creating the AppRoutingModule.
src/app/heroes/heroes-routing.module.ts
  1. import { NgModule } from '@angular/core';
  2. import { RouterModule, Routes } from '@angular/router';
  3.  
  4. import { HeroListComponent } from './hero-list.component';
  5. import { HeroDetailComponent } from './hero-detail.component';
  6.  
  7. const heroesRoutes: Routes = [
  8. { path: 'heroes', component: HeroListComponent },
  9. { path: 'hero/:id', component: HeroDetailComponent }
  10. ];
  11.  
  12. @NgModule({
  13. imports: [
  14. RouterModule.forChild(heroesRoutes)
  15. ],
  16. exports: [
  17. RouterModule
  18. ]
  19. })
  20. export class HeroRoutingModule { }
Put the routing module file in the same folder as its companion module file. Here both heroes-routing.module.ts and heroes.module.ts are in the same src/app/heroes folder.
Consider giving each feature module its own route configuration file. It may seem like overkill early when the feature routes are simple. But routes have a tendency to grow more complex and consistency in patterns pays off over time.
Import the hero components from their new locations in the src/app/heroes/ folder, define the two hero routes, and export the HeroRoutingModule class.
Now that you have routes for the Heroes module, register them with the Router via the RouterModule almostas you did in the AppRoutingModule.
There is a small but critical difference. In the AppRoutingModule, you used the static RouterModule.forRoot method to register the routes and application level service providers. In a feature module you use the static forChild method.
Only call RouterModule.forRoot in the root AppRoutingModule (or the AppModule if that's where you register top level application routes). In any other module, you must call the RouterModule.forChild method to register additional routes.

Add the routing module to the HeroesModule

Add the HeroRoutingModule to the HeroModule just as you added AppRoutingModule to the AppModule.
Open heroes.module.ts. Import the HeroRoutingModule token from heroes-routing.module.ts and add it to the imports array of the HeroesModule. The finished HeroesModule looks like this:
src/app/heroes/heroes.module.ts
  1. import { NgModule } from '@angular/core';
  2. import { CommonModule } from '@angular/common';
  3. import { FormsModule } from '@angular/forms';
  4.  
  5. import { HeroListComponent } from './hero-list.component';
  6. import { HeroDetailComponent } from './hero-detail.component';
  7.  
  8. import { HeroService } from './hero.service';
  9.  
  10. import { HeroRoutingModule } from './heroes-routing.module';
  11.  
  12. @NgModule({
  13. imports: [
  14. CommonModule,
  15. FormsModule,
  16. HeroRoutingModule
  17. ],
  18. declarations: [
  19. HeroListComponent,
  20. HeroDetailComponent
  21. ],
  22. providers: [ HeroService ]
  23. })
  24. export class HeroesModule {}

Remove duplicate hero routes

The hero routes are currently defined in two places: in the HeroesRoutingModule, by way of the HeroesModule, and in the AppRoutingModule.
Routes provided by feature modules are combined together into their imported module's routes by the router. This allows you to continue defining the feature module routes without modifying the main route configuration.
But you don't want to define the same routes twice. Remove the HeroListComponent import and the /heroesroute from the app-routing.module.ts.
Leave the default and the wildcard routes! These are concerns at the top level of the application itself.
src/app/app-routing.module.ts (v2)
import { NgModule }              from '@angular/core';
import { RouterModule, Routes }  from '@angular/router';

import { CrisisListComponent }   from './crisis-list.component';
// import { HeroListComponent }  from './hero-list.component';  // <-- delete="" line="" span="" this="">
import { PageNotFoundComponent } from './not-found.component';

const appRoutes: Routes = [
  { path: 'crisis-center', component: CrisisListComponent },
  // { path: 'heroes',     component: HeroListComponent }, // <-- delete="" line="" span="" this="">
  { path: '',   redirectTo: '/heroes', pathMatch: 'full' },
  { path: '**', component: PageNotFoundComponent }
];

@NgModule({
  imports: [
    RouterModule.forRoot(
      appRoutes,
      { enableTracing: true } // <-- debugging="" only="" purposes="" span="">
    )
  ],
  exports: [
    RouterModule
  ]
})
export class AppRoutingModule {}

Import hero module into AppModule

The heroes feature module is ready, but the application doesn't know about the HeroesModule yet. Open app.module.ts and revise it as follows.
Import the HeroesModule and add it to the imports array in the @NgModule metadata of the AppModule.
Remove the HeroListComponent from the AppModule's declarations because it's now provided by the HeroesModule. This is important. There can be only one owner for a declared component. In this case, the Heroes module is the owner of the Heroes components and is making them available to components in the AppModule via the HeroesModule.
As a result, the AppModule no longer has specific knowledge of the hero feature, its components, or its route details. You can evolve the hero feature with more components and different routes. That's a key benefit of creating a separate module for each feature area.
After these steps, the AppModule should look like this:
src/app/app.module.ts
  1. import { NgModule } from '@angular/core';
  2. import { BrowserModule } from '@angular/platform-browser';
  3. import { FormsModule } from '@angular/forms';
  4.  
  5. import { AppComponent } from './app.component';
  6. import { AppRoutingModule } from './app-routing.module';
  7. import { HeroesModule } from './heroes/heroes.module';
  8.  
  9. import { CrisisListComponent } from './crisis-list.component';
  10. import { PageNotFoundComponent } from './not-found.component';
  11.  
  12. @NgModule({
  13. imports: [
  14. BrowserModule,
  15. FormsModule,
  16. HeroesModule,
  17. AppRoutingModule
  18. ],
  19. declarations: [
  20. AppComponent,
  21. CrisisListComponent,
  22. PageNotFoundComponent
  23. ],
  24. bootstrap: [ AppComponent ]
  25. })
  26. export class AppModule { }

Module import order matters

Look at the module imports array. Notice that the AppRoutingModule is last. Most importantly, it comes afterthe HeroesModule.
src/app/app.module.ts (module-imports)
imports: [
  BrowserModule,
  FormsModule,
  HeroesModule,
  AppRoutingModule
],
The order of route configuration matters. The router accepts the first route that matches a navigation request path.
When all routes were in one AppRoutingModule, you put the default and wildcard routes last, after the /heroes route, so that the router had a chance to match a URL to the /heroes route before hitting the wildcard route and navigating to "Page not found".
The routes are no longer in one file. They are distributed across two modules, AppRoutingModule and HeroesRoutingModule.
Each routing module augments the route configuration in the order of import. If you list AppRoutingModulefirst, the wildcard route will be registered before the hero routes. The wildcard route—which matches everyURL—will intercept the attempt to navigate to a hero route.
Reverse the routing modules and see for yourself that a click of the heroes link results in "Page not found". Learn about inspecting the runtime router configuration below.

Route definition with a parameter

Return to the HeroesRoutingModule and look at the route definitions again. The route to HeroDetailComponent has a twist.
src/app/heroes/heroes-routing.module.ts (excerpt)
{ path: 'hero/:id', component: HeroDetailComponent }
Notice the :id token in the path. That creates a slot in the path for a Route Parameter. In this case, the router will insert the id of a hero into that slot.
If you tell the router to navigate to the detail component and display "Magneta", you expect a hero id to appear in the browser URL like this:
localhost:3000/hero/15
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.
ROUTE PARAMETER: REQUIRED OR OPTIONAL?
Embedding the route parameter token, :id, in the route definition path is a good choice for this 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.

Setting the route parameters in the list view

After navigating to the HeroDetailComponent, you expect to see the details of the selected hero. You need two pieces of information: the routing path to the component and the hero's id.
Accordingly, the link parameters array has two items: the routing path and a route parameter that specifies the id of the selected hero.
src/app/heroes/hero-list.component.ts (link-parameters-array)
['/hero', hero.id] // { 15 }
The router composes the destination URL from the array like this: localhost:3000/hero/15.
How does the target HeroDetailComponent learn about that id? Don't analyze the URL. Let the router do it.
The router extracts the route parameter (id:15) from the URL and supplies it to the HeroDetailComponent via the ActivatedRoute service.

Activated Route in action

Import the RouterActivatedRoute, and ParamMap tokens from the router package.
src/app/heroes/hero-detail.component.ts (activated route)
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
Import the switchMap operator because you need it later to process the Observable route parameters.
src/app/heroes/hero-detail.component.ts (switchMap operator import)
import { switchMap } from 'rxjs/operators';
As usual, you write a constructor that asks Angular to inject services that the component requires and reference them as private variables.
src/app/heroes/hero-detail.component.ts (constructor)
constructor(
  private route: ActivatedRoute,
  private router: Router,
  private service: HeroService
) {}
Later, in the ngOnInit method, you use the ActivatedRoute service to retrieve the parameters for the route, pull the hero id from the parameters and retrieve the hero to display.
src/app/heroes/hero-detail.component.ts (ngOnInit)
ngOnInit() {
  this.hero$ = this.route.paramMap.pipe(
    switchMap((params: ParamMap) =>
      this.service.getHero(params.get('id')))
  );
}
The paramMap processing is a bit tricky. When the map changes, you get() the id parameter from the changed parameters.
Then you tell the HeroService to fetch the hero with that id and return the result of the HeroService request.
You might think to use the RxJS map operator. But the HeroService returns an Observable. So you flatten the Observable with the switchMap operator instead.
The switchMap operator also cancels previous in-flight requests. If the user re-navigates to this route with a new id while the HeroService is still retrieving the old idswitchMap discards that old request and returns the hero for the new id.
The observable Subscription will be handled by the AsyncPipe and the component's hero property will be (re)set with the retrieved hero.

ParamMap API

The ParamMap API is inspired by the URLSearchParams interface. It provides methods to handle parameter access for both route parameters (paramMap) and query parameters (queryParamMap).
MemberDescription
has(name)
Returns true if the parameter name is in the map of parameters.
get(name)
Returns the parameter name value (a string) if present, or null if the parameter name is not in the map. Returns the first element if the parameter value is actually an array of values.
getAll(name)
Returns a string array of the parameter name value if found, or an empty array if the parameter name value is not in the map. Use getAll when a single parameter could have multiple values.
keys
Returns a string array of all parameter names in the map.

Observable paramMap and component reuse

In this example, you retrieve the route parameter map from an Observable. That implies that the route parameter map can change during the lifetime of this component.
They might. By default, the router re-uses a component instance when it re-navigates to the same component type without visiting a different component first. The route parameters could change each time.
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.
You 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.
Unfortunately, ngOnInit is only called once per component instantiation. You need a way to detect when the route parameters change from within the same instance. The observable paramMap property handles that beautifully.
When subscribing to an observable in a component, you almost always arrange to unsubscribe when the component is destroyed.
There are a few exceptional observables where this is not necessary. The ActivatedRouteobservables are among the exceptions.
The ActivatedRoute and its observables are insulated from the Router itself. The Router destroys a routed component when it is no longer needed and the injected ActivatedRoute dies with it.
Feel free to unsubscribe anyway. It is harmless and never a bad practice.

Snapshot: the no-observable alternative

This application won't re-use the HeroDetailComponent. The user always returns to the hero list to select another hero to view. There's no way to navigate from one hero detail to another hero detail without visiting the list component in between. Therefore, the router creates a new HeroDetailComponent instance every time.
When you know for certain that a HeroDetailComponent instance will never, never, ever be re-used, you can simplify the code with the snapshot.
The route.snapshot provides the initial value of the route parameter map. You can access the parameters directly without subscribing or adding observable operators. It's much simpler to write and read:
src/app/heroes/hero-detail.component.ts (ngOnInit snapshot)
ngOnInit() {
  let id = this.route.snapshot.paramMap.get('id');

  this.hero$ = this.service.getHero(id);
}
Remember: you only get the initial value of the parameter map with this technique. Stick with the observable paramMap approach if there's even a chance that the router could re-use the component. This sample stays with the observable paramMap strategy just in case.
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 you can bind to a [routerLink] directive. It holds the path to the HeroListComponent:
src/app/heroes/hero-detail.component.ts (excerpt)
gotoHeroes() {
  this.router.navigate(['/heroes']);
}

Route Parameters: Required or optional?

Use route parameters to specify a required parameter value within the route URL as you do when navigating to the HeroDetailComponent in order to view the hero with id 15:
localhost:3000/hero/15
You can also add optional information to a route request. For example, when returning to the heroes list from the hero detail view, it would be nice if the viewed hero was preselected in the list.
Selected hero
You'll implement this feature in a moment by including the viewed hero's id in the URL as an optional parameter when returning from the HeroDetailComponent.
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 you 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 flexibility of expression.
The router supports navigation with optional parameters as well as required route parameters. Define optional parameters in a separate object after you define the required route parameters.
In general, prefer a required route parameter when the value is mandatory (for example, if necessary to distinguish one route path from another); prefer an optional parameter when the value is optional, complex, and/or multivariate.

Heroes list: optionally selecting a hero

When navigating to the HeroDetailComponent you specified the required id of the hero-to-edit in the route parameter and made it the second item of the link parameters array.
src/app/heroes/hero-list.component.ts (link-parameters-array)
['/hero', hero.id] // { 15 }
The router embedded the id value in the navigation URL because you had defined it as a route parameter with an :id placeholder token in the route path:
src/app/heroes/heroes-routing.module.ts (hero-detail-route)
{ path: 'hero/:id', component: HeroDetailComponent }
When the user clicks the back button, the HeroDetailComponent constructs another link parameters arraywhich it uses to navigate back to the HeroListComponent.
src/app/heroes/hero-detail.component.ts (gotoHeroes)
gotoHeroes() {
  this.router.navigate(['/heroes']);
}
This array lacks a route parameter because you had no reason to send information to the HeroListComponent.
Now you have a reason. You'd like to send the id of the current hero with the navigation request so that theHeroListComponent can highlight that hero in its list. This is a nice-to-have feature; the list will display perfectly well without it.
Send the id with an object that contains an optional id parameter. For demonstration purposes, there's an extra junk parameter (foo) in the object that the HeroListComponent should ignore. Here's the revised navigation statement:
src/app/heroes/hero-detail.component.ts (go to heroes)
gotoHeroes(hero: Hero) {
  let heroId = hero ? hero.id : null;
  // Pass along the hero id if available
  // so that the HeroList component can select that hero.
  // Include a junk 'foo' property for fun.
  this.router.navigate(['/heroes', { id: heroId, foo: 'foo' }]);
}
The application still works. Clicking "back" returns to the hero list view.
Look at the browser address bar.
It should look something like this, depending on where you run it:
localhost:3000/heroes;id=15;foo=foo
The id value appears in the URL as (;id=15;foo=foo), not in the URL path. The path for the "Heroes" route doesn't have an :id token.
The optional route parameters are not separated by "?" and "&" as they would be in the URL query string. They are separated by semicolons ";" This is matrix URL notation—something you may not have seen before.
Matrix URL notation is an idea first introduced in a 1996 proposal by the founder of the web, Tim Berners-Lee.
Although matrix notation never made it into the HTML standard, it is legal and it became popular among browser routing systems as a way to isolate parameters belonging to parent and child routes. The Router is such a system and provides support for the matrix notation across browsers.
The syntax may seem strange to you but users are unlikely to notice or care as long as the URL can be emailed and pasted into a browser address bar as this one can.

Route parameters in the ActivatedRoute service

The list of heroes is unchanged. No hero row is highlighted.
The live example / download example does highlight the selected row because it demonstrates the final state of the application which includes the steps you're about to cover. At the moment this guide is describing the state of affairs prior to those steps.
The HeroListComponent isn't expecting any parameters at all and wouldn't know what to do with them. You can change that.
Previously, when navigating from the HeroListComponent to the HeroDetailComponent, you subscribed to the route parameter map Observable and made it available to the HeroDetailComponent in the ActivatedRouteservice. You injected that service in the constructor of the HeroDetailComponent.
This time you'll be navigating in the opposite direction, from the HeroDetailComponent to the HeroListComponent.
First you extend the router import statement to include the ActivatedRoute service symbol:
src/app/heroes/hero-list.component.ts (import)
import { ActivatedRoute, ParamMap } from '@angular/router';
Import the switchMap operator to perform an operation on the Observable of route parameter map.
src/app/heroes/hero-list.component.ts (rxjs imports)
import { Observable } from 'rxjs';
import { switchMap } from 'rxjs/operators';
Then you inject the ActivatedRoute in the HeroListComponent constructor.
src/app/heroes/hero-list.component.ts (constructor and ngOnInit)
export class HeroListComponent implements OnInit {
  heroes$: Observable<Hero[]>;

  private selectedId: number;

  constructor(
    private service: HeroService,
    private route: ActivatedRoute
  ) {}

  ngOnInit() {
    this.heroes$ = this.route.paramMap.pipe(
      switchMap((params: ParamMap) => {
        // (+) before `params.get()` turns the string into a number
        this.selectedId = +params.get('id');
        return this.service.getHeroes();
      })
    );
  }
}
The ActivatedRoute.paramMap property is an Observable map of route parameters. The paramMap emits a new map of values that includes id when the user navigates to the component. In ngOnInit you subscribe to those values, set the selectedId, and get the heroes.
Update the template with a class binding. The binding adds the selected CSS class when the comparison returns true and removes it when false. Look for it within the repeated 
  •  tag as shown here:
    src/app/heroes/hero-list.component.ts (template)
    template: `
      

    HEROES

    • ngFor="let hero of heroes$ | async" [class.selected]="hero.id === selectedId"> <a [routerLink]="['/hero', hero.id]"> {{ hero.id }}{{ hero.name }} </a>
    `
    When the user navigates from the heroes list to the "Magneta" hero and back, "Magneta" appears selected:
    Selected List
    The optional foo route parameter is harmless and continues to be ignored.

    Adding animations to the routed component

    The heroes feature module is almost complete, but what is a feature without some smooth transitions?
    This section shows you how to add some animations to the HeroDetailComponent.
    First import BrowserAnimationsModule:
    src/app/app.module.ts (animations-module)
    import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
    
    @NgModule({
      imports: [
        BrowserAnimationsModule
    Create an animations.ts file in the root src/app/ folder. The contents look like this:
    src/app/animations.ts (excerpt)
    import { animate, state, style, transition, trigger } from '@angular/animations';
    
    // Component transition animations
    export const slideInDownAnimation =
      trigger('routeAnimation', [
        state('*',
          style({
            opacity: 1,
            transform: 'translateX(0)'
          })
        ),
        transition(':enter', [
          style({
            opacity: 0,
            transform: 'translateX(-100%)'
          }),
          animate('0.2s ease-in')
        ]),
        transition(':leave', [
          animate('0.5s ease-out', style({
            opacity: 0,
            transform: 'translateY(100%)'
          }))
        ])
      ]);
    This file does the following:
    • Imports the animation symbols that build the animation triggers, control state, and manage transitions between states.
    • Exports a constant named slideInDownAnimation set to an animation trigger named routeAnimation; animated components will refer to this name.
    • Specifies the wildcard state , *, that matches any animation state that the route component is in.
    • Defines two transitions, one to ease the component in from the left of the screen as it enters the application view (:enter), the other to animate the component down as it leaves the application view (:leave).
    You could create more triggers with different transitions for other route components. This trigger is sufficient for the current milestone.
    Back in the HeroDetailComponent, import the slideInDownAnimation from './animations.ts. Add the HostBinding decorator to the imports from @angular/core; you'll need it in a moment.
    Add an animations array to the @Component metadata's that contains the slideInDownAnimation.
    Then add three @HostBinding properties to the class to set the animation and styles for the route component's element.
    src/app/heroes/hero-detail.component.ts (host bindings)
    @HostBinding('@routeAnimation') routeAnimation = true;
    @HostBinding('style.display')   display = 'block';
    @HostBinding('style.position')  position = 'absolute';
    The '@routeAnimation' passed to the first @HostBinding matches the name of the slideInDownAnimationtriggerrouteAnimation. Set the routeAnimation property to true because you only care about the :enterand :leave states.
    The other two @HostBinding properties style the display and position of the component.
    The HeroDetailComponent will ease in from the left when routed to and will slide down when navigating away.
    Applying route animations to individual components works for a simple demo, but in a real life app, it is better to animate routes based on route paths.

    Milestone 3 wrap up

    You've learned how to do the following:
    • Organize the app into feature areas.
    • Navigate imperatively from one component to another.
    • Pass information along in route parameters and subscribe to them in the component.
    • Import the feature area NgModule into the AppModule.
    • Apply animations to the route component.
    After these changes, the folder structure looks like this:
    router-sample
    src
    app
    heroes
    hero-detail.component.ts
    hero-list.component.ts
    hero.service.ts
    heroes.module.ts
    heroes-routing.module.ts
    app.component.ts
    app.module.ts
    app-routing.module.ts
    crisis-list.component.ts
    main.ts
    index.html
    styles.css
    tsconfig.json
    node_modules ...
    package.json
    Here are the relevant files for this version of the sample application.
    1. import { Component } from '@angular/core';
    2.  
    3. @Component({
    4. selector: 'app-root',
    5. template: `
    6. Angular

      Router
  • <a routerLink="/crisis-center" routerLinkActive="active">Crisis Center</a>
  • <a routerLink="/heroes" routerLinkActive="active">Heroes</a>
  • <router-outlet></router-outlet>
  • `
  • })
  • export class AppComponent { }
  • Comments