Angular HTTP Pending Request Pattern
Tags: angular, rxjs
Published: 2020-01-02 06:06:15

---
published: true
title: Angular HTTP Pending Request Pattern
description: Learn how to display HTTP request status messages to users using RxJS Observables.
tags: angular, rxjs
canonical_url: https://coryrylan.com/blog/angular-http-pending-request-pattern
---

Often in single-page apps, we want to show the status of when something is loading as well as show the user when something goes wrong. Pending states can be rather tricky when dealing with asynchronous JavaScript. In Angular, we have RxJS Observables to help us manage async complexity. This post, I'll show a pattern I came up with to solve something I was working on that helped me display the status of an API request as well as any errors.

This pattern I call the pending request pattern. Probably not a great name, but here is how it works. Typically we make an HTTP request in Angular and get back a single Observable that will emit the request value when completed. We have no easy way to show the user that we are loading the data or when something goes wrong without a lot of code defined within our components. With this pattern, instead of the service returning the response Observable, we return a new object that contains two Observables. One Observable for the HTTP response and another Observable with status updates of the request.

```typescript
export interface Pending<T> {
  data: Observable<T>;
  status: Observable<Status>;
}

export enum Status {
  LOADING = 'LOADING',
  SUCCESS = 'SUCCESS',
  ERROR = 'ERROR'
}
```

In our services, we can return the pending object instead of the raw HTTP Observable.

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

  load(userId: number): Pending<User> { ... }
}
```

Within our `load` method we can push out status updates to anyone using our `Pending` object. This pattern makes our code cleaner within our components. Let's take a look at a component example, and we will come back to the `load()` implementation.


```typescript
import { Component } from '@angular/core';

import { UserService, User, Pending, Status } from './user.service';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent {
  readonly Status = Status;
  readonly user: Pending<User>;

  constructor(private userService: UserService) {
    this.user = this.userService.load(1);
  }
}
```

In the component, we inject the `UserService` to call the `load()` method and assign the `Pending` object to the `user` property. I also set the `Status` enum as a property of the class so I can reference it within my template.

```html
<section *ngIf="user.data | async as user">
  <h3>{{user.name}}</h3>
  <p>Height: {{user.height}}</p>
  <p>Mass: {{user.mass}}</p>
  <p>Homeworld: {{user.homeworld}}</p>
</section>
```

In the template, we use the [`async` pipe](https://coryrylan.com/blog/angular-async-data-binding-with-ng-if-and-ng-else) to subscribe to my user data from the `Pending` object. Once subscribed, we can display my data as usual per an Angular template.

To display status messages, we can subscribe to the status Observable within the template as well.

```html
<section *ngIf="user.data | async as user">
  <h3>{{user.name}}</h3>
  <p>Height: {{user.height}}</p>
  <p>Mass: {{user.mass}}</p>
  <p>Homeworld: {{user.homeworld}}</p>
</section>

<section [ngSwitch]="user.status | async">
  <span *ngSwitchCase="Status.LOADING">Loading User...</span>
  <span *ngSwitchCase="Status.ERROR">There was an error loading the user.</span>
</section>
```

Now when the user status returns an update that the request has started, we will show the `Loading User...` message. When the status emits an update that there was any error, we can show the error message to the user. With the `Pending` object, we can show the status of a current request within our template pretty easily.

Now we go back to our `UserService` we can see how we implemented the `Pending` object within the `load()` method.

```typescript
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, ReplaySubject, defer } from 'rxjs';

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

  load(userId: number): Pending<User> {
    const status = new ReplaySubject<Status>();
    const data = this.http.get<User>(`https://swapi.co/api/people/${userId}`);

    return { data, status };
  }
}
```

Here is our starting point for the `load()` method of our service. We have two Observables making up our pending object. First is the `status` which is a special kind of Observable called a [`ReplaySubject`](https://coryrylan.com/blog/rxjs-observables-versus-subjects).

The ReplaySubject will allow anyone who subscribes after events have already fired to get the last event that was emitted. Second is our standard HTTP Observable from the Angular HTTP Client Service.

First, we want to be able to notify when the request has started. To do this we need to wrap our HTTP Observable so we can emit a new status when subscribed to.

```typescript
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, ReplaySubject, defer } from 'rxjs';

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

  load(userId: number): Pending<User> {
    const status = new ReplaySubject<Status>();
    const request = this.http.get<User>(`https://swapi.co/api/people/${userId}`);

    const data = defer(() => {
      status.next(Status.LOADING);
      return request;
    });

    return { data, status };
  }
}
```

Using the `defer` function from RxJS, we can wrap the existing HTTP
Observable, execute some code, and then return the new Observable. By using the `defer`, we can trigger a status loading event only when someone subscribes. Using the `defer` is important because Observables, by default, are lazy and won't execute our HTTP request until subscribed.

Next we need to handle errors if something goes wrong with our request.

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

  load(userId: number): Pending<User> {
  const status = new ReplaySubject<Status>();

  const request = this.http
    .get<User>(`https://swapi.co/api/people/${userId}`)
    .pipe(
      retry(2),
      catchError(error => {
      status.next(Status.ERROR);
      throw 'error loading user';
     })
    );

   const data = defer(() => {
     status.next(Status.LOADING);
     return request;
   });

   return { data, status };
  }
}
```

Using the `catchError` and `retry` operators, we can catch exceptions and retry a given number of failures. Once we have our starting and error status events working, we need to add a success status.

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

  load(userId: number): Pending<User> {
    const status = new ReplaySubject<Status>();

    const request = this.http
      .get<User>(`https://swapi.co/api/people/${userId}`)
      .pipe(
        retry(2),
        catchError(error => {
          status.next(Status.ERROR);
          throw 'error loading user';
        }),
        tap(() => status.next(Status.SUCCESS))
      );

      const data = defer(() => {
        status.next(Status.LOADING);
        return request;
      });

    return { data, status };
  }
}
```

Using the `tap` operator, we can trigger a side effect (code outside the observable) function whenever an event returns from the HTTP Observable. With `tap`, trigger a success status event to our component.

There is a [feature within the Angular HTTP Client Service](https://angular.io/guide/http#listening-to-progress-events)
which can accomplish something similar by listening to HTTP event updates from a request. However, this can be expensive as it causes change detection to run with every event.

Let me know in the comments below what you think! This pattern solved a few scenarios for me, but I'm interested in expanding more on this.[Demo Example](https://stackblitz.com/edit/angular-huwsri)