ng2_router30_upgrade

Angular 2 Upgrading to new 3.0 Router

In this episode we upgrade the ng2play repo to leverage the new 3.0 router and implement a route guard to protect components that require authentication. Here’s the entire changeset, not that bloody considering the changes but this is also a very small application.

Screencast

Defining Routes

With the old router we decorated our app component with the RouteConfig annotation to configure the routs.

  @Routes([		
    { path: '/', component: Todo },
    { path: '/about/:id', component: About },
    { path: '/profile', component: Profile}		
  ])
  export class AppComponent { ... }

With the new router the routing configuration is no longer bound to a specific component, instead we make it accessible as a provider by exporting it as a const from a separate routes.ts file. Another essential change is that we need to remove the forward slash in the beginning when defining the path.

  import { provideRouter, RouterConfig } from '@angular/router';

  export const appRoutes: RouterConfig = [
    { path: '', component: Todo },
    { path: 'about/:id', component: About },
    { path: 'profile', component: Profile }
  ];

  export const APP_ROUTER_PROVIDER = provideRouter(appRoutes);

Wich we can pass in to the bootstrap function in boot.ts instead of passing ROUTER_PROVIDERS as we did earlier.

import {APP_ROUTER_PROVIDER} from './routes';

bootstrap(AppComponent, [
  ...
  APP_ROUTER_PROVIDER,
  ...
]);

Route Parameters

A component that we route to can access information about route parameters, query parameters and URL fragments by something called, ActivatedRoute, which we can inject into the constructor. The key difference is that route parameters can be accessed through the params property as an Observable or by the snapshot property if subscribing for future changes is overkill. Here’s both ways in our about.ts component:

  import {Component, OnInit} from '@angular/core';
  import { ActivatedRoute } from '@angular/router';

  @Component({
    selector: 'about',
    template: `Welcome to the about page! This is the ID: {{id}}`
  })
  export class About implements OnInit {
    id: string;

    constructor(private route: ActivatedRoute) {}

    ngOnInit() {
      this.id = this.route.snapshot.params['id'];
      this.route.params
        .map(params => params['id'])
          .subscribe(id => {
            this.id = id;
        });
    }
  }

Guarding routes

Previously we could prevent our components from activating for unauthenticated users by using the @CanActivate and @CanDeactivate annotations. The guarding logic could be put inside a callback function:

import {CanActivate} from '@angular/router';
@Component({ ... })
@CanActivate(() => tokenNotExpired())
export class Profile { ... }

This approach suffered from that we don’t have access to the applications dependency injection and that we need to decorate each component class with it. The new router solves these problems, we can easily create an auth-guard injectable service that we can pass in to the route configuration accordingly:

  import { provideRouter, RouterConfig } from '@angular/router';
  import { AuthGuard} from './auth-guard';

  export const appRoutes: RouterConfig = [
    { path: '', component: Todo },
    { path: 'about/:id', component: About },
    { path: 'profile', component: Profile, canActivate: [AuthGuard] }
  ];

  export const APP_ROUTER_PROVIDER = provideRouter(appRoutes);

The auth guard has access to the applications dependency injection and we can redirect the user to the root component if they are not allowed to navigate to the component.

import {tokenNotExpired} from 'angular2-jwt';
import { Injectable } from '@angular/core';
import {
  CanActivate,
  ActivatedRouteSnapshot,
  RouterStateSnapshot,
  Router
} from '@angular/router';

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private router: Router) {}

  canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
    if (tokenNotExpired()) {
      return true;
    }

    this.router.navigate(['']);
    return false;
  }
}

Let’s also not forget that we’ll need to pass the auth-guard to our bootstrap method to be able to resolve the dependency:

import {APP_ROUTER_PROVIDER} from './routes';
import {AuthGuard} from './auth-guard';

bootstrap(AppComponent, [
  ...
  APP_ROUTER_PROVIDER,
  AuthGuard
  ...
]);

Summary

With these steps we’ve managed to migrate our application to the new 3.0 router, quite frictionless seemingly, but imho they started calling angular2 for RC way too early. It’s still moving too much to even consider using this for production anytime soon.

Until next time, have an excellent day!