Articles | Callibrity IT Consulting | Custom Software Development

Angular Route Resolves in 10 Minutes

Written by Victor Chtelmakh | 1/8/17

Updated on 07/13/2018: Extensive overhaul of the code to use newer conventions. Upgraded Angular and RxJS to the latest available versions, moved examples to StackBlitz.

The goal of this post is to demonstrate how Angular route resolves work in 10 minutes, along with providing sample code on Stackblitz. If you are already familiar with route resolves from AngularJS, I recommend skipping the intro and jumping straight to the sample app section.

Table of Contents

Intro
Sample App

Notes & Tips

Closing Notes

 

Intro

Route resolves are nothing more than a way to pre-fetch the data a component needs before it is initialized. Usually this data comes from an API. Let’s say you have a component whose only role is to display a chart of daily sales for the month; There is no point in rendering the view or loading this component before the sales data is available. In fact, many charting libraries will throw an error if you try to initialize a chart before supplying it with data (and so will *ngFor). Of course, you can easily work around this problem by hiding the html with an *ngIf or temporarily supplying an empty array until the necessary data is loaded. However, while you can get by without resolves, implementing them helps make code more readable and maintenable by:

  1. Eliminating confusing clutter in your component’s template and code.
  2. Clarifying your intent by showing which data must be pre-fetched.

Despite these benefits, many sites avoid using resolves in favor of displaying most of the component and showing a spinner in the sections for which data is still being loaded. In some cases, this approach is desirable from a UX point of view, and a resolve should not be used. Use your discretion.

 

Sample App

Open Sample App

As you can see, our starting point is a very basic app with two routes - “Home” and “News”. You navigate between the routes by clicking on the corresponding tab. We are going to add a resolve to pre-fetch news from an API before the News component loads. This will take three steps:


Step 1: Create a resolver class which makes an Http call to pre-fetch the data we need.

Create a new Typescript file inside the app folder and name it:

news-resolver.service.ts

Then copy and paste the following code into your new file (explained below):

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Resolve } from '@angular/router';
import { Observable } from 'rxjs';
import { catchError, map } from 'rxjs/operators';

@Injectable()
export class NewsResolver implements Resolve<any> {

  constructor(private http: HttpClient) { }

  resolve(): Observable<any> {
    let newsUrl = 'https://httpbin.org/post';
    let news = 'The sky is blue'; //Mock data to be returned by test API

    return this.http.post(newsUrl, news).pipe(
      map( (dataFromApi) => dataFromApi ),
      catchError( (err) => Observable.throw(err.json().error) )
    )
  }
}

Let’s break down what we did in the code above:

  • Added ES6 import statements to bring in the necessary modules.

  • Created a new TypeScript NewsResolver class.

  • Added the Resolve interface to our class - this is OPTIONAL, but any class we plan to use as a resolver must implement a resolve method, so it’s a good convention.

  • Added a resolve() method to NewsResolver - this is the method responsible for returning the data we need. Naming the method that returns data for a resolve guard “resolve” is NOT optional - the resolver would not work if the method was named anything else.

  • If you noticed that a POST request is incorrectly being used instead of a GET in the code above, you are absolutely right; In a real app, this would be a GET request. The examples here take advantage of http://httpbin.org/, which is a site providing test API endpoints.

Before moving on, we must include the resolver class we just created in our routing module. Navigate to app-routing.module.ts and add NewsResolver to the providers array. Or, if you are just starting to work with Angular 2, simply replace the contents of app-routing.module.ts with the code below - changes are marked with notes:

import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { RouterModule, Routes } from '@angular/router';

import { AppComponent } from './app.component';
import { HomeComponent } from './home.component';
import { NewsComponent } from './news.component';
import { NewsResolver } from './news-resolver.service'; //Import our new resolver

const routes: Routes = [
  { path: '', redirectTo: '/home', pathMatch: 'full' },
  { path: 'home', component: HomeComponent },
  { path: 'news', component: NewsComponent }
];

@NgModule({
  imports: [RouterModule.forRoot(routes), HttpClientModule],
  exports: [RouterModule],
  providers: [NewsResolver] //Add NewsResolver to the providers array
})
export class AppRoutingModule {}

With this done, we have now defined the resolver class. In the following steps, we will add it to our route.

Code After Adding the Resolver Class


Step 2: Add a Resolve Guard to the route.

