Angular RxJS - How to monitor progress of HTTP Get Request (not file)
Asked Answered
A

3

10

How to utilize Angular HTTPClient's progress event to show progress in percentage of Get request which does not necessarily a file request?

Currently HTTPClient's progress event fires after request completion. I am hoping to work with Content-Length at back end and determine percentage of content loaded at front end.

I am loading a large amount of rows for a grid and need to show incremental progress on UI. Is it possible?

Aundrea answered 26/1, 2018 at 12:28 Comment(0)
C
16

I know this question is older but, i stumbled upon this while searching for an answer to a similar problem and since there is no accepted answer i post my solution.

I recently implemented a generic way to display a progress bar for every request no matter the type in angular 8.

First i created a HttpInterceptor which would automatically intercept every http call where the reportProgress option is set to true.

@Injectable()
export class HttpProgressInterceptor implements HttpInterceptor {

  constructor(
    private spinnerService: SpinnerService // my personal service for the progress bar - replace with your own
  ) {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    if (req.reportProgress) {
      // only intercept when the request is configured to report its progress
      return next.handle(req).pipe(
        tap((event: HttpEvent<any>) => {
          if (event.type === HttpEventType.DownloadProgress) {
            // here we get the updated progress values, call your service or what ever here
            this.spinnerService.updateGlobalProgress(Math.round(event.loaded / event.total * 100)); // display & update progress bar
          } else if (event.type === HttpEventType.Response) {
            this.spinnerService.updateGlobalProgress(null); // hide progress bar
          }
        }, error => {
          this.spinnerService.updateGlobalProgress(null); // hide progress bar
        })
      );
    } else {
      return next.handle(req);
    }
  }
}

You need to register this interceptor in your module of course:

@NgModule({
  declarations: [
    AppComponent,
    ...
  ],
  imports: [
    BrowserModule,
    ...
    RouterModule.forRoot(appRoutes)
  ],
  providers: [
    ...
    { provide: HTTP_INTERCEPTORS, useClass: HttpProgressInterceptor, multi: true },
    ...}
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

Basically we're done here, only thing left is that we need to change the way we call our apis. If you want a specific request to be monitored using this interceptor you need to tell angular to report the progress on the HttpRequest:

@Injectable()
export class MyService {

  constructor(
    private http: HttpClient
  ) {}

  myGetMethod() {
    const url = "api/data/load/big/data";
    const req = new HttpRequest("GET", url, {
      reportProgress: true  // this is important!
    });

    return this.http.request(req);
  }
}

This way of calling the httpClient api deliveres a different object when calling .subscribe so we need to take care of that when calling the myGetMethod():

ngOnInit() {
  this.myService.myGetMethod().subscribe((event: HttpEvent<any>) => {
    if (event.type === HttpEventType.Response) {
      const responseData = event.body;
      console.dir(responseData); // do something with the response
    }
  });
}

We could also listen here for the HttpEventType.DownloadProgress event and update the progress values inside this component - but that was not the point of my example.

Hint: if you encounter the problem that event.total is undefined - you must check whether your REST backend REALLY is providing the Content-Length header - if this header is missing, you will not be able to calculate the progress!

anyway, i hope this will help somebody someday πŸ˜‰

Corell answered 12/2, 2020 at 13:23 Comment(2)
I also add near reportProgress: true following option: observe: "events" - this makes that progress bar refresh more often – Coruscation
Can you add this to stackblitz and share the link? – Avaunt
N
5

What about this:

import { HttpEventType, HttpClient, HttpRequest} from '@angular/common/http';

...

const request = new HttpRequest('GET', url,  {
  reportProgress: true
});

http.request(request).subscribe(event => {

  // progress
  if (event.type === HttpEventType.DownloadProgress) {
    console.log(event.loaded, event.total); 
    // event.loaded = bytes transfered 
    // event.total = "Content-Length", set by the server

    const percentage = 100 / event.total * event.loaded;
    console.log(percentage);
  }

  // finished
  if (event.type === HttpEventType.Response) {
    console.log(event.body);
  }

})
Nathanson answered 26/1, 2018 at 12:46 Comment(9)
The three console.log prints just once, after the request has completed. loaded, total: 4 undefined progress: NaN response: null – Aundrea
What angular version are you using? – Nathanson
I am using 4.4.3. – Aundrea
I don't understand your printed values? Is total empty? – Nathanson
Yes, total is always undefined. Don't you think total shows Content-Length? – Aundrea
Yes, the total value depends on the Content-Length-Header. Are you sure it is set in the response? And how large is your response? Only 4 bytes? – Nathanson
I am setting it with response.Headers.Add("Content-Length", System.Text.ASCIIEncoding.ASCII.GetByteCount(serializedDataTable).ToString());. It's a Nancy endpoint. Response is around 10KB. But this header doesn't show in Network. I am using webpack proxy for API requests. Any ideas? – Aundrea
If the header isn't visible in network in your browser, your server isn't sending it. Different problem, different question ;-) – Nathanson
Yeah, problem is, any header except Content-Length is sent. For some reason, content-length isn't getting through. – Aundrea
B
1

You can use this

    const req = new HttpRequest('POST', '/upload/file', file, {
    reportProgress: true
    });

Next, pass this request object to the HttpClient.request() method, which returns an Observable of HttpEvents, the same events processed by interceptors:

    // The `HttpClient.request` API produces a raw event stream
    // which includes start (sent), progress, and response events.
    return this.http.request(req).pipe(
    map(event => this.getEventMessage(event, file)),
    tap(message => this.showProgress(message)),
    last(), // return last (completed) message to caller
    catchError(this.handleError(file))
    );

Finally you can use that to help

    /** Return distinct message for sent, upload progress, & response events */
    private getEventMessage(event: HttpEvent<any>, file: File) {
    switch (event.type) {
    case HttpEventType.Sent:
      return `Uploading file "${file.name}" of size ${file.size}.`;

    case HttpEventType.UploadProgress:
      // Compute and show the % done:
      const percentDone = Math.round(100 * event.loaded / event.total);
      return `File "${file.name}" is ${percentDone}% uploaded.`;

    case HttpEventType.Response:
      return `File "${file.name}" was completely uploaded!`;

    default:
      return `File "${file.name}" surprising upload event: ${event.type}.`;
      }
    }
Bedabble answered 30/7, 2018 at 10:35 Comment(1)
It's written "not file" – Haire

© 2022 - 2024 β€” McMap. All rights reserved.