Categories: Angular 2

Complete beginners guide to Routing in Angular 2

In this tutorial you will learn Angular 2 Routing from scratch by buildng a real life application . The Angular 2 router was built with componentization in mind. We will see how can we create our custom links and make components react to them in the following pages. In this Tutorial, we will:

  • Discover how to define routes to switch components on and off and redirect them to other routes
  • Trigger routes and load components in our views depending on the requested route
  • Pass parameters to our components straight from our routes
  • Look at the different component lifecycle hooks based on the routing stages
  • Define different URL representation strategies

Adding support for the Angular 2 router

Angular team introduced a revamped routing mechanism when switching versions from Beta to Release Candidate. This new routing machinery, which aims to replace the routing API that Angular had been implementing since its Alpha version, also introduced relevant breaking changes with its previous incarnation. In order to ensure that applications built on top of the previous router could upgrade to Angular 2 Release Candidate seamlessly and prevent major issues, the Angular team made available a snapshot of the Beta Router, available from the @angular/router-deprecated barrel. That is why we installed and configured two routing packages.

We also need to inform Angular about the base path we want to use, so it can properly build and recognize the URLs as the user browses the website, as we will see in the next section. Our first task will be to insert a base href statement within our <HEAD> element. Append the following line of code at the end of your code statement inside the <head> tag:

index.html

<base href="/">

The base tag informs the browser about the path it should follow while attempting to load external resources, such as media or CSS files, once it goes deeper into the URL hierarchy.

Now, we can start playing around with all the goodies existing in the router library. Prior to this, we would need to inform the dependency injector about how it can instantiate the tokens we will require later on while implementing the routing features in our components. All these providers are accessible from the ROUTER_PROVIDERS symbol. In a similar fashion as we did with HTTP_PROVIDERS, we need to declare it in the providers property of the top root component so that it is available for all its child components’ injectors.

Open your top component module and append the following import statement to the existing block of imported symbols. Then, add it to the providers property of the component decorator:

app/app.component.ts

...
import { SHARED_PROVIDERS } from './shared/shared';
import { HTTP_PROVIDERS } from '@angular/http';
import { ROUTER_PROVIDERS } from '@angular/router-deprecated';

@Component({
  selector: 'pomodoro-app',
  directives: [ROUTER_DIRECTIVES],
  providers: [SHARED_PROVIDERS, HTTP_PROVIDERS, ROUTER_PROVIDERS],
  template: `
...

Setting up the router service

With the providers and directives in place, our first step will be to turn our main host component into a router component. Basically, any component can become a routing component just by conforming to the following requirements:

  • Just like the component class is flagged with a @Component decorator, we want to decorate it with a @RouteConfig decorator.
  • The @RouteConfig decorator is configured with an array of RouteDefinitions, which are basically object literals defining a path identified with a name and pointing to a component type.
  • The component decorated with the @RouteConfig decorator is then supposed to include a RouterOutlet directive in its template. This element will become the placeholder where the components will be loaded and rendered upon loading a route pointing to each of them, removing any previous component existing there, if any.

In this sense, it is right to say that the router watches for state changes in the browser URL and then searches for a RouteDefinition object whose path property matches the existing URL. Then, it instantiates the component defined in such route definition inside the placeholder represented by the router outlet directive, which is meant to live in the template belonging to the component decorated with that router configuration.

Let’s see all this through a real example. we will define different routes to use each one, implementing the following logic:

  • The user reaches our app and checks the current listing of the tasks pending to be done. The user can schedule the tasks to be done in order to get the required time estimation for the next Pomodoro session.
  • If desired, the user can jump onto another page and see a create task form (we will create the form, but will not implement its editing features until the next chapter).
  • The user can choose any task at any time and begin the Pomodoro session required to accomplish it.
  • The user can move back and forth across the pages she or he has already visited.

Building a new component for demonstration purposes

So far, we have built two well-differentiated components we can leverage to deliver a multipage navigation. But in order to provide a better user experience, we might need a third one.

We will create a component in our tasks feature folder, anticipating the form we will use in the next chapter to publish new tasks. Create the following files in the locations pointed out for each one:

app/tasks/task-editor.component.ts

import { Component } from 'angular2/core';
import { ROUTER_DIRECTIVES } from 'angular2/router';

@Component({
  selector: 'pomodoro-tasks-editor',
  directives: [ROUTER_DIRECTIVES],
  templateUrl: 'app/tasks/task-editor.component.html'
})
export default class TaskEditorComponent {
  constructor() {}
}

app/tasks/task-editor.component.html

<form class="container">
  <h3>Task Editor:</h3>
  <div class="form-group">
    <input type="text"
      class="form-control"
      placeholder="Task name"
      required>
  </div>

  <div class="form-group">
    <input type="Date"
      class="form-control"
      required>
  </div>

  <div class="form-group">
    <input type="number"
      class="form-control"
      placeholder="Pomodoros required"
      min="1"
      max="4"
      required>
  </div>

  <div class="form-group">
    <input type="checkbox" name="queued">
    <label for="queued"> this task by default?</label>
  </div>

  <p>
    <input type="submit" class="btn btn-success" value="Save">
    <a href="/" class="btn btn-danger">Cancel</a>
  </p>
</form>

This is the most basic definition of a component, but we will also bring the ROUTER_DIRECTIVES symbol from the router library. This will provide us support, as we will see later on, to include routing directives in our HTML template. This will be used to introduce links in our template to jump to other components, as we will see shortly. Last but not least, we need to expose this new component from our feature folder facade:

app/tasks/tasks.ts

import TasksComponent from './tasks.component';
import TaskEditorComponent from './task-editor.component';
import TaskTooltipDirective from './task-tooltip.directive';

const TASKS_DIRECTIVES: any[] = [
  TasksComponent,
  TaskEditorComponent,
  TaskTooltipDirective
];

export {
  TASKS_DIRECTIVES,
  TasksComponent,
  TaskEditorComponent,
  TaskTooltipDirective
};

Configuring the RouteConfig decorator with the RouteDefinition instances

In order to achieve these goals, we need to start building our top router, which will be in charge of kicking off the routes’ scaffolding. The logical path begins in our top root component. Open its file module and import the following tokens, right next to the ROUTER_PROVIDERS symbol we imported at the beginning of this chapter. The code is as follows:

app/app.component.ts

...
import { 
  ROUTER_PROVIDERS,
  RouteConfig,
  ROUTER_DIRECTIVES 
} from '@angular/router-deprecated';
import { TimerComponent } from './timer/timer';
import { 
  TasksComponent,
  TaskEditorComponent } from './tasks/tasks';
...

The RouteConfig represents the decorator type that will turn our component into a router component. The ROUTER_DIRECTIVES symbol wraps the view directives we will need shortly to link to these routes. We also import the tokens of all the three components we will be dealing with. As we will shortly see, each route needs to declare the type of the component we are routing the browser to.

Let’s continue by replacing the directives in our AppComponent module by the ROUTE_DIRECTIVES symbol, since we will not need to declare the facade tokens of the components that lived in its template anymore. The router will handle this for us:

app/app.component.ts

@Component({
  selector: 'pomodoro-app',
  directives: [ROUTER_DIRECTIVES],
  providers: [SHARED_PROVIDERS, HTTP_PROVIDERS, ROUTER_PROVIDERS],
  template: `
  ...
})