In app-routing.module.ts, change the following line of code { path: 'news', component: NewsComponent } to:

{ path: 'news',
  component: NewsComponent,
  resolve: {
    news: NewsResolver
  }
}

All we did here was add the resolve guard we just defined to our news route. This tells Angular that we must wait for NewsResolver’s resolve() method to return data before we display the NewsComponent.

It’s important to point out that news in the line news: NewsResolver of code is what I chose to name whatever data is returned by the resolver. You can name it anything you want.

As a bit of a tangential note - if you are unfamiliar with Angular route guards in general, and would like to know more, the documentation is here. Going into details about guards is outside of the scope of this post, but you should know that there are other guards besides resolve available, which is why I brought up the term.

Code With The Resolve Guard Added


Step 3: Get the resolved data from the component’s activated route.

Now that we have a resolver class and we have added it to our route, data is being pre-fetched. The final step is to access the pre-fetched data in our NewsComponent located in app/news.component.ts. Navigate to that file and add the following ES6 module:

import { ActivatedRoute } from '@angular/router';

then provide ActivatedRoute in the News component’s constructor by adding the following code at the top of the NewsComponent class definition:

constructor(private route: ActivatedRoute) {}

As you know, or might have guessed, ActivatedRoute allows us to access information about the route which is currently active, such as the url of the route, query parameters, etc. What we care about here is the news data which gets loaded into the ActivatedRoute from the resolve guard. To get the resolved news data from the route, add a property to hold the news data:

public news: any;

and then get the data from the activated route when the NewsComponent is initialized:

  ngOnInit(): void {
    this.news = this.route.snapshot.data['news'];
  }

That’s virtually it. Change the news component template to display the news from the route resolve by removing the current contents:

<div>This is just a placeholder for now. News will go here. </div>

and replacing them with:

You should now be greeted with the latest news, “The sky is blue”, when you click the News tab.

Finished Code

 

Notes & Tips

 

• Resolves can cause redundant API calls: A resolve gets data every time a component is loaded. This often results in unnecessary API calls which adversely affect performance. If your resolve gets data that doesn’t change frequently, consider writing the data returned by the resolve to a property in the resolver class and simply returning that property if it has already been set. For instance, in our example, we would add an initially undefined news property like so: public news: any = undefined; in our news resolver. Then, in the resolve() method, check if the news property is already set and return its value without making an API call if it is, i.e.:

  resolve(): Observable<any> {
    if (this.news) {
      return this.getSavedNews();
    } else {
      return this.getNewsFromApi()
    }
  }

Complete code example below. If the observable syntax seems a bit unusual to you, it’s because I am using RxJS 6. There is a good tutorial on the changes recently introduced to RxJS here if you need a refresher.

Return Saved Data, if Already Fetched from API

Naturally, you could go further and set a time period during which the data is valid by not only saving the data, but adding another timestamp property and making an API call if the the data is older than x.


• Just like in AngularJS, you can use multiple resolves on the same route. The resolve calls are made in parallel and the component will load only after all of the calls return data. To use multiple resolves, simply add them to the route:

  { path: 'news',
    component: NewsComponent,
    resolve: {
      news: NewsResolver, // first resolve
      alternativeNews: AlternativeNewsResolver // second resolve
    }
  }

Then additional resolve data can be accessed from the route snapshot just like a single resolve:

  ngOnInit(): void {
    this.news = this.route.snapshot.data['news'];
    this.alternativeNews = this.route.snapshot.data['alternativeNews'];
  }

Code With Multiple Resolves


• The resolver has access to route params. Let’s say you have a component that displays a list of news story titles. When a story is clicked, another component, that shows the full article, is opened. Before loading that component, we need to pre-fetch the content of the news story whose title was clicked - this can be done in the resolve method. The resolver class has access to the ActivatedRoute, so we can get the id of story that was clicked:

  resolve(route: ActivatedRouteSnapshot) {
    let id: any = route.params['id']);
    return this.getNewsStory(id);
  }

This is fairly self-explanatory. For an example, check out the new src/news-story-resolver.service.ts file in the link below. The links to the new component were added in the News tab (news.component.ts).

Code With Parameterized Resolve

 

Closing Notes

This was just a brief overview of how to use route resolves. The official documentation is getting better every day, but I always find it helpful to have working code examples - hopefully these help you as well.