Sticky Routes In Angular 2.3+

With RouteReuseStrategy

Beginning with Version 2.3 (and the corresponding Router Version 3.3), Angular will support sticky routes. Such routes preserve the current component's state when they are deactivated, so that it is still available when it is re-activated later. For this, the application can define its own Strategy for reusing components. This post shows how to use this new technique by an example that can be found here.

This post is covering the basics and I'm sure that Victor Savkin, the team-member behind the router, will write an in-depth post about this during the next days.

Overview

To illustrate the behavior of sticky routes, I'm using an example application with a route that allows to search for flights. One of the found flights can be selected:

Sample App

As this route's component stores its state within its own properties, this state is lost when the user deactivates the route by navigating to another one. When the user navigates back to to it, they will face an empty search result:

Empty Search Result

With sticky routes, the former state would have been reused and the user would see the last search result.

Implementing a RouteReuseStrategy

To get sticky routes, an application has to implement/ extend the abstract class RouteReuseStrategy:

// Taken from angular's source code at github:
export declare abstract class RouteReuseStrategy {
    /**
     * Determines if this route (and its subtree) should be detached to be reused later.
     */
    abstract shouldDetach(route: ActivatedRouteSnapshot): boolean;
    /**
     * Stores the detached route.
     */
    abstract store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void;
    /**
     * Determines if this route (and its subtree) should be reattached.
     */
    abstract shouldAttach(route: ActivatedRouteSnapshot): boolean;
    /**
     * Retrieves the previously stored route.
     */
    abstract retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle;
    /**
     * Determines if a route should be reused.
     */
    abstract shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean;
}

As the comments in the last listing state, a RouteReuseStrategy decides on whether the router should store the current route when deactivating it or whether the router should restore it when the user re-activates it. In addition to this, it is also responsible for storing and retrieving the routes in question.

The following listing shows a simple implementation of a RouteReuseStrategy that stores the routes in an map with the name handlers:

// This impl. bases upon one that can be found in the router's test cases.
export class CustomReuseStrategy implements RouteReuseStrategy {

    handlers: {[key: string]: DetachedRouteHandle} = {};

    shouldDetach(route: ActivatedRouteSnapshot): boolean {
        console.debug('CustomReuseStrategy:shouldDetach', route);
        return true;
    }

    store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
        console.debug('CustomReuseStrategy:store', route, handle);
        this.handlers[route.routeConfig.path] = handle;
    }

    shouldAttach(route: ActivatedRouteSnapshot): boolean {
        console.debug('CustomReuseStrategy:shouldAttach', route);
        return !!route.routeConfig && !!this.handlers[route.routeConfig.path];
    }

    retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
        console.debug('CustomReuseStrategy:retrieve', route);
        if (!route.routeConfig) return null;
        return this.handlers[route.routeConfig.path];
    }

    shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
        console.debug('CustomReuseStrategy:shouldReuseRoute', future, curr);
        return future.routeConfig === curr.routeConfig;
    }

}

Activating the custom RouteReuseStrategy

To activate a custom RouteReuseStrategy the application has to set up a provider for it:

@NgModule({
    [...],
    providers: [
        {provide: RouteReuseStrategy, useClass: CustomReuseStrategy}
    ]
)}
export class AppModule {
}

After this, the application should behave in the above described desired way. As mentioned, the implementation shown here can be found in my github account.