Now, let’s expand the component class definition with the RouteConfig decorator by appending the following decorator right after the @Component decorator block and before the class statement:

app/app.component.ts

@RouteConfig([
  { path: '',
    name: 'TasksComponent',
    component: TasksComponent 
  },
 { 
    path: 'tasks/editor',
    name: 'TaskEditorComponent',
    component: TaskEditorComponent 
  }, {
    path: 'timer',
    name: 'TimerComponent',
    component: TimerComponent
  }
])
export default class AppComponent {}

As we pointed out at the beginning of this chapter, the RouteConfig decorator must be populated with an array of RouteDefinition objects, each one specifying a path that, once reached by the user, will enable the component whose type we have defined in the component property.

Tip

Note: The new Router replaced the @RouteConfig decorator by the @Routes decorator. The name property is removed from the route definitions schema and route matching is performed just by checking the path value.

In the previous example, our host component will react to three different routes and thus serve the TasksComponent item, the TimerComponent item, or the TaskEditorComponent item depending on the route’s path.

Tip

Here we stumble upon another common convention in the previous version fo the Angular 2 router: naming routes with the same name as the component they refer to. As we will see shortly, we will use each route name for populating the links pointing to each resource, so naming routes after the component they will activate becomes pretty useful and intuitive when it comes to assessing the target of each link found in the template. This convention is no longer enforced in the new router, since only paths are used to perform route matching.

There are two questions at this point: where will these components be rendered and how will we trigger each route? In order to answer these questions, we need to look into the router directives in detail.

The router directives – RouterOutlet and RouterLink

The ROUTER_DIRECTIVES symbol gives us access to the only two directives we will need in our applications.

First, the RouterOutlet directive is the placeholder directive where the different components whose paths have been navigated to by the user will be rendered. The RouterLink is used as an attribute directive to help the HTML controls behave as anchors or link buttons leading to the different routes by specifying the unique name each route is configured with, as we will see next.

In the previous chapters, we configured our top root component template to display a cute nav bar header, followed by the custom elements representing the Pomodoro timer and the tasks list. Now, we will strip the HTML template out from the component decorator definition and save it into its own template file, in order to access it more conveniently when editing the HTML is required. Also, we will refactor it into a router-friendly component template with links pointing to the different views or states our application can feature, as follows:

app/app.component.ts

   ...
   @Component({
  selector: 'pomodoro-app',
  directives: [ROUTER_DIRECTIVES],
  providers: [SHARED_PROVIDERS, HTTP_PROVIDERS, ROUTER_PROVIDERS],
  templateUrl: 'app/app.component.html'
})
...

app/app.component.html

<nav class="navbar navbar-default navbar-static-top">
  <div class="container">
    <div class="navbar-header">
      <strong class="navbar-brand">My Pomodoro App</strong>
    </div>
    <ul class="nav navbar-nav navbar-right">
      <li><a [routerLink]="['TasksComponent']">Tasks</a></li>
      <li><a [routerLink]="['TimerComponent']">Timer</a></li>
      <li><a [routerLink]="['TaskEditorComponent']">
          Publish Task</a>
      </li>
    </ul>
  </div>
</nav>

<router-outlet></router-outlet>

As you can see in the template, the location where the components used to live has been replaced by the <router-outlet> directive, and three new links compound up our beautiful nav bar. Reload the page and see how our tasks list is rendered on the screen, and then click on the Timer link. Awesome! The TimerComponent item just loaded on the screen. Click on the other link or hit back on your browser to see how you can jump across the different components seamlessly.

Let’s take a look at these links more closely, taking the link pointing to the timer as an example:

