Using resolvers in Angular

Using resolvers in Angular

Let's explain how to use a resolver with a basic Angular application.

What's the matter?

Well, frequently our pages get the content from an API, and the API response takes some time. Therefore, we are forced to use a loading page or spinner while the data is completely resolved.

However, there is another way to handle this behavior.

Using resolvers

Angular has this amazing feature called resolver which helps us to handle the time between the redirection to a page and the resolution of the necessary data to render the page.

Angular prevents the load of the page until the data is completely resolved. Thus, our component is always first rendered with all the data.

Let's see it in action.

In this example, I'll be using a main page with a button that redirects to another page with fake content.

First, we need to create a new resolver in our Angular application. Run the following command:

$ ng n resolver article

Then, the new resolver should look like this

1import { Injectable } from '@angular/core';
2import {
3  Router, Resolve,
4  RouterStateSnapshot,
5  ActivatedRouteSnapshot
6} from '@angular/router';
7import { Observable, of } from 'rxjs';
8
9@Injectable({
10  providedIn: 'root'
11})
12export class ResolverResolver implements Resolve<boolean> {
13  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
14    return of(true);
15  }
16}
17
18

As you can see, it's just a service class which implements a Resolve class with the resolve method. By default, the resolve is of type Observable. But, you could return a Promise too. In addition, the resolve method has the route and state parameters in case you need to use them. For example, to get a param id or get the current state.

However, we don't need to use them. So, replace the code with:

1import { Injectable } from '@angular/core';
2import { Resolve } from '@angular/router';
3import { delay, Observable, of } from 'rxjs';
4import { article } from './constants/article-mock';
5import { Article } from './interfaces/article.interface';
6
7@Injectable({
8  providedIn: 'root'
9})
10export class ArticleResolver implements Resolve<Article> {
11  resolve(): Observable<Article> {
12    return of(article).pipe(
13      delay(2000)
14    );
15  }
16}
17

In this example, it's quite simple. We get rid of all the useless imports. And, we are returning and Observable with the of operator with a mock data set that contains an object with {title: string; content: string; author: string}. But, in a real application, you should return the API request.

Also, we are using the pipe and the delay operators to simulate a slow request to the backend in order to show you how the new page wouldn't be rendered until this resolve is finished.

Now, let's utilize this new resolver. In the app-routing.module.ts file, let's add a configuration in our component Route

1import { ArticleResolver } from './article.resolver';
2
3const routes: Routes = [
4  {
5    path: 'main',
6    component: MainComponent
7  },
8  {
9    path: 'fake-page',
10    component: FakePageComponent,
11    resolve: { article: ArticleResolver }
12  },
13  {
14    path: '**',
15    pathMatch: 'full',
16    redirectTo: '/main'
17  }
18];
19

In short, we are only adding a resolve property in the fake-page configuration, which it's just with our resolve class in and object with any name, in this case article

Now, if we try to navigate to localhost:4200/fake-page, this redirection should take 2 seconds. Due to the delay(2000).

Next, let's get the data from the fake-page.component.ts file:

1export class FakePageComponent implements OnInit {
2
3  article?: Article;
4
5  constructor(private activatedRoute: ActivatedRoute) { }
6
7  ngOnInit(): void {
8    const { article } = this.activatedRoute.snapshot.data;
9    this.article = article;
10  }
11}
12

To achieve this, we need to inject ActivatedRoute because it is in this class where our resolved data lives.

And, in order to get the resolved data, we can destructure it from const { article } = this.activatedRoute.snapshot.data; because we name it article in the routing configuration.

Finally, we only need to render the content of our page in the fake-page.component.html file:

1<section class="container">
2  <h1>{{article?.title}}</h1>
3  <article class="article">
4    {{article?.content}}
5  </article>
6  <p class="author">- by {{article?.author}}</p>
7</section>
8

And this should be the final behavior.

Summary

We just learned how to implement the resolve Class in a simple Angular application. We learn, that we only prevent the new page from being redirected until the asynchronous data is resolved. You can find the repository of this example in here.

References: