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.