5.8: Adding Route Resolvers

As our page loads and a component's ngOnInit() method fires - triggering a call to our API - there may be a delay between the view rendering and the data initially appearing. The data will show up, but there may be errors in the console and the user experience isn't the best.

One solution is to use spinning loading icons.

We can also use route resolvers to load the data before the layout is rendered.

This article from alligator.io explains route resolvers much better than the official docs which don't offer much in the way of explanation.

Basically, think of them as being in the same 'family' as guards. They are run when a user navigates to a route - a guard checks whether a user is allowed to access to that route and a resolver will preemptively load the data.

Add MemberDetailResolver

Add a new folder in the /src/app folder called resolvers.

Inside of that folder, add a new file called member-detail.resolver.ts

import { Resolve, Router, ActivatedRouteSnapshot } from '@angular/router';
import { User } from '../models/User';
import { Injectable } from '@angular/core';
import { UserService } from '../services/user.service';
import { Observable } from 'rxjs/Observable';

@Injectable()
export class MemberDetailResolver implements Resolve<User> {

    constructor(
        private _userService: UserService,
        private _router: Router) {}

    resolve(route: ActivatedRouteSnapshot): Observable<User> {
        return this._userService.getUser(route.params['id']);
    }
}

Add the resolver to the providers array in app.module.ts:

import { MemberDetailResolver } from './resolvers/member-detail.resolver';
//  ...
providers: [
    AuthService,
    UserService,
    AuthGuard,
    MemberDetailResolver            //  <--- Added
  ],
//  ...

Next, we need to add it to our routes inside of routes.ts.

//  ...
children: [
            { path: 'members', component: MemberListComponent },
            { path: 'members/:id', component: MemberDetailComponent, resolve: { user: MemberDetailResolver }},       //  <--- Added
            { path: 'messages', component: MessagesComponent },
            { path: 'followers', component: FollowersComponent },
        ]
//  ...

Using the Resolver in the Component

In member-detail.component.ts, change ngOnInit() to subscribe to data from the resolver:

ngOnInit() {
    this._route.data.subscribe(data => {
        this.user = data['user'];
    });
}

We can also delete the loadUser() method now.

Adding MemberListResolver

Let's go ahead and create one for the MemberListComponent as well. It will almost all be the same code. You can copy the member-detail.resolver.ts file and rename it to member-list.resolver.ts. Make the following changes:

export class MemberListResolver implements Resolve<User[]> {

    constructor(
        private _userService: UserService,
        private _router: Router) {}

    resolve(route: ActivatedRouteSnapshot): Observable<User[]> {
        return this._userService.getUsers();
    }
}

Add it to the providers array as well

providers: [
    AuthService,
    UserService,
    AuthGuard,
    MemberDetailResolver,
    MemberListResolver
  ],

And, also to the routes:

children: [
    { path: 'members', component: MemberListComponent, resolve: {users: MemberListResolver }},
    { path: 'members/:id', component: MemberDetailComponent, resolve: { user: MemberDetailResolver }},
    { path: 'messages', component: MessagesComponent },
    { path: 'followers', component: FollowersComponent },
]

Update member-list.component.ts to use the resolver

constructor(
    private _userService: UserService,
    private _route: ActivatedRoute) { }

ngOnInit() {
    this._route.data.subscribe(data => {
        this.users = data['users'];
    });
}

Delete the loadUsers() method and ensure that the data still loads.

Last updated