Angular 2 HTTP Progress bar

Solution 1:

Currently (from v. 4.3.0, when using new HttpClient from @ngular/common/http) Angular provides listening to progress out of the box. You just need to create HTTPRequest object as below:

import { HttpRequest } from '@angular/common/http';
...

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

And when you subscribe to to request you will get subscription called on every progress event:

http.request(req).subscribe(event => {
  // Via this API, you get access to the raw event stream.
  // Look for upload progress events.
  if (event.type === HttpEventType.UploadProgress) {
    // This is an upload progress event. Compute and show the % done:
    const percentDone = Math.round(100 * event.loaded / event.total);
    console.log(`File is ${percentDone}% uploaded.`);
  } else if (event instanceof HttpResponse) {
    console.log('File is completely uploaded!');
  }
});

More info here.

Solution 2:

You could leverage the onprogress event provided by XHR (see this plunkr: http://plnkr.co/edit/8MDO2GsCGiOJd2y2XbQk?p=preview).

This allows to get hints about the progress of the download. This isn't supported out of the box by Angular2 but you can plug it by extended the BrowserXhr class:

@Injectable()
export class CustomBrowserXhr extends BrowserXhr {
  constructor(private service:ProgressService) {}
  build(): any {
    let xhr = super.build();
    xhr.onprogress = (event) => {
      service.progressEventObservable.next(event);
    };
    return <any>(xhr);
  }
}

and override the BrowserXhr provider with the extended:

bootstrap(AppComponent, [
  HTTP_PROVIDERS,
  provide(BrowserXhr, { useClass: CustomBrowserXhr })
]);

See this question for more details:

  • Angular2 / RxJS - updating variable after getting data from Http observable

Solution 3:

When you make http cals in angular2, it returns an Observable of type Response, this response is created inside class called XHRConnection where all the magic happens.

The XHRConnection builds the response by listening to XMLHttpRequest's load event, this means it will return one response at the end of the request.

Now to be able to alter this behavior we need to make our connection class listen to the progress event.

So we need to create custom Connection class, to handle the response as we see fit.

I did it this way, Take note that my php API returns multi response in a single request and this responses are plain strings.

my_backend.ts

import {Injectable} from "angular2/core";
import {Observable} from "rxjs/Observable";
import {Observer} from "rxjs/Observer";
import {Connection,ConnectionBackend} from "angular2/src/http/interfaces";
import {ReadyState, RequestMethod, ResponseType} from "angular2/src/http/enums";
import {ResponseOptions} from "angular2/src/http/base_response_options";
import {Request} from "angular2/src/http/static_request";
import {Response} from "angular2/src/http/static_response";
import {BrowserXhr} from "angular2/src/http/backends/browser_xhr";
import {Headers} from 'angular2/src/http/headers';
import {isPresent} from 'angular2/src/facade/lang';
import {getResponseURL, isSuccess} from "angular2/src/http/http_utils"

export class MyConnection implements Connection { 
    readyState: ReadyState;
    request: Request;
    response: Observable<Response>;

    constructor(req: Request, browserXHR: BrowserXhr, baseResponseOptions?: ResponseOptions) {       
        this.request = req;
        this.response = new Observable<Response>((responseObserver: Observer<Response>) => {
            let _xhr: XMLHttpRequest = browserXHR.build();
            _xhr.open(RequestMethod[req.method].toUpperCase(), req.url);
            // save the responses in array 
            var buffer :string[] = []; 
            // load event handler
            let onLoad = () => {
                let body = isPresent(_xhr.response) ? _xhr.response : _xhr.responseText;
                //_xhr.respons 1 = "Loading data!"
                //_xhr.respons 2 = "Loading data!Ready To Receive Orders."
                // we need to fix this proble 
                // check if the current response text contains the previous then subtract
                // NOTE: I think there is better approach to solve this problem.
                buffer.push(body);
                if(buffer.length>1){
                    body = buffer[buffer.length-1].replace(buffer[buffer.length-2],'');
                }
                let headers = Headers.fromResponseHeaderString(_xhr.getAllResponseHeaders());
                let url = getResponseURL(_xhr);
                let status: number = _xhr.status === 1223 ? 204 : _xhr.status;
                let state:number = _xhr.readyState;
                if (status === 0) {
                    status = body ? 200 : 0;
                }
                var responseOptions = new ResponseOptions({ body, status, headers, url });
                if (isPresent(baseResponseOptions)) {
                    responseOptions = baseResponseOptions.merge(responseOptions);
                }
                let response = new Response(responseOptions);
                //check for the state if not 4 then don't complete the observer
                if(state !== 4){
                    //this will return stream of responses
                    responseObserver.next(response);
                    return;
                }
                else{
                    responseObserver.complete();
                    return;
                }
                responseObserver.error(response);
            };
            // error event handler
            let onError = (err: any) => {
                var responseOptions = new ResponseOptions({ body: err, type: ResponseType.Error });
                if (isPresent(baseResponseOptions)) {
                    responseOptions = baseResponseOptions.merge(responseOptions);
                }
                responseObserver.error(new Response(responseOptions));
            };

            if (isPresent(req.headers)) {
                req.headers.forEach((values, name) => _xhr.setRequestHeader(name, values.join(',')));
            }
            _xhr.addEventListener('progress', onLoad);
            _xhr.addEventListener('load', onLoad);
            _xhr.addEventListener('error', onError);

            _xhr.send(this.request.text());

            return () => {
                _xhr.removeEventListener('progress', onLoad);
                _xhr.removeEventListener('load', onLoad);
                _xhr.removeEventListener('error', onError);
                _xhr.abort();
            };
        });
    }
}
@Injectable()
export class MyBackend implements ConnectionBackend {
  constructor(private _browserXHR: BrowserXhr, private _baseResponseOptions: ResponseOptions) {}
  createConnection(request: Request):MyConnection {
    return new MyConnection(request, this._browserXHR, this._baseResponseOptions);
  }
}

And in the app.component.ts

import {Component, provide} from 'angular2/core';
import {HTTP_PROVIDERS,XHRBackend} from 'angular2/http';
import {MyBackend} from './my_backend';
@Component({
    selector: 'my-app',
    providers:  [
        HTTP_PROVIDERS,
        MyBackend,
        provide(XHRBackend, {useExisting:MyBackend})
    ]
    .
    .
    .

Now calling http.get will return a steam of responses.

Solution 4:

@Bartek Chichoki's answer is correct but it was not working for my case. Adding observe: 'events' did the trick for me

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

Hope it helps

Solution 5:

I strongly recomend using this

https://www.npmjs.com/package/angular-progress-http

otherwise messing around with xhr will make you miss sessions cookies and other stuffs

besides it'll be more portable and way easier to implement