Updating Your Angular 2 App to Use the New Router: A Practical Guide

| Comments

UPDATE 14th Sep 2016: Hi there! I have added an additional step to update your router from RC4 to RC5, RC6, RC7 and Angular 2 Final.

This week I’ve been updating my Getting Started with Angular 2 Step by Step series from the beta in which they were written to the latest version of Angular which is now RC4.

I had heard about the router debacle but I wasn’t aware of the massive changes required to move from the router in the beta until I updated the articles. I thought it would be helpful to write a quick, super practical guide while it’s fresh in my head. So if you’ve been postponing the router update until now, I hope this guide will help you.

Angular 2 Getting Started

Updating the Angular 2 Router Step by Step

These are the steps that you’ll need to follow to update the router:

  1. Create a separate router configuration file
    1. Remove routes name property
    2. Remove the first / from the path property
    3. Use route redirects instead of useAsDefault
    4. Export your application route configuration
  2. Apply that configuration when bootstrapping your Angular 2 app
  3. Update your [routeLink] directives
  4. Use the new ActivatedRoute service instead of RouteParams
  5. Add a base element inside the <head> your HTML markup

1. Create a Separate Router Configuration File

In Angular 2 beta we used the @RouteConfig decorator to both define the routes of our application and apply them to our route component directly:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { Component } from 'angular2/core';
import { PeopleListComponent } from './people-list.component';
import { RouteConfig } from 'angular2/router';


@Component({...})
@RouteConfig([
  {
    path: '/persons',
    name: 'Persons',
    component: PeopleListComponent,
    useAsDefault: true
  },
  {
    path: '/persons/:id',
    name: 'Person Details'
    component: PersonDetailsComponent
  },
])
export class AppComponent {
  title:string = 'Star Wars Peoplez!';
}

With the new component router you specify the configuration in a separate module. For instance, app.routes.ts:

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
import { provideRouter, RouterConfig } from '@angular/router';
import { PeopleListComponent } from './people-list.component';

// Route config let's you map routes to components
const routes: RouterConfig = [
  // map '/persons' to the people list component
  {
    path: 'persons',
    component: PeopleListComponent,
  },
  // map '/persons/:id' to person details component
  {
    path: 'persons/:id',
    component: PersonDetailsComponent
  },
  // map '/' to '/persons' as our default route
  {
    path: '',
    redirectTo: '/persons',
    pathMatch: 'full'
  },
];

export const APP_ROUTER_PROVIDERS = [
  provideRouter(routes)
];

There’s several things to highlight here. First, the router is placed in a different module. We go from:

1
import { RouteConfig } from 'angular2/router';

to:

1
import { RouteConfig } from '@angular/router';

Then the RouteConfig is no longer a decorator but an array of routes. The routes themselves no longer have a name nor useAsDefault properties and the path property leading / is removed.

Default routes are defined as redirects:

1
2
3
4
5
6
7
8
9
10
11
12
// Route config let's you map routes to components
const routes: RouterConfig = [
  // this means that the default route
  // is the /persons route
  {
    path: '',
    redirectTo: '/persons',
    pathMatch: 'full'
  },
  {...},
  {...}
];

Having defined the routes, we need to make the rest of the application aware of them. We use the provideRouter function for that:

1
2
3
export const APP_ROUTER_PROVIDERS = [
  provideRouter(routes)
];

This defines an APP_ROUTER_PROVIDERS export that not only includes our application routes but also router services suchs as the Router or ActivatedRoute (the new RouteParams).

Updating to Angular RC5, RC6, RC7 or Final? Then Things Have Changed

With RC5 (and the new version of the router which is RC1) there’s some changes that you’ll need to do. RouterConfig is now named Routes, you no longer import provideRouter but RouterModule and the way you export your application is slightly different. Here you can see an updated version of the previous app.routes.ts:

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
// - Routes instead of RouteConfig
// - RouterModule instead of provideRoutes
import { Routes, RouterModule } from '@angular/router';

import { PeopleListComponent } from './people-list.component';
import { PersonDetailsComponent } from './person-details.component';

const routes: Routes = [
  {
    path: 'persons',
    component: PeopleListComponent,
  },
  {
    path: 'persons/:id',
    component: PersonDetailsComponent
  },
  {
    path: '',
    redirectTo: '/persons',
    pathMatch: 'full'
  },
];

// - Updated Export
export const routing = RouterModule.forRoot(routes);

2. Apply that configuration when bootstrapping your Angular 2 app

