One way to deal with getting 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’s another way however using what’s known as a route resolver, which allows you to get data before navigating to the new route.
Simple Resolver
In this post we’ll implement a simple route resolver that gets data from the Hacker News API made available from the HNPWA project before navigating to a route that displays the gathered data. We’ll start by implementing the simplest resolver that simply returns a string after a delay of 2 seconds. This should help get the big picture of how to wire everything together.
We’ll create a separate class for our resolver in a file of its own:
hn.resolver.ts
import { Injectable } from '@angular/core'; import { Resolve } from '@angular/router'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/observable/of'; import 'rxjs/add/operator/delay'; @Injectable() export class HnResolver implements Resolve<Observable<string>> { constructor() {} resolve() { return Observable.of('Hello Alligator!').delay(2000); } }
As you can see, the only requirement to implement the Angular router’s Resolve interface is that our class has a resolve method. Whatever is returned from that method will be the resolved data.
Here we return an observable that wraps a string after a delay of 2 seconds.
Route Configuration
Now we can setup our routing module to include our resolver:
app-routing.module.ts
import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { TopComponent } from './top/top.component'; import { HomeComponent } from './home/home.component'; import { HnResolver } from './hn.resolver'; const routes: Routes = [ { path: '', pathMatch: 'full', component: HomeComponent }, { path: 'top', component: TopComponent, resolve: { message: HnResolver } } ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule], providers: [ HnResolver ] }) export class AppRoutingModule {}
Notice how our resolver is provided just like a service and then we include the resolver with our route definition. Here the resolved data will be available under the message key.
Accessing Resolved Data in the Component
In the component, we can access the resolved data using the data property of ActivatedRoute’s snapshot object:
import { Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; @Component({ ... }) export class TopComponent implements OnInit { data: any; constructor(private route: ActivatedRoute) {} ngOnInit() { this.data = this.route.snapshot.data; } }
And that’s all we need. Now, in our component, we can access our Hello Alligator! message like so:
<p>the message: {{ data.message }}</p>
You’ll notice when navigating to the route that there’s now a 2-second delay because the data is resolved first.
Resolving Data From an API
Let’s now make things more real-life by actually getting some data from an API. Here we’ll create a service that gets data from the Hacker News API.
Here’s our simple service, where we also use the new HttpClient:
hn.service.ts
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; @Injectable() export class HnService { endpoint = 'https://hnpwa.com/api/v0/news.json'; constructor(private http: HttpClient) {} getTopPosts() { return this.http.get(this.endpoint); } }
And now we can change our resolver to something like this:
hn.resolver.ts
// ...imports @Injectable() export class HnResolver implements Resolve<any> { constructor(private hnService: HnService) {} resolve() { return this.hnService.getTopPosts(); } }
Access to Route Params
You can get access to the current route parameters in your resolver using the ActivatedRouteSnapshot object. Here’s an example where we would use our resolver to get access to the id param of the current route:
hn.resolver.ts
import { Injectable } from '@angular/core'; import { HnService } from './hn.service'; import { Resolve } from '@angular/router'; import { ActivatedRouteSnapshot } from '@angular/router'; @Injectable() export class HnResolver implements Resolve<any> { constructor(private hnService: HnService) {} resolve(route: ActivatedRouteSnapshot) { return this.hnService.getPost(route.paramMap.get('id')); } }
Our route can look like this:
{ path: 'post/:id', component: PostComponent, resolve: { hnData: HnResolver } }
And here’s the getPost method in our service:
getPost(postId: string) { const endpoint = 'https://hnpwa.com/api/v0/item'; return this.http.get(`${endpoint}/${postId}.json`); }
Now if a user goes to, say, http://localhost:4200/post/15392112, the data for the post id 15392112 will be resolved.
Error Handling
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:
import { Injectable } from '@angular/core'; import { HnService } from './hn.service'; import { Resolve } from '@angular/router'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/catch'; import 'rxjs/add/observable/of'; @Injectable() export class HnResolver implements Resolve<any> { constructor(private hnService: HnService) {} resolve() { return this.hnService.getTopPosts().catch(() => { return Observable.of('data not available at this time'); }); } }
Or you could return an empty observable, in which case the user won’t be sent to the new route:
resolve() { return this.hnService.getTopPosts().catch(() => { return Observable.empty(); }); }