Joshua Colvin

Creating a Route Resolver in Angular

Published January 29, 2019

Angular Resolvers allow us to get data before the user is navigated to a route. If we have a products route that displays a list of products there is no point in navigating the user to that page until the product data is available. By using a Resolver we can pre-fetch the data so it is availbe when the user hits the page.

Creating a resolver

We create a resolver by creating a class that implements the Resolve interface which looks like this:

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

In order to correctly implement the Resolve interface our class must have a resolve method that returns either an Observable or a Promise of some type. Here we will us the any type for simplicity.

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

export class ProductsResolver implements Resolve<Observable<any>> {
  resolve(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<any> {}
}

The resolve method has two parameters: route, of type ActivatedRouteSnapshot, which gives you access to information about the route such as the url or any params or queryParams associated with the route and state, of type RouterStateSnapshot, which represents the state of the router.

We can create a service that will fetch an array of products from an API:

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

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

  public getProducts(): Observable<any> {
    return this.http.get('/api/products')
  }
}

And use that service in our Resolver:

src/app/products/products.resolver.ts
import { ProductsService } from './products.service'

@Injectable()
export class ProductsResolver implements Resolve<Observable<any>> {
  constructor(private productsService: ProductsService) {}

  resolve(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<any> {
    return this.productsService.getProducts()  }
}

Using a Resolver

In order to use a resolver we must provide it in the module it will be used in:

src/app/app.module.ts
import { ProductsResolver } from './products/products.resolver';

@NgModule({
  declarations: [AppComponent, ProductsComponent, HomeComponent],
  imports: [BrowserModule, AppRoutingModule, HttpClientModule],
  providers: [ProductsService, ProductsResolver],  bootstrap: [AppComponent]
})

Now we need to tell the route that we want to resolve the data for the products route using our ProductsResolver:

src/app/app-routing.module.ts
const routes: Routes = [
  {
    path: 'products',
    component: ProductsComponent,
    resolve: {      products: ProductsResolver,    },  },
]

We do this by adding a resolve property to our route declaration. resolve takes a key/value pair where the key is the variable the data should be saved to, in this case products, and the value is our ProductsResolver.

The last thing we need to do is use our resolved data in our component. We can access the data by subscribing to the ActivatedRoute data observable:

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

@Component({
  selector: 'app-products',
  templateUrl: './products.component.html',
  styleUrls: ['./products.component.css'],
})
export class ProductsComponent implements OnInit {
  constructor(private route: ActivatedRoute) {}

  products: any

  ngOnInit() {
    this.route.data.subscribe(data => {      this.products = data.products    })  }
}

And finally, render the list of products:

src/app/products/products.component.html
<div *ngFor="let product of products" class="card">
  <div class="card-body">
    <h5 class="card-title">
      {{ product.name }}
      <span class="text-muted">{{ product.price | currency }}</span>
    </h5>
    <p class="card-text">{{ product.description }}</p>
    <button class="btn btn-primary">Add to Cart</button>
  </div>
</div>

You can find a working example of this resolver here.

Find this article helpful or interesting? Follow me on Twitter for related content.

Joshua Colvin is a UI Software Engineer specializing in building component libraries. He lives with his wife and two kids in Michigan.