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
- Step 1: Create a Resolver Class
- Step 2: Add a Resolve Guard to the Route
- Step 3: Get the Resolved Data from the Component's Activated Route
- Avoiding Redundant API Calls
- Multiple Resolves on the Same Route
- Passing Route Params to the Resolver
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:
- Eliminating confusing clutter in your component’s template and code.
- 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
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):
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 toNewsResolver
- 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 aGET
in the code above, you are absolutely right; In a real app, this would be aGET
request. The examples here take advantage ofhttp://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:
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:
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:
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.
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.:
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:
Then additional resolve data can be accessed from the route snapshot just like a single resolve:
• 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:
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.