In the previous section we saw how we use the app.routes.ts module to define the routing configuration of our app and expose it as an export. The next step is to connect this configuration to our app.

In this new version of Angular 2 we do that while bootstrapping our application often located in a main.ts module:

1
2
3
4
5
import { bootstrap } from '@angular/platform-browser-dynamic';
import { APP_ROUTER_PROVIDERS } from './app.routes';
import { AppComponent } from './app.component';

bootstrap(AppComponent, [APP_ROUTER_PROVIDERS]);

This will establish our application routes and provide the different routing services to the various components.

Updating to Angular RC5, RC6, RC7 or Final? Then Things Have Changed

Angular 2 RC5 introduces @NgModule which is a way to define our application dependencies so you don’t need to do it repeatedly on a per-component basis (Take a look at this guide for more information about NgModule). This means that they way you incorporate routing configuration into your application is no longer bootstrapping but in your NgModule like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

import { routing } from './app.routes'; // ROUTING HERE!

import { AppComponent }  from './app.component';
import { PeopleListComponent } from './people-list.component';
import { PersonDetailsComponent } from './person-details.component';

import { PeopleService } from './people.service';

@NgModule({
  imports: [ BrowserModule, routing, FormsModule, HttpModule], // AND HERE!
  declarations: [ AppComponent, PeopleListComponent, PersonDetailsComponent],
  bootstrap: [ AppComponent ],
  providers: [ PeopleService]
})
export class AppModule { }

3. Update your [routeLink] values to arrays of values

The [routeLink] directive helps you compose routes based on your routing configuration. In the beta, having defined a parameterized route like this:

1
2
3
4
5
  {
    path: 'persons/:id',
    name: 'Person Details'
    component: PersonDetailsComponent
  },

The corresponding link would be created as follows:

1
<a href="#" [routerLink]="['Person Details', {id: person.id}]">

But in the latest router we do the following:

1
<a href="#" [routerLink]="['/persons', person.id]">

That is:

  • we no longer use the name to create a route link, and instead we use the path
  • we no longer use an object as a container for the parameterized values but continue adding values to the link array.

4. Use the new ActivatedRoute service instead of RouteParams

In Angular 2 beta we used the RouteParams service to retrieve the parameters from a route when initializing a component. In the latest version on Angular 2 this role falls on ActivatedRoute.

So instead of importing this service:

1
import { RouteParams } from 'angular2/router';

You’ll import this one:

1
import { ActivatedRoute } from '@angular/router';

Where the RouteParams service exposed a get method that you could use to retrieve the parameters of a route:

1
2
3
4
 ngOnInit(){
    let id = Number.parseInt(this._routeParams.get('id'));
    this.person = this._peopleService.get(id);
}

The ActivatedRoute service offers a params observable that lets you access the parameters of a route in an asynchronous fashion:

1
2
3
4
5
6
 ngOnInit(){
    this.route.params.subscribe(params => {
      let id = Number.parseInt(params['id']);
      this.person = this.peopleService.get(id);
    });
}

A complete example of using ActivatedRoute could look like this:

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
import { Component, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

import { Person } from './person';
import { PeopleService } from './people.service';

@Component({
  selector: 'person-details',
  template: `
  <!-- new syntax for ng-if -->
  <section *ngIf="person">
    <h2>You selected:    </h2>
    <h3>Description</h3>
    <p>
         weights   and is   tall.
    </p>
  </section>
  `
})
export class PersonDetailsComponent implements OnInit, OnDestroy {
    person: Person;
    sub: any;

    constructor(private peopleService: PeopleService,
               private route: ActivatedRoute){
    }

    ngOnInit(){
        this.sub = this.route.params.subscribe(params => {
          let id = Number.parseInt(params['id']);
          this.person = this.peopleService.get(id);
        });
    }

    ngOnDestroy(){
        this.sub.unsubscribe();
    }
}

5. Add a base element inside the <head> your HTML markup

The new router expects you to have a base element in the head section of your HTML markup (in the index.html, _Layout.cshtml or whichever file holds your application markup):

1
2
3
4
5
6
7
8
<html>
  <head>
    <title>My app</title>
    <base href="/">
    <!-- etc... -->
 </head>
<!-- etc... -->
</html>

This element enables the HTML 5 history.pushState api that let’s Angular 2 provide “HTML5 style” URLs (as opposed to using # prefixed ones).

And that’s it! Now everything should just work.

Would You Like to Learn More About the New Router?

Then check any of these great articles that contain a ton of information about it:

Comments