Simplify Angular Components with Route Resolvers
Simplify components with route resolvers

Simplify Angular Components with Route Resolvers

I often come across Angular components that retrieve data from a remote API inside the ngOnInit lifecycle hook. While this is an easy way to load data into the component, it can can increase the size of the code and breaks the single responsibility principle. A better approach is to use route resolvers to provide data to Angular components before the component loads.

Benefits of using a route resolver in Angular:

  • Simplify component initialisation code by moving retrieval code to resolvers
  • Increased maintainability due to compliance with single responsibility principle. The component is responsible for display, not for data retrieval
  • Data is available before the component loads
  • There is no need to inject services into the component as the data is accessible from the activated route

What is a Route Resolver?

A route resolver in Angular is just a class, which implements the Resolve interface. Through this interface, the class can use the resolve method to return data to the route.

interface Resolve<T> {
  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) 
: Observable<T>
}

The interface is really simple. The only requirement is to return an Observable<T>.

T here is the instance type you wish to make available to the route. It can be string, number or event a complex object like User.

The two parameters are optional but you most likely need the ActivatedRouteSnapshot to access the parameters in the route. It is a good idea to add it to your implementation.

How to User Resolvers in Angular?

The following class is a simple route resolver that makes a customer available to its associated route.

import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';

import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';

@Injectable({providedIn : "root"})
export class CustomerResolver implements Resolve<Observable<string>> {
  
  constructor(private customerService: CustomerService) {}

   resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) : Observable<string> {
    const id = route.params["customerId"];
    return this.customerService.get(id);
  }
}

Our simple CustomerResolver implements the Resolve interface with type string.

Firstly, it reads the customer id from the route object. Then it returns an Observable<string> through the CustomerService using the customer’s id.

Incase you’re wondering what the get method looks like, here is the signature:

// Given an id, returns the customer name
public get(id: string) : Observable<string> { 
   // return customer from the API 
}

Configure Routing

We are now ready to make use of the CustomerResolver. We just need to tell Angular which routes to use the resolver for.

import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { CustomerResolver } from "../core/resolvers/customer.resolver";
import { CustomerComponent } from "./customers/customer.component";

const routes: Routes = [
    {
        path: "customer",
        component: CustomerComponent,
        resolve: { customer: CustomerResolver }
    }
];

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

The routing configuration above is pretty standard apart from the resolve setup.

We provide a JSON object with a customer key and assign the resolver to it.

This tells Angular that the CustomerResolver is to be executed and its result should be stored in the router with the key customer.

There is no need to provide the resolver as it is marked Injectable.

Getting Resolved Data in the Component

Now that we the resolver is setup, we can access the customer data inside the component. We will inject the ActivatedRoute service into the component to access the customer.

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({ ... })
export class CustomerComponent implements OnInit {
  private customer: string;

  constructor(private route: ActivatedRoute) {}

  ngOnInit() {
    this.customer = this.activatedRoute.snapshot.data["customer"]);
  }
}

That’s it! We have just completed implementing a resolver in Angular.

We can now access the customer name in the component template like so:

{{ customer }}

You will notice that the resolver will execute before the component loads into the view.

Error Handling in Route Resolvers

In case there’s an error while retrieving the data, you could catch and deal with the error in the resolver using RxJS’s catch operator.

resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
    const id = route.params["customerId"];
    return this.customerService
               .get(id)
               .pipe(catchError(error => {
                  console.log(error);
                  return of('Failed to get customer');
               }));
}

Summary

In this post, we have implemented a simple route resolver. As mentioned, route resolvers us to reduce the amount of retrieval code in components, making our code-base more maintainable.

Umut Esen

Umut is a certified Microsoft Certified Solutions Developer and has an MSc in Computer Science. He is currently working as a senior software developer for Royal London. He is the primary author and the founder of onthecode.

Leave a Reply

Close Menu

Free Template

Get your Angular Material application template to kick-start your next project.