<a [routerLink]="['TimerComponent']">Timer</a>

The morphology of the routerLink directive is pretty self-explanatory. On the right-hand side of the equal symbol, it expects an array of route names corresponding to the named route and, optionally, the subroutes within the former that we want to navigate to. Most of the time, we will just see one unique string value. However, the array can allocate many values depending on whether the component whose named path we are pointing to hosts its own router with named routes as well. In this case, the subroute names we want to load will be declared next in the strings array. This is why it is so convenient and recommended to name our routes after the components they point at.

Note

The new Release Candidate Router deprecates named routes, favoring URL paths instead. Therefore, the [routerLink] directive will expect a full path as a value.

The routerLink definition also leaves room to add parameters declaratively, so we can trigger dynamic routes at runtime. We will tap into all these features throughout the next sections, but now we need to answer one important question. What if we want to navigate to a component imperatively without actually clicking on a link, but rather as the by-product of an action performed within the component’s controller class?

Triggering routes imperatively

Perhaps, you would like to jump on our timer by selecting the task we want to work on. We have already set up a behavior that displayed a queued label whenever each task was picked for being done. We will leverage the same behavior to create a work on button that will redirect the user to the timer component.

First, let’s inject the Router type as a dependency in our TasksComponent module, so we can gain access imperatively to its methods. Since we already declared the ROUTER_PROVIDERS symbol while bootstrapping the application, Angular 2 will take care of injecting the router type if properly declared in our component, so let’s do it. Add the following import statement at the top of the component, right after the existing ones:

app/tasks/tasks.component.ts

import { Router } from '@angular/router-deprecated';

Now, let’s update the constructor to inject the router type. We will mark the constructor as a private form so that it becomes privately available from the component members:

app/tasks/tasks.component.ts

...
constructor(
  private taskService: TaskService,
  private settingsService: SettingsService,
  private router: Router) {
    this.tasks = this.taskService.taskStore;
    this.today = new Date();
    this.queueHeaderMapping = settingsService.pluralsMap.tasks;
    this.timerMinutes = settingsService.timerMinutes;
  }
...

Now, let’s add a method right below the updateQueuedPomodoros() method, which will lead the user imperatively to the task route upon executing it:

workOn(): void {
  this.router.navigate(['TimerComponent']);
}

How do we execute it? Let’s introduce a new button in our table, next to the toggle task button at the last cell on each row, with a click handler pointing to the preceding method. The code is as follows:

app/tasks/tasks.component.html

<td>
  <button type="button" class="btn btn-default btn-xs"
    [ngSwitch]="task.queued"
    (click)="toggleTask(task)">
    <template [ngSwitchWhen]="false">
      <i class="glyphicon glyphicon-plus-sign"></i>
      Add
    </template>
    <template [ngSwitchWhen]="true">
      <i class="glyphicon glyphicon-minus-sign"></i>
      Remove
    </template>
    <template ngSwitchDefault>
      <i class="glyphicon glyphicon-plus-sign"></i>
      Add
    </template>
  </button>
  <button type="button"
    class="btn btn-default btn-xs"
    *ngIf="task.queued"
    (click)="workOn()">
    <i class="glyphicon glyphicon-expand"></i> Start
  </button>
</td>

A convenient NgIf directive will display the button only when required. We have included an icon for cosmetic purposes, but feel free to remove it or replace it by any other glyph of your choice.

Now reload the table, set out any task to be done, and click on the button that appears. Voila! You will be redirected to the timer to begin working on that task if desired.

Note

The navigate() method of the new Router will expect a string containing the full path instead.

CSS hooks for active routes

We have seen how to turn any link or DOM element into a hyperlink pointing to a named route that instantiates a component. However, it would be great to provide some kind of visual cue about the active link at any given time. That is precisely one of the side features implemented in the routerLink directive: whenever the route defined on a routerLink directive becomes active (regardless of whether the user reached that route declaratively or imperatively), the element will be decorated with the router-link-active class. We can therefore introduce specific CSS definitions in our components’ style sheets to highlight if a specific link is active or not.

We will see all this functionality in action just by tweaking our top parent component a bit. Open the top root component controller class file and insert the following style sheet in hte component decorator configuration:

app/app.component.ts

@Component({
  selector: 'pomodoro-app',
  directives: [ROUTER_DIRECTIVES],
  providers: [SHARED_PROVIDERS, HTTP_PROVIDERS, ROUTER_PROVIDERS],
  styles: [`
      .router-link-active {
          font-weight: bold;
          border-bottom: 2px #d9534f solid;
      }
  `],
  templateUrl: 'app/app.component.html'
})

Now reload the browser and click on any link or navigate to a task timer from the tasks table, keeping an eye on the visual state of the top nav bar. The active link will be properly enhanced with the styling we defined. If you inspect the code, you will see the router-link-active class rendered on the active link each time.

Handling route parameters

We have configured pretty basic paths in our routes so far, but what if we want to build dynamic paths with support for parameters or values created at runtime? Creating (and navigating to) URLs that load specific items from our data stores is a common action we need to confront on a daily basis. For instance, we might need to provide a master-detail browsing functionality, so each generated URL living in the master page contains the identifiers required to load each item once the user reaches the detail page.

We are basically tackling a double trouble here: creating URLs with dynamic parameters at runtime and parsing the value of such parameters. No problem, the Angular router has got our back and we will see how using a real example.

Passing dynamic parameters in our routes

