Search code examples
angulartypescripthttprequestangular-http-interceptors

Angular Http Interceptor blocking http progress events


I have tried this a bunch of ways. This method is very much by the book, however, my HTTP Interceptor is not forwarding the reportProgress events to my component. I can see the events at the Interceptor, but they are not at the component no matter what I try (eg {observe: 'events'} etc). I can't figure how to get the progress in the component.

interceptor.ts

import {Component, Input, OnInit} from '@angular/core';
import {
  HttpClient,
  HttpRequest,
} from '@angular/common/http';
import {tap} from 'rxjs/operators';
import {AlertService} from '../../services/alert.service';

@Component({
  selector: 'app-upload',
  templateUrl: './upload.component.html',
  styleUrls: ['./upload.component.scss']
})

export class UploadComponent implements OnInit {

  constructor(private http: HttpClient, public alertService: AlertService) {
  }

  ngOnInit(): void {
  }

  files: File[] = [];

  upload(files: File[]) {
    this.files = files;
    for (let file of files) {
      const formData: FormData = new FormData();
      formData.append('file', file, file.name);

      const url = 'http://localhost:4000/upload';

      const req = new HttpRequest('POST', url, formData, {
        reportProgress: true
      });

      this.http.request(req)
        .pipe(
          tap(console.log) // ONLY THE FINAL RESPONSE HERE
        )
        .subscribe();

    }
  }
}

component.ts

import {Injectable} from '@angular/core';
import {
  HttpResponse,
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpInterceptor
} from '@angular/common/http';
import {Observable} from 'rxjs';
import {StateService} from '../services/state.service';

@Injectable()
export class LoaderInterceptor implements HttpInterceptor {
  private requests: HttpRequest<any>[] = [];

  constructor(private alertService: AlertService, private stateService: StateService) {
  }

  removeRequest(req: HttpRequest<any>) {
    const i = this.requests.indexOf(req);
    if (i >= 0) {
      this.requests.splice(i, 1);
    }
    this.stateService.isLoading$.next(this.requests.length > 0);
  }

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    this.requests.push(req);
    this.stateService.isLoading$.next(true);
    return new Observable(observer => {
      const subscription = next.handle(req)
        .subscribe(
          event => {
              console.log(event); // I CAN SEE THE LOADED EVENT HERE
            if (event instanceof HttpResponse) {
              this.removeRequest(req);
              observer.next(event);
            }
          },
          err => {
            this.alertService.setAlert('error', err.message);
            this.removeRequest(req);
            observer.error(err);
          },
          () => {
            this.removeRequest(req);
            observer.complete();
          });
      // remove request from queue when cancelled
      return () => {
        this.removeRequest(req);
        subscription.unsubscribe();
      };
    });
  }
}

Solution

  • You're only emitting the response event from the interceptor.

    if (event instanceof HttpResponse) {
      this.removeRequest(req);
      observer.next(event);
    }
    

    You should move observer.next out of the if block:

    const subscription = next.handle(req)
      .subscribe(event => {
        console.log(event); // I CAN SEE THE LOADED EVENT HERE
        if (event instanceof HttpResponse) {
          this.removeRequest(req);      
        }
        // MOVED HERE
        observer.next(event);
      }, err => {
        this.alertService.setAlert('error', err.message);
        this.removeRequest(req);
        observer.error(err);
      }, () => {
        this.removeRequest(req);
        observer.complete();
      });
    

    I would personally do this in a pipe rather than creating a new subscription, but that's irrelevant to the solution.