Angular: display a spinner on any component that does an HTTP request
Tags: angular, http, component, interceptor
Published: 2019-09-04 12:52:00

---
title: Angular: display a spinner on any component that does an HTTP request
published: true
description: make everything spin around
tags: Angular, HTTP, Component, interceptor
---

This is by definition one of the weirdest, useful and a bit ugly component I've written. 

Our goal is to `apply` a spinner on top of any component that relies on an HTTP request

First we need to create a simple component that has it can take the size of its parent and has a spinner in the middle. I am using the Angular Material library to make things simpler. 

This component is using a single service called `HttpStateService`. As we will see in a moment `HttpStateService` has only a single property of type [`BehaviorSubject`](http://reactivex.io/rxjs/manual/overview.html#behaviorsubject). So its basically being used to pass messages back and forth.

So our component subscribes to any messages coming from that subject.
The spinner component also has an `@Input()` property which is on which url it should react.

```typescript
@Component({
  selector: 'http-spinner',
  templateUrl: './spinner.component.html',
  styleUrls: ['./spinner.component.scss']
})
export class SpinnerComponent implements OnInit {
  public loading = false;
  @Input() public filterBy: string | null = null;
  constructor(private httpStateService: HttpStateService) { }

  /**
   * receives all HTTP requests and filters them by the filterBy
   * values provided
   */
  ngOnInit() {
    this.httpStateService.state.subscribe((progress: IHttpState) => {
      if (progress && progress.url) {
        if (!this.filterBy) {
          this.loading = (progress.state === HttpProgressState.start) ? true : false;
        } else if (progress.url.indexOf(this.filterBy) !== -1) {
          this.loading = (progress.state === HttpProgressState.start) ? true : false;
        }
      }
    });
  }
}

```
the css

```css
.loading {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    background: rgba(0, 0, 0, 0.15);
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: center;
  }
```
and the html, in which we just either a) display the whole thing or b) not

```html
<div *ngIf="loading" class="loading">
  <mat-spinner></mat-spinner>
</div>
```

our extremely simple HttpProgressState denoting
whether a request has started or ended

```typescript
export enum HttpProgressState {
    start,
    end
}
```
The single `BehaviorSubject` property service
```typescript
@Injectable({
  providedIn: 'root'
})
export class HttpStateService {
  public state = new BehaviorSubject<IHttpState>({} as IHttpState);

  constructor() { }
}
```

And now the most important bit, the `HttpInterceptor`. An `HttpInterceptor` is basically a `man in the middle` service that intercepts all requests that you might try to do through the `HttpClientModule` and manipulate them or react to them before they get fired. Here I have a relatively simple implementation of an `HttpInterceptor`. I've added take and delay to underline some powerful capabilities an `HttpInterceptor` might have.

Apart from take and delay, I've added one more and that is finalize.

So basically every time the `InterceptorService ` intercepts a request it sends a message to the `HttpStateService` containing the url and a start state.
then on finalize (after the request has finished) sends an end state to the `HttpStateService`

```typescript
@Injectable({
  providedIn: 'root'
})
export class InterceptorService implements HttpInterceptor {

  private exceptions: string[] = [
    'login'
  ];

  constructor(
    private httpStateService: HttpStateService) {

  }

  /**
   * Intercepts all requests
   * - in case of an error (network errors included) it repeats a request 3 times
   * - all other error can be handled an error specific case
   * and redirects into specific error pages if necessary
   *
   * There is an exception list for specific URL patterns that we don't want the application to act
   * automatically
   * 
   * The interceptor also reports back to the httpStateService when a certain requests started and ended 
   */
  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    if (!this.exceptions.every((term: string) => request.url.indexOf(term) === -1)) {
      return next.handle(request).pipe(tap((response: any) => {},
     (error) => {}));
    }

    this.httpStateService.state.next({
      url: request.url,
      state: HttpProgressState.start
    });

    return next.handle(request).pipe(retryWhen(
      error => {
        return error.pipe(take(3), delay(1500),
          tap((response: any) => {
             // ...logic based on response type
             // i.e redirect on 403
             // or feed the error on a toaster etc
          })
        );
      }
    ), finalize(() => {
      this.httpStateService.state.next({
        url: request.url,
        state: HttpProgressState.end
      });
    }));
  }
}
```
Its usage is simple add it to any component that needs a spinner and define which endpoint it needs to listen to.

```html
<http-spinner filterBy="data/products"></http-spinner>
```

Lastly to add an interceptor on a `Module` you just need to add another providers like the following example

```typescript
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: InterceptorService,
      multi: true
    }
....
]
```

missing interface (see comments)
```typescript
export interface IHttpState {
    url: string;
    state: HttpProgressState;
}
```