We updated the tasks list to display a button leading to the timer component page when clicked. But we just load the timer component with no context whatsoever of what task we are supposed to work on once we get there. Let’s extend the component to display the task we picked prior to jumping to this page.

First, let’s get back to the tasks list component template and update the signature of the button that triggers the navigation to the timer component in order to include the index of the task item corresponding to that loop iteration:

app/tasks/tasks.component.html

...
<button type="button"
    class="btn btn-default btn-xs"
    *ngIf="task.queued"
    (click)="workOn(i)">
    <i class="glyphicon glyphicon-expand"></i> Start
  </button>
...

Remember that such an index was generated at every iteration of the NgFor directive that rendered the table rows. Now that the call incorporates the index in its signature, we just need to modify the payload of the navigate method:

workOn(index: number): void {
  this.router.navigate(['Timer', { id: index }]);
}

If this had been a routerLink directive, the parameters would have been defined in the same way: a hash object following the path name string (or strings, as we will see while tapping into the child routers) inside the array. This is the way parameters are added to the generated link. However, if we click on any button now, we will see that the dynamic ID values are appended as query string parameters. While this might suffice in some scenarios, we are after a more elegant workaround for this. So, let’s update our route definition to include the parameter in the path. Go back to our top root component and update the route inside the RouteConfig decorator as follows:

app/app.component.ts

...
}, {
  path: 'timer/:id',
  name: 'TimerComponent',
  component: TimerComponent
}
...

Refresh the application, schedule the last task on the table, and click on the Start button. You will see how the browser loads the Timer component under a URL like /timer/3.

Each path can contain as many tokens prefixed by a colon as required. These tokens will be translated to the actual values when we act on a routerLink directive or execute the navigate method of the Router class by passing a hash of the key/value pairs, matching each token with its corresponding key. So, in a nutshell, we can define route paths as follows:

{
  path: '/products/:category/:id',
  name: 'ProductsByCategoryComponent',
  component: ProductsByCategoryComponent 
}

Then, we can execute any given route such as the one depicted earlier as follows:

<a [routerLink]="['ProductsByCategoryComponent', { 
  category: 'toys',
  id: 452 
}]">See Toy</a>

The same applies to the routes called imperatively:

router.navigate(['ProductsByCategoryComponent', {
  category: 'toys',
  id: 452 
}]);

Parsing route parameters with the RouteParams service

Great! Now, we are passing the index of the task item we want to work on loading the timer, but how do we parse that parameter from the URL? The Angular router provides a convenient injectable type (already included in ROUTER_PROVIDERS) named RouteParams that we can use from the components handled by the router to fetch the parameters defined in the route definition path.

Open our timer component and import it with the following import statement. Also, let’s inject the TaskService provider, so we can retrieve information from the task item requested:

app/timer/timer-widget.component.ts

import { Component, OnInit } from '@angular/core';
import { SettingsService, TaskService } from '../shared/shared';
import { RouteParams } from '@angular/router-deprecated';
...

We need to alter the component’s definition in order to assign the TaskService as an annotated dependency for this component, so the injector can properly perform the provider lookup.

Note

The new Release Candidate router has deprecated the RouteParams class, favoring the new RouteSegments class, which exposes more and more useful methods and helpers. Please refer to the official documentation for broader insights on its API.

We will also leverage this action to insert the interpolated title corresponding to the requested task in the component template:

app/timer/timer-widget.component.ts

...
@Component({
  selector: 'pomodoro-timer-widget',
  template: `
    <div class="text-center">
      <img src="/app/shared/assets/img/pomodoro.png">
      <h3><small>{{ taskName }}</small></h3>
      <h1> {{ minutes }}:{{ seconds  | number: '2.0' }} </h1>
      <p>
        <button (click)="togglePause()" class="btn btn-danger">
        {{ buttonLabelKey | i18nSelect: buttonLabelsMap }}
        </button>
      </p>
    </div>`
})
...

The taskName variable is the placeholder we will be using to interpolate the name of the task. With all this in place, let’s update our constructor to bring both the RouteParams type and the TaskService classes to the game as private class members injected from the constructor:

app/timer/timer-widget.component.ts

...
constructor(
  private settingsService: SettingsService,
  private routeParams: RouteParams,
  private taskService: TaskService) {
    this.buttonLabelsMap = settingsService.labelsMap.timer;
}
...

With these types now available in our class, we can leverage the ngOnInit hook to fetch the task details of the item in the tasks array corresponding to the index passed as a parameter. Waiting for the OnInit stage is not easy, since we will find issues when trying to access the properties contained in routeParams before that stage:

app/timer/timer-widget.component.ts

ngOnInit(): void {
  this.resetPomodoro();
  setInterval(() => this.tick(), 1000);

  let taskIndex = parseInt(this.routeParams.get('id'));
  if (!isNaN(taskIndex)) {
    this.taskName = this.taskService.taskStore[taskIndex].name;
  }
}

How do we fetch the value from that id parameter? The RouteParams object exposes a get(param: string) method we can use to address parameters by name. In our example, we retrieved the value of the id parameter by executing the routeParams.get('id') command in the ngOnInit() hook method. Basically, this is how we get parameter values from our routes. First, we grab an instance of the RouteParams class through the component injector and then we retrieve values by executing its getter function, which will expect a string parameter with the name of the token corresponding to the parameter we need.

Defining child routers

As our applications scale, the idea of bundling all the route definitions in a centralized location (for example, the root component) does not seem like a good approach. The more routes we define there, the harder it will be to maintain the application, let alone the tight coupling we generate between our components (that are meant to be as much reusable and application-agnostic as possible) and the application itself.

