Tutorial

How To Use Route Resolvers with Angular Router

Updated on March 19, 2021
Default avatar

By Alligator.io

How To Use Route Resolvers with Angular Router

Introduction

One way for handling retrieving and displaying data from an API is to route a user to a component, and then in that component’s ngOnInit hook call a method in a service to get the necessary data. While getting the data, perhaps the component can show a loading indicator.

There is another way to use what is known as a route resolver, which allows you to get data before navigating to the new route.

One API that is available for use is the Hacker News API. Hacker News is a website for sharing links and discussing them. The API can be used to retrieve the most popular posts and display information about individual posts.

In this tutorial, you will implement a route resolver that gets data from the Hacker News API before navigating to a route that displays the gathered data.

Prerequisites

To complete this tutorial, you will need:

This tutorial was verified with Node v15.3.0, npm v6.14.9, @angular/core v11.0.1, @angular/common v11.0.1, @angular/router v11.0.1, and rxjs v6.6.0.

Step 1 — Setting Up the Project

For the purpose of this tutorial, you will build from a default Angular project generated with @angular/cli.

  1. npx @angular/cli new angular-route-resolvers-example --style=css --routing --skip-tests

This will configure a new Angular project with styles set to “CSS” (as opposed to “Sass”, Less", or “Stylus”), routing enabled, and skipping tests.

Navigate to the newly created project directory:

  1. cd angular-route-resolvers-example

At this point, you have a new Angular project with @angular/router.

Step 2 — Building a Resolver

Let’s start by implementing a resolver that returns a string after a delay of 2 seconds. This small proof of concept can help with exploring the fundamentals of wiring routes that can be applied to larger projects.

First, create a separate class for the resolver in a file of its own:

  1. ./node_modules/@angular/cli/bin/ng generate resolver news

This will use the @angular/cli to generate a resolver named news:

src/app/news.resolver.ts
import { Injectable } from '@angular/core';
import { Resolve } from '@angular/router';

import { Observable, of } from 'rxjs';
import { delay } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class NewsResolver implements Resolve<Observable<string>> {
  resolve(): Observable<string> {
    return of('Route!').pipe(delay(2000));
  }
}

Implementing the Angular router’s Resolve interface requires the class to have a resolve method. Whatever is returned from that method will be the resolved data.

This code will return an observable that wraps a string after a delay of 2 seconds.

Step 3 — Configuring Routes

In order to experience two different routes, you will need two new components. home will be the landing page. And top will feature the top posts from Hacker News API.

First, use @angular/cli to generate a home component:

  1. ./node_modules/@angular/cli/bin/ng generate component home

Then, use @angular/cli to generate a top component:

  1. ./node_modules/@angular/cli/bin/ng generate component top

Now you can set up the routing module to include the resolver.

src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { NewsResolver } from './news.resolver';

import { TopComponent } from './top/top.component';
import { HomeComponent } from './home/home.component';

const routes: Routes = [
  {
    path: '',
    pathMatch: 'full',
    component: HomeComponent
  },
  {
    path: 'top',
    component: TopComponent,
    resolve: { message: NewsResolver }
  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Notice how the resolver is provided just like a service and then you include the resolver with the route definition. Here the resolved data will be available under the message key.

Step 4 — Accessing the Resolved Data in the Component

In the component, you can access the resolved data using the data property of ActivatedRoute’s snapshot object:

src/app/top/top.component.ts
import { Component, OnInit } from '@angular/core';

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

@Component({ ... })
export class TopComponent implements OnInit {
  data: any;

  constructor(private route: ActivatedRoute) {}

  ngOnInit(): void {
    this.data = this.route.snapshot.data;
  }
}

Now, in the component, you can access the Route! message like so:

src/app/top/top.module.html
<p>The message: {{ data.message }}</p>

At this point, you can compile your application:

  1. npm start

And visit localhost:4200/top in a web browser.

Output
The message: Route!

You will observe when navigating to the top route that there is now a 2-second delay because the data is resolved first.

Step 5 — Resolving Data from an API

Let’s make things more real-life by actually getting some data from an API. Here you will create a service that gets data from the Hacker News API.

You will need HttpClient to request the endpoint.

First, add the HttpClientModule to app.module.ts:

src/app/app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
    AppRoutingModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Then, create a new service:

  1. ./node_modules/@angular/cli/bin/ng generate service news

This will use the @angular/cli to generate a service named news:

src/app/news.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable({
  providedIn: 'root'
})
export class NewsService {
  constructor(private http: HttpClient) { }

  getTopPosts() {
    const endpoint = 'https://hacker-news.firebaseio.com/v0/topstories.json';

    return this.http.get(endpoint);
  }
}

And now you can replace the string code in NewsResolver with NewsService:

src/app/news.resolver.ts
import { Injectable } from '@angular/core';
import { Resolve } from '@angular/router';
import { Observable } from 'rxjs';

import { NewsService } from './news.service';

export class NewsResolver implements Resolve<any> {
  constructor(private newsService: NewsService) {}

  resolve(): Observable<any> {
    return this.newsService.getTopPosts();
  }
}

At this point, if you look at the top route in a browser, you will be presented with a list of numbers representing ids of the top posts on Hacker News.

Step 6 — Accessing Route Parameters

You can get access to the current route parameters in your resolver using the ActivatedRouteSnapshot object.

Here’s an example where you would use a resolver to get access to the id param of the current route.

First, use the @angular/cli to generate a resolver named post:

  1. ./node_modules/@angular/cli/bin/ng generate resolver news

Then, modify post.resolver.ts to use ActivatedRouteSnapshot:

src/app/post.resolver.ts
import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot } from '@angular/router';
import { Observable } from 'rxjs';

import { NewsService } from './news.service';

@Injectable({
  providedIn: 'root'
})
export class PostResolver implements Resolve<any> {
  constructor(private newsService: NewsService) {}

  resolve(route: ActivatedRouteSnapshot): Observable<any> {
    return this.newsService.getPost(route.paramMap.get('id'));
  }
}

Next, add a getPost method to NewsService:

src/app/news.service.ts
// ...

export class NewsService {
  constructor(private http: HttpClient) { }

  // ...

  getPost(postId: string) {
    const endpoint = 'https://hacker-news.firebaseio.com/v0/item';

    return this.http.get(`${endpoint}/${postId}.json`);
  }
}

And add PostResolver and the post/:id route to app-routing.module.ts:

src/app/app-routing.module.ts
// ...

import { PostResolver } from './post.resolver';

// ...

const routes: Routes = [
  // ...
  {
    path: 'post/:id',
    component: PostComponent,
    resolve: { newsData: PostResolver }
  }
];

// ...

Next, create the new PostComponent:

  1. ./node_modules/@angular/cli/bin/ng generate component post

Then, modify post.component.ts to use snapshot data:

src/app/post/post.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({ ... })
export class PostComponent implements OnInit {
  data: any;

  constructor(private route: ActivatedRoute) { }

  ngOnInit(): void {
    this.data = this.route.snapshot.data;
  }
}

And modify post.component.html to display the title:

src/app/post/post.component.html
<p>{{ data.newsData.title }}</p>

Now if a user goes to http://localhost:4200/post/15392112, the data for the post id 15392112 will be resolved.

Step 7 — Handling Errors

In case there’s an error while fetching the data, you could catch and deal with the error in the resolver using RxJS’s catch operator. Something like this for example:

src/app/news.resolver.ts
import { Injectable } from '@angular/core';
import { Resolve } from '@angular/router';

import { Observable, of } from 'rxjs';
import { catchError } from 'rxjs/operators';

import { NewsService } from './news.service';

@Injectable()
export class NewsResolver implements Resolve<any> {
  constructor(private newsService: NewsService) {}

  resolve(): Observable<any> {
    return this.newsService.getTopPosts().pipe(catchError(() => {
      return of('data not available at this time');
    }));
  }
}

Or you could return an EMPTY observable and return the user to the root path:

src/app/news.resolver.ts
import { Injectable } from '@angular/core';
import { Router, Resolve } from '@angular/router';

import { Observable, EMPTY } from 'rxjs';
import { catchError } from 'rxjs/operators';

import { NewsService } from './news.service';

@Injectable()
export class NewsResolver implements Resolve<any> {
  constructor(private router: Router, private newsService: NewsService) {}

  resolve(): Observable<any> {
    return this.newsService.getTopPosts().pipe(catchError(() => {
      this.router.navigate(['/']);
      return EMPTY;
    }));
  }
}

These two approaches will lead to a better user experience if there is an error when retrieving data from the API.

Conclusion

In this tutorial, you implemented a route resolver that gets data from the Hacker News API before navigating to a route that displayed the gathered data. This was achieved by utilizing @angular/router, @angular/common/http, and rxjs.

If you’d like to learn more about Angular, check out our Angular topic page for exercises and programming projects.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about us


About the authors
Default avatar
Alligator.io

author

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
1 Comments


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

This comment has been deleted

    Try DigitalOcean for free

    Click below to sign up and get $200 of credit to try our products over 60 days!

    Sign up

    Join the Tech Talk
    Success! Thank you! Please check your email for further details.

    Please complete your information!

    Get our biweekly newsletter

    Sign up for Infrastructure as a Newsletter.

    Hollie's Hub for Good

    Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

    Become a contributor

    Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

    Welcome to the developer cloud

    DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

    Learn more
    DigitalOcean Cloud Control Panel