Some days ago, the new Angular2 RC3 release candidate (and also some days earlies RC2) has been released. Again, some changes were made to the Angular2 structure that everyone needs to update in their Angular2 applications to become compatible to the new version. I want to show some of the changes in the new Angular2 RC3 Router in this article.
Angular2 RC3 Router
After the Angular2 Beta Router and the new Router introduced in RC0, yet another Router package has been released and the old one has bee deprecated (and is now still available as package @angular/router-deprecated version 2.0.0-rc.2). According to the team, the new router was necessary to handle some fundamental problems with the previous routing mechanism, which might be working correctly with 90%ish of the use cases, but was not able to handle all use cases Angular2 wants to address.
Although the new router package is official still in Alpha, it has been announced by the Angular2 team, that the new Router package should already be used instead of the old package.
Include the new Dependency
To upgrade Angular2 from the old RC0/1/2 versions to RC3, you upgrade the package version of the Angular packages to 2.0.0-rc.3. Because of the unregular Router upgrades, the Angular2 team has decided to use a different release cycle for the Router than for the rest of Angular2, so the current version is “3.0.0-alpha.7“:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
{ ... "dependencies": { "@angular/common": "2.0.0-rc.3", "@angular/compiler": "2.0.0-rc.3", "@angular/core": "2.0.0-rc.3", "@angular/http": "2.0.0-rc.3", "@angular/platform-browser": "2.0.0-rc.3", "@angular/platform-browser-dynamic": "2.0.0-rc.3", "@angular/router": "3.0.0-alpha.7", "core-js": "^2.4.0", "reflect-metadata": "^0.1.3", "rxjs": "5.0.0-beta.6", "systemjs": "0.19.27", "zone.js": "^0.6.12", ... }, ... } |
Define routes
In the Router Version2 the routes were defined by the @Routes annotation on the application component, which contained an Array of all paths with corresponding Component.
In the new Version3 Router, the routes already have to be given as dependency to the bootstrap process.
In the easiest configuration the routes also contain just a path (buth now without leading slash!) and a component. It is recommended to bundle the routes into a single Typescript file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import { provideRouter, RouterConfig } from '@angular/router' import {FooComponent} from "./foo.component" import {BarComponent} from "./bar.component" import {BazComponent} from "./baz.component" export const routes: RouterConfig = [ { path: "", component: FooComponent }, { path: "bar", component: BarComponent }, { path: "baz/:id", component: BazComponent } ]; export const APP_ROUTER_PROVIDERS = [ provideRouter(routes) ]; |
1 2 3 4 5 6 7 8 9 10 |
import {bootstrap} from "@angular/platform-browser-dynamic" import {AppComponent} from "./app.component" import {APP_ROUTER_PROVIDERS} from "./app.routes" /** * Bootstrap application. */ bootstrap(AppComponent, [ APP_ROUTER_PROVIDERS ]).catch(err => console.error(err)) |
Similar to the previous router, the definitions allow path paramaters beginning with a colon (like “baz/:id” can match “baz/123“). These path parameters can later be used in the target component e.g. to load a specific dataset with a given id.
Instead of listing all routes in a plain list, routes can contain sub-routes by defining the “children” attribute. Those child-routes are then appended to the prefix defined by the parent route, 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 |
import { provideRouter, RouterConfig } from '@angular/router' import {FooComponent} from "./foo.component" import {BarComponent} from "./bar.component" import {BazComponent, BazDetailComponent, BazOverviewComponent } from "./baz.component" export const routes: RouterConfig = [ { path: "", component: FooComponent }, { path: "bar", component: BarComponent }, { path: "baz", component: BazComponent, children: [ { path: ':id', component: BazDetailComponent }, { path: '', component: BazOverviewComponent } ] } ]; export const APP_ROUTER_PROVIDERS = [ provideRouter(routes) ]; |
Placing the Router Outlet
This part works exactly than in the V2 Router: To tell the Router where to include the currently activated Component, a special Tag
1 |
<router-outlet></router-outlet> |
has to be placed somewhere in the template of the application. Whenever a route is selected, the corresponding component will be embedded inside this tag.
There are more advanced setups, which can include secondary router outlets, e.g. for a sidebar or an overview. For this the outlet has to be given a name attribute, and also the routes for this outlet has to declare the same name as “outlet” attribute.
Placing Router Links
In order to navigate from one route to an other, you have to place special routerLink attributes in your template, e.g. in buttons or hyperlinks. To use this directive, you need to import and provide “ROUTER_DIRECTIVES” in the corresponding components (like in the V2 Router):
1 2 3 4 5 6 7 8 9 10 11 |
import {Component} from "@angular/core" import {ROUTER_DIRECTIVES} from "@angular/router" @Component({ selector: "foo", templateUrl: "assets/foo.html", directives: [ROUTER_DIRECTIVES] }) export class FooComponent { ... } |
1 2 3 4 |
<div> <a [routerLink]="['/bar']" title="Go to Bar">Bar</a> <a [routerLink]="['/baz/123']" title="Go to Baz">Baz 123</a> </div> |
Like real URLs, the routerlinks can be either absolute paths and match a complete route from the beginning, or relative paths, which will match a route relative to the current route, e.g.
1 2 3 4 5 |
<div> <!-- current route: /baz --> <a [routerLink]="['../bar']" title="Go to Bar">Bar</a> <a [routerLink]="['123']" title="Go to Baz">Baz/123</a> </div> |
Path parameters
In order to parse the given path parameters (e.g. to get the “123” from the route “baz/123“), the component can declare the dependency “ActivatedRoute” to get the parameters. In difference to the RC2 router, the parameters can not be evaluated directly, but are instead given as Observable. When a route is changed but still uses the same Component (e.g. link from “baz/123” to “baz/456“), the component is not recreated, but instead only the ActivatedRoute parameters given as Obserables publish a new value.
In the official documentation it is recommended to create a subscription in the ngOnInit method and unsubscribe in the ngOnDestroy method to prevent a memory leak.
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 |
import {Component, OnInit, OnDestroy} from "@angular/core" import {ROUTER_DIRECTIVES, ActivatedRoute} from "@angular/router" import {Subscription} from "rxjs/Rx"; @Component({ selector: "baz", templateUrl: "assets/baz.html", directives: [ROUTER_DIRECTIVES] }) export class BazComponent implements OnInit, OnDestroy { constructor(private activatedRoute: ActivatedRoute) { } ngOnInit() { this.idSubscription = this.activatedRoute.params.subscribe(params => { this.id = params['id'] this.load() }) } ngOnDestroy() { this.idSubscription.unsubscribe() } public id: string private idSubscription: Subscription load() { console.log("Load Id " + this.id); } } |
That’s weird! In previous versions you could name a route, so that you could have same parameters to different routes, like:
path: ‘:categoryUrl’, name: ‘Category’, component: CategoryComponent -> [routerLink]=”[‘/Category’, { categoryUrl: categoryUrl.url }]”
path: ‘:categoryUrl’, name: ‘Product’, component:ProductComponent -> [routerLink]=”[‘/Product’, { categoryUrl: categoryUrl.url }]”
Both routes have the same parameter and I can say which component I want to hit. Is it possible to keep doing that in RC3? I really tried but with no success.
I noticed that routes are solved in a tree, so adding the following routes solved my problem:
{ path: ‘:categoryUrl’, component: CategoryComponent } —> <a [routerLink]="['/', category.url, product.url]" …