Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML5

Do We Really Need Angular Route Resolvers?

4.86/5 (7 votes)
29 Jun 2020CPOL4 min read 8.8K  
Best practices for using Angular Route resolvers
This post intends to capture the best practices and anti-patterns when it comes to using Angular Route Resolvers.

Resolvers have been around in Angular for a while now and they have become quite popular if you look at the forums.

In my opinion, resolvers solve a very specific problem, i.e., they help you load data for a “component to function” before the component is activated. That’s my definition btw, but the way Angular markets the feature is, by suggesting that we should use resolvers for pre-fetching data.

What I Thought / Hoped They Were?

You know when I hear the term pre-fetch it sounds promising. When I think about it in my head, I imagine data being pre-fetched for things I anticipate the user to do in the immediate future. It’s like this machine learnt navigation behavior where one could calculate the probability of a user clicking on a certain link and pre-fetch data for that page before the user even clicks it. But I know that’s just wishful thinking. 😁

What They Really Are?

In simple terms, Resolvers are just plain-old middlewares in the Angular routing pipeline which facilitates loading the data required for a component before the component is loaded. That’s it. No magic. It plugs into the routing pipeline and makes the API calls to get the data and when the data is ready, it activates the component. This way, the component just appears with no “loading…”

Image 1

The Angular Resolver Workflow

Now, there are use cases when I would want to have some data available before component initialization, but in the recent months, I’ve seen Resolvers being abused to make API calls which sometimes take 3-5 seconds. Imagine being an end user, and you click a link to display a component only to be waiting for 5 seconds before you even know that the click registered (if you’re anything like me, you would have clicked 2-3 times in that period). This does seem like bad UX.

Let’s look at our options (with and without resolvers) with the code samples below.

Code with Resolvers

JavaScript
// Snippet from the RecordsResolverService 
@Injectable({
  providedIn: 'root'
})
export class RecordsResolverService implements Resolve<Record[]>{

  constructor(private _recordsService: RecordsService) { }
  resolve(route: ActivatedRouteSnapshot, 
  state: RouterStateSnapshot): Record[] | Observable<Record[]> | Promise<Record[]> {
    return this._recordsService.Get();
  }
}
JavaScript
// Snippet from app-routing.module.ts
  {
    path: 'records-resolver', component: ResultsWithResolverComponent, resolve: {
      records: RecordsResolverService
    }
  }
JavaScript
// Snippet from the component consuming route data from Resolver
export class ResultsWithResolverComponent implements OnInit {
  records: Record[] = [];
  constructor(private route: ActivatedRoute) { }

  ngOnInit(): void {
    this.route.data.subscribe((data: { records: Record[] }) => {
      this.records = data.records;
    });
  }
}
HTML
<!-- HTML with Resolvers-->
<table>
    <thead>
        <tr>
            <td>ID</td>
            <td>Price</td>
            <td>Product</td>
            <td>Order Date</td>
        </tr>
    </thead>
    <tbody>
        <tr *ngFor="let rec of records">
            <td>{{rec.guid}}</td>
            <td>{{rec.price}}</td>
            <td>{{rec.product}}</td>
            <td>{{rec.orderDate}}</td>
        </tr>
    </tbody>
</table>

Code without Resolvers (RxJS and Async Pipe)

JavaScript
// Snippet from app-routing.module.ts
 {
    path: 'records-async', component: ResultsAsyncRxComponent
  }
JavaScript
// Snippet from the component consuming route data directly from API
export class ResultsAsyncRxComponent implements OnInit {
  records$: Observable<Record[]>;
  constructor(private _recordsService: RecordsService) { }

  ngOnInit(): void {
    this.records$ = this._recordsService.Get();
  }
}
HTML
<!-- HTML with Async Pipe-->
<table *ngIf="records$ | async as records; else loading">
    <thead>
        <tr>
            <td>ID</td>
            <td>Price</td>
            <td>Product</td>
            <td>Order Date</td>
        </tr>
    </thead>
    <tbody>
        <tr *ngFor="let rec of records">
            <td>{{rec.guid}}</td>
            <td>{{rec.price}}</td>
            <td>{{rec.product}}</td>
            <td>{{rec.orderDate}}</td>
        </tr>
    </tbody>
</table>

Now let’s see a side-by-side comparison of output (with and without resolvers). For demo purposes, I’ve introduced a delay of 2 seconds in API calls.

With Resolvers Without Resolvers (Async+Rx)
Image 2 Image 3

As we can see, even with a 2 second delay in API call, fetching data on the component instead of the resolver, gives some indication to the user that his/her click on the link registered.

So Why Do Resolvers Exist and What Are They Good For?

Well, I’d argue in most of the cases, you don’t need resolvers since the popular adoption of reactive programming (rxJs) in Angular.

But, Resolvers (may be) could have a use-case when:

  • you want to send session information or get unique identifiers for the component to “function” once activated.
  • you want to add extra query/route params to the URL and navigate again based on the response from the API.

For instance, consider a hypothetical scenario (source code):

  • You have a list of customers (with ID) displayed on a page. Clicking on a customer ID should show the associated orders.
  • Each customer has multiple orders associated, but (here’s the twist) the ordering database doesn’t use the same customerIDs; instead they have a surrogate ID.
  • This customerID to surrogateID mapping comes from another WebAPI.
  • As you might have figured by now, for the orders page to be functional, you need the surrogateID before loading the component.

This makes for a good candidate for using Resolvers, wherein before navigating to the orders component, you make a call to the surrogate API to get the surrogate ID and pass it to orders component on activation (using route.data or queryParams).

The rest of the business of getting the order history for the customer is (and should be) taken care by the order component in an async fashion (using RxJs Observables and AsyncPipe).

If you guys want to see the code for hypothetical example, it’s available at this GitHub repo.

What Resolvers Should NOT be Used For?

For fetching actual content (records, tabular data) for a component, you shouldn’t use resolvers (since their synchronous nature doesn’t make for good UX).

In these scenarios, it’s best to use RxJS and Observables (and Async Pipes) to fetch the data in the component itself. This would make your app navigation appear more “responsive” from an end-user perspective.

Resolvers sound cool, especially the concept of pre-fetching data, but in a reactive (RxJs) world, you’d rarely find a scenario where you can use Angular Resolvers to have a positive (if any) effect on application performance. More often than not, RxJs would solve all your data fetching requirements in a performant and asynchronous fashion.

I’d like to end by acknowledging that my opinion on Resolvers might appear strong, but I’m a flexible person 🙂 . Feel free to shout out in the comments and I’d be happy to get enlightened.

Happy coding!!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)