This is why it is generally a good practice to split and wrap the route definitions that apply to a specific feature around a router configuration defined on a specific component per feature level, usually the root component that wraps that feature context. The Angular team had this idea in mind when the Router library was designed and thus implemented support to extend a route with children routes while keeping the parent route fully agnostic of what routes are defined above its layer of functionality.

Let’s see all this through an actual example. We updated our timer recently to display the name of the task we wanted to work on after selecting it from the table. However, what if we want to keep providing a standalone timer not bound to any specific task? This way, we can leverage the countdown functionality for any impromptu task without having to create it beforehand.

So, we will want to give access to the timer in two flavors:

  • timer: This will load the timer as it is without pointing to any specific task
  • timer/task/{id}: This will load the timer specifying a task name, where {id} is the index of the task we want to load from the overall tasks array.

We will begin by updating the main root component, now turned into a router component, to turn the /timer/:id path into a path pointing to a child router component. Open the component and replace the route definition pointing to the timer component using the following definition:

app/app.component.ts

{ 
  path: 'timer/...',
  name: 'TimerComponent',
  component: TimerComponent 
}

That’s it. The ellipsis right next to the route path informs the Angular Router that it should expect route definitions nested within that component. The problem here is that a component should not route to itself, since it cannot instantiate itself inside its own RouterOutlet directive. This is why we need to proxy the timer component with a router component for this example. So, let’s create a routing component for our timer inside its own folder for simplicity sake. A routing component is, by definition, a component with no implementation other that to serve as a component dispatcher depending on routes. Their implementation, if any, is generally pretty limited, and it basically entails the RouteConfig decorator containing the routes delivered by that feature context and the RouterOutlet present in its template. Routing components are indeed a good way to decouple routing functionalities from the specific implementation of each component in the context of that feature, ensuring full reusability of its non-routing components of that feature.

Open the timer feature folder and create a file for our timer routing component with the following implementation:

app/timer/timer.component.ts

import { Component } from '@angular/core';
import { RouteConfig, ROUTER_DIRECTIVES } from '@angular/router-deprecated';
import TimerWidgetComponent from './timer-widget.component';

@Component({
  selector: 'pomodoro-timer',
  directives: [ROUTER_DIRECTIVES],
  template: '<router-outlet></router-outlet>'
})
@RouteConfig([
  { path: '/', name: 'GenericTimer',
    component: TimerWidgetComponent,
    useAsDefault: true }, { 
    path: '/task/:id',
    name: 'TaskTimer',
    component: TimerWidgetComponent 
  }
])
export default class TimerComponent {}

As we can see, we have created a component class with no implementation other than dispatching routes to the TimerWidgetComponent component itself. The RouteConfig type allows us to create a proper decorator containing route definitions and ROUTER_DIRECTIVES will allow us to bind the RouterOutlet directive in the component template.

Please pay attention to the new useAsDefault Boolean property in the first route definition. This informs the router that if no matching paths are found, the Router class should load this route by default.

Note

At the time of this writing, the new Release Candidate Router still does not implement the useAsDefault property, but it is planned to be implemented by its final version. Please refer to the official documentation for further details.

It is important to note that any component acting as a router component can have its own implementation living in parallel to the RouterOutlet directive. Just like we did with AppComponent, we can provide additional functionalities to our component other than the mere routing features.

All right, we have a component now that can redirect users to our timer component in two flavors, but how do we link to it?

Linking to child routes

There is no difference whatsoever in linking to a child router and linking to any given route managed by a top router, except for the fact that we will populate the route names array with the names of the child routes we want to link as well. Open the PomodoroTaskList component and refactor the workOn() method to look like this:

  workOn(index: number): void {
    this.router.navigate(['TimerComponent', 'TaskTimer', {    id: index }]);
  }

Here, we are telling Angular to link to the route named TimerComponent (hence the importance of naming our routes after each target component’s name). Since this is a parent route (remember the ellipsis) configured at our top root router, we need to provide the name of the child route to load from within the routes configured at the child router level, TaskTimer in this case. Obviously, we will compound up the route with the ID information required for loading the task requested. Click on any task and see how the timer is loaded, displaying the task name we wish to work on.

This implementation approach gives the component the chance to be displayed with or without Task ID information. This way, we can keep on browsing to the timer functionality, either from the top nav link or by clicking the Start button at the tasks table.

Just remember we configured the main route definition with the useAsDefault property set as true, remember? This means that anything pointing to the timer route will degrade gracefully to this last route definition once we reach the child route domain.

The Router lifecycle hooks

Just like components go through a set of different phases during their lifetime, a routing operation goes through different lifecycle stages. Each one is accessible from a different lifecycle hook which, just like components, can be handled by implementing a specific interface in the component subject of the routing action. The only exception for this is the earliest hook in the routing lifecycle, the CanActivate hook, which takes the shape of a decorator annotation, since it is meant to be called before the component is even instantiated.

The CanActivate hook

The CanActivate hook, presented as a decorator annotating the component, is checked by the Router right before it is instantiated. It will need its setup to be configured with a function that is intended to return a Boolean value (or a Promise-typed Boolean value) indicating whether the component should be finally activated or not:

@CanActivate((next, prev) => boolean | Promise<boolean>)

The @CanActivate decorator is therefore a function that expects another function as an argument, expecting the latter two ComponentInstruction objects as parameters in its signature: the first argument represents the route we want to navigate to and the second argument represents the route we are coming from. These objects expose useful properties about the route we come from and the component we aim to instantiate: path, parameters, component type, and so on.

