Updating Your Angular 2 App to Use the New Router: A Practical Guide
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.
Updating the Angular 2 Router Step by Step
These are the steps that you’ll need to follow to update the router:
- Create a separate router configuration file
- Remove routes
name
property - Remove the first
/
from thepath
property - Use route redirects instead of
useAsDefault
- Export your application route configuration
- Remove routes
- Apply that configuration when bootstrapping your Angular 2 app
- Update your
[routeLink]
directives - Use the new
ActivatedRoute
service instead ofRouteParams
- 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:
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
:
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:
import { RouteConfig } from 'angular2/router'
to:
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:
// 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:
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
:
// - 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:
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:
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:
{
path: 'persons/:id',
name: 'Person Details'
component: PersonDetailsComponent
},
The corresponding link would be created as follows:
<a href="#" [routerLink]="['Person Details', {id: person.id}]">
But in the latest router we do the following:
<a href="#" [routerLink]="['/persons', person.id]">
That is:
- we no longer use the
name
to create a route link, and instead we use thepath
- 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:
import { RouteParams } from 'angular2/router'
You’ll import this one:
import { ActivatedRoute } from '@angular/router'
Where the RouteParams
service exposed a get
method that you could use to retrieve the parameters of a route:
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:
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:
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: {{person.name}} </h2>
<h3>Description</h3>
<p>
{{person.name}} weights {{person.weight}} and is {{person.height}} 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):
<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:
- Angular Router by Victor Savkin updated with the latest Angular 2 router
- Angular Router: Empty Paths, Componentless Routs and Redirects also by Viktor Savkin with extra information about corner cases for the Angular 2 router
- Angular 2 Router straight from the Angular 2 docs with a great coding sample
- Routing in Angular 2 Revisited from the thoughtram blog
Written by Jaime González García , dad, husband, software engineer, ux designer, amateur pixel artist, tinkerer and master of the arcane arts. You can also find him on Twitter jabbering about random stuff.