Note

This hook represents a good point in the overall component’s lifecycle to implement behaviors such as session validation, allowing us to protect areas of our application. Unfortunately the CanActivate hook does not natively support dependency injection, which makes harder to introduce advanced business logic prior to activate a route. The next chapters will describe workarounds for scenarios such as user authentication.

In the following example, we password-protect the form so that it won’t be instantiated should the user enters the wrong passphrase. First, open the TaskEditorComponent module file and import all that we will need for our first experiment, along with all the symbols required for implementing the interfaces for the routing lifecycle hooks we will see throughout this chapter. Then, proceed to apply the CanActivate decorator to the component class:

app/tasks/task-editor.component.ts

import { Component } from '@angular/core';
import {
  ROUTER_DIRECTIVES,
  CanActivate,
  ComponentInstruction,
  OnActivate,
  CanDeactivate,
  OnDeactivate } from '@angular/router-deprecated';

@Component({
  selector: 'pomodoro-tasks-editor',
  directives: [ROUTER_DIRECTIVES],
  templateUrl: 'app/tasks/task-editor.component.html'
})
@CanActivate((
  next: ComponentInstruction,
  prev: ComponentInstruction): boolean => {
    let passPhrase = prompt('Say the magic words');
    return (passPhrase === 'open sesame');
  }
)
export default class TaskEditorComponent {
...

As you can see, we are populating the @CanActivate decorator with an arrow function declaring two ComponentInstruction typed arguments (which are not actually required for our example, although they have been included here for instructional purposes). The arrow function returns a Boolean value depending on whether the user types the correct case-sensitive passphrase. We would advise you to inspect the next and previous parameters in the console to get yourself more acquainted with the information these two useful objects provide.

By the way, did you notice that we declared the ROUTER_DIRECTIVES token in the directives property? The routing directives are not required for our overview of the different routing lifecycle hooks, but now we are tweaking this component and will keep updating it to test drive the different lifecycle hooks. Let’s introduce a convenient back button, leveraging the Cancel button already present in the component template:

app/tasks/task-editor.component.html

<form class="container">
...
  <p>
    <input type="submit" class="btn btn-success" value="Save">
    <a [routerLink]="['TasksComponent']" class="btn btn-danger">
      Cancel
    </a>
  </p>
</form>

The OnActivate Hook

The OnActivate hook allows us to perform custom actions once the route navigation to the component has been successfully accomplished. We can easily handle it by implementing a simple interface. These custom actions can even encompass asynchronous operations, in which case, we just need to return a Promise from the interface function. If so, the route will only change once the promised has been resolved.

Let’s see an actual example where we will introduce a new functionality by changing the title of our form page. To do so, we will keep working on the TaskEditorComponent module to bring support for the OnActivate hook interface and the Title class whose API exposes utility methods (https://angular.io/docs/ts/latest/api/platform/browser/Title-class.html) to set or get the page title while executing applications in web browsers. Let’s import the Title symbol and declare it as a provider in the component to make it available for the injector (you can also inject it earlier at the top root component should you wish to interact with this object in other components):

app/tasks/task-editor.component.ts

import { Component } from '@angular/core';
import {
  ROUTER_DIRECTIVES,
  CanActivate,
  ComponentInstruction,
  OnActivate,
  CanDeactivate,
  OnDeactivate } from '@angular/router-deprecated';
import { Title } from '@angular/platform-browser';

@Component({
  selector: 'pomodoro-tasks-editor',
  directives: [ROUTER_DIRECTIVES],
  providers: [Title],
  templateUrl: 'app/tasks/task-editor.component.html'
})

Now, let’s implement the interface with its required routerOnActivate method. As a rule of thumb, all router lifecycle hooks are named after the hook name prefixed by router in lowercase:

app/tasks/task-editor.component.ts

export default class TaskEditorComponent implements OnActivate {

  constructor(private title: Title) {}
  routerOnActivate(
    next: ComponentInstruction,
    prev: ComponentInstruction): void {
      this.title.setTitle('Welcome to the Task Form!');
  }

}

Please note how we inject the Title type in the class through its constructor and how we later on execute it when the router activates the component once the navigation has finished. Save your changes and reload the application. You will notice how the browser title changes once we successfully access the component after passing the CanActivate and OnActivate stages.

The CanDeactivate and OnDeactivate hooks

Just like we can filter if the component we are navigating to can be activated, we can apply the same logic when the user is about to leave the current component towards another one located elsewhere in our application. As we saw in case of the CanActivate hook, we must return a Boolean or a Promise resolving to a Boolean in order to allow the navigation to proceed or not. When the CanDeactivate hook returns or is resolved to true, the OnDeactivate hook is executed just like the OnActivate hook after the navigation is accomplished.

In the following example, we will intercept the deactivation stages of the routing lifecycle to first interrogate the user whether he wants to leave the component page or not, and then we will restore the page title if so. For both operations, we will need to implement the CanDeactivate and OnDeactivate interfaces in our component. The code is as follows:

export default class TaskEditorComponent implements OnActivate, CanDeactivate, OnDeactivate {
  constructor(private title: Title) {}

  routerOnActivate(): void {
    this.title.setTitle('Welcome to the Task Form!');
  }

  routerCanDeactivate(): Promise<boolean> | boolean {
    return confirm('Are you sure you want to leave?');
  }
  routerOnDeactivate(): void {
    this.title.setTitle('My Angular 2 Pomodoro Timer');
  }
}

Please note that we have removed the (next: ComponentInstruction, prev: ComponentInstruction) arguments from our hook implementations because they were of no use for these examples, but you can access a lot of interesting information through them in your own custom implementations.

Same as the CanActivate hook, the CanDeactivate hook must return a Boolean value or a Promise resolved to a Boolean value in order to allow the routing flow to continue.

The CanReuse and OnReuse hooks

Last but not least, we can reuse the same instance of a component while browsing from one component to another component of the same type. This way, we can skip the process of destroying and instantiating a new component, saving resources on the go.

This requires us to ensure that the information contained in the parameters and stuff is properly handled to refresh the component UI or logic if required in the new incarnation of the same component.

The CanReuse hook is responsible for all this, and it tells the Router whether the component should be freshly instantiated or whether we should reuse the component in the future calls of the same route. The CanReuse interface method should return a Boolean value or a Promise resolving to a Boolean value (just like the CanActivate or CanDeactivate hooks do), which informs the Router if it should reuse this component in the next call. If the CanReuse implementation throws an error or is rejected from within the Promise, the navigation will be cancelled.

On the other hand, if the CanReuse interface returns or resolves to true, the OnReuse hook will be executed instead of the OnActivate hook should the latter exist already in the component. Therefore, use only one of these two whenever you implement this functionality.

Let’s see all these in an actual example. When we schedule a task in the task list table and proceed to its timer, we can jump at any time to the generic timer accessible from the top nav bar, thereby loading another timer that is not bound to any task whatsoever. By doing so, we are actually jumping from one instance of the TimerWidgetComponent component to another TimerWidgetComponent component and the Angular router will destroy and instantiate the same component again. We can save the Router from doing so by configuring the component to be reused. Open the TimerWidgetComponent module and import the interfaces we will need for this, along with the symbols we were importing already from the Router library:

app/timer/timer-widget.component.ts

import { Component, OnInit } from '@angular/core';
import { SettingsService, TaskService } from '../shared/shared';
import { RouteParams, CanReuse, OnReuse } from '@angular/router-deprecated';

Now, implement the CanReuse and OnReuse interfaces in the class by adding them to the implements declaration and then proceed to attach the following required interface methods to the class body:

routerCanReuse(): boolean {
  return true;
}

routerOnReuse(next: ComponentInstruction): void {
  // No implementation yet
}

Now go to the tasks table, schedule any task, and go to its timer. Click on the Timer link in the top nav bar. You will see how the URL changes in the browser but nothing happens. We are reusing the same component as it is. While this saves memory resources, we need a fresh timer when performing this action. So, let’s update the OnReuse method accordingly, resetting the taskName value and the Pomodoro itself:

routerOnReuse(): void {
  this.taskName = null;
  this.isPaused = false;
  this.resetPomodoro();
}

Reproduce now the same navigation journey and see what happens. Voila! New behavior but same old component.

Advanced tips and tricks

Although we have discussed all that you need to start building complex applications with routing functionalities, there is still a big collection of advanced techniques you can use to take our application to the next level. In the upcoming sections, we will highlight just a few.

Redirecting to other routes

Besides the route definition types we have seen already, there is another RouteDefinition type named Redirect that is not bound to any named Route or component, but will rather redirect to another existing Route.

So far, we were serving the task list table from the root path, but what if we want to deliver this table from a path named /tasks while ensuring that all the links pointing to the root are properly handled? Let’s create a redirect route then. We will update the top root router configuration with a new path for the existing home path and a redirect path to it from the new home URL. The code is as follows:

app/app.component.ts

...
@RouteConfig([{ 
  path: '', 
  name: 'Home', 
  redirectTo: ['TasksComponent']
}, { 
  path: 'tasks',
  name: 'TasksComponent',
  component: TasksComponent,
  useAsDefault: true
}, { 
  path: 'tasks/editor',
  name: 'TaskEditorComponent',
  component: TaskEditorComponent
}, { 
  path: 'timer/...',
  name: 'TimerComponent',
  component: TimerComponent 
}])
export default class AppComponent {}

The new redirecting route just needs a string path property and a redirectTo property declaring the array of named routes we want to redirect all the requests to.

Note

At the time of this writing, the rotue definitions in the new Router still do not implement support for the redirectTo property. Please check the online documentation for a more up-to-date status on the subject.

Tweaking the base path

When we began working on our application routing, we defined the base href of our application at index.html, so the Angular router is able to locate any resource to load apart from the components themselves. We obviously configured the root / path, but what if, for some reason, we need to deploy our application with another base URL path while ensuring the views are still able to locate any required resource regardless of the URL they’re being served under? Or perhaps we do not have access to the HEAD tag in order to drop a <base href="/"> tag, because we are just building a redistributable component and do not know where this component will wind up later. Whatever the reason is, we can easily circumvent this issue by overriding the value of the APP_BASE_HREF token, which represents the base href to be used with our LocationStrategy of choice.

Try it for yourself. Open the main.ts file where we bootstrap the application, import the required tokens, and override the value of the aforementioned base href application variable by a custom value:

app/main.ts

import 'rxjs/add/operator/map';
import { bootstrap } from '@angular/platform-browser-dynamic';
import AppComponent from './app.component';
import { provide } from '@angular/core';
import { APP_BASE_HREF } from '@angular/common';

bootstrap(AppComponent, [provide(APP_BASE_HREF, { 
  useValue: '/my-apps/pomodoro-app' 
})]);

Reload the app and see the resulting URL in your browsers.

Finetuning our generated URLs with location strategies

As you have seen, whenever the browser navigates to a path by command of a routerLink or as a result of the execution of the navigate method of the Router object, the URL showing up in the browser’s location bar conforms to the standardized URLs we are used to seeing, but it is in fact a local URL. No call to the server is ever made. The fact that the URL shows off a natural structure is because of the pushState method of the HTML5 history API that is executed under the folds and allows the navigation to add and modify the browser history in a transparent fashion.

There are two main providers, both inherited from the LocationStrategy type, for representing and parsing state from the browser’s URL:

  • PathLocationStrategy: This is the strategy used by default by the location service, honoring the HTML5 pushState mode, yielding clean URLs with no hash-banged fragments (example.com/foo/bar/baz).
  • HashLocationStrategy: This strategy makes use of hash fragments to represent state in the browser URL (example.com/#foo/bar/baz).

Regardless of the strategy chosen by default by the Location service, you can fallback to the old hashbang-based navigation by picking the HashLocationStrategy as the LocationStrategy type of choice.

In order to do so, go to main.ts and tell the Angular global injector that, from now on, any time the injector requires binding the LocationStrategy type for representing or parsing state (which internally picks PathLocationStrategy), it should use not the default type, but use HashLocationStrategy instead.

It just takes to override a default provider injection:

app/main.ts

import 'rxjs/add/operator/map';
import { bootstrap } from '@angular/platform-browser-dynamic';
import AppComponent from './app.component';
import { provide } from '@angular/core';
import {
  LocationStrategy,
  HashLocationStrategy
} from '@angular/common';

bootstrap(AppComponent, [provide(LocationStrategy, {
  useClass: HashLocationStrategy
})]);

Save your changes and reload the application, requesting a new route. You’ll see the resulting URL in the browser.

Note

Please note that any location-related token in the example is not imported from ‘@angular/router-deprecated’ but from ‘@angular/common’ instead.

Loading components asynchronously with AsyncRoutes

As you have seen in this chapter, each route definition needs to be configured with a component property that will inform the router about what to load into the router outlet when the browsers reach that URL. However, we might sometimes find ourselves in a scenario where this component needs to be fetched at runtime or is just the by-product of an asynchronous operation. In these cases, we need to apply a different strategy to pick up the component we need. Here’s where a new type of router definition named AsyncRoute comes to the rescue. This specific kind of route exposes the same properties of the already familiar RouteDefinition class we have been using along this chapter. It replaces the component property with a loader property that will be linked to a Promise that resolves asynchronously to a component loaded on demand.

Let’s see this with an actual example. In order to keep things simple, we will not be importing the component we want to load at runtime, rather we will return it from an asynchronous operation. Open the top root component module and replace the route pointing to TimerComponent with this async route definition:

app/app.component.ts

...

@RouteConfig([{ 
    path: '',
    name: 'Home',
    redirectTo: ['TasksComponent'] 
}, { 
    path: 'tasks',
    name: 'TasksComponent',
    component: TasksComponent,
    useAsDefault: true
}, { 
    path: 'tasks/editor',
    name: 'TaskEditorComponent',
    component: TaskEditorComponent
}, { 
    path: '/timer/...', 
    name: 'TimerComponent', 
    loader: () => {
      return new Promise(resolve => {
        setTimeout(() => resolve(TimerComponent), 1000);
      });
    } 
  } 
])
export default class AppComponent {}

The next time we attempt to load any route belonging to the timer branch (either the generic timer accessible from the nav bar or any task-specific timer), we will have to wait until the Promise resolves to the component we need. Obviously, the goal of this example is not to teach how to make things load slower, but to provide a simple example of loading a component asynchronously.

We have now uncovered the power of the Angular router and we hope you have enjoyed this journey into the intricacies of this library. One of the things that definitely shines in the Router module is the vast number of options and scenarios we can cover with such a simple but powerful implementation.

In this chapter, we discussed how to install and provide support for routing in our applications and how to turn any given component into a routing component by decorating it with the router configuration decorator and placing a router outlet in its template, even spreading routers downward in the components tree. We also saw how to define regular routes and some other advanced types such as redirect or async routes. The routing lifecycle has no secrets for us anymore and harnessing its power will open the door to deliver advanced functionalities in our applications with no effort. The possibilities are endless and, most importantly, routing contributes to delivering a better browsing experience.

 

Save

Deven Rathore

Deven is an Entrepreneur, and Full-stack developer, Constantly learning and experiencing new things. He currently runs CodeSource.io and Dunebook.com.

Published by
Deven Rathore

Recent Posts

Flutter App Development: Essential Things You Should Know

Divided by mobile operating systems, companies have to develop their apps twice and roll them…

6 hours ago

7 Tips To Build Responsive & Dynamic Drupal Website For Your Business

For optimal user experience, consumers want responsive websites that are easy to use, multi-device friendly,…

2 days ago

OpenCart vs Magento: What You Should Choose in 2021

Users all over the world are faced with the problem of choosing a platform for…

1 week ago

Top 20 Android Open Source Projects

Reading codes and contributing to open source has been proven to be one of the…

3 weeks ago

Top 5 tools to proofread and edit essays with the help of software

Poor grammar and incorrect spelling can significantly lower the value of any literary work. Going…

5 hours ago

The Best 5 Career Advice for Web Design Students

Are you thinking of a career in web design but not sure where to start? The…

4 weeks ago