matrmawi
matrmawi

Reputation: 121

angular2 http requist handling chunke of responses

While I'm experimenting with angular2 a small obstacle came up:

Now when I tried to get the same functionality using angular2 http.get(), I couldn't output the results as they arrive from the server! instead the results are shown by angular at the end of the process after receiving the last response.
I think the rxjs Observer object is buffering the responses!.

So how can I change this behavior?

here is my php code, testing.php:

echo date('H:i:s')." Loading data!";
ob_flush();
flush();
sleep(5);
echo "Ready to run!";

here is my angular2 code:

template: `
    <div>
        <h3>experimenting!</h3>
        <button (click)="callServer()">run the test</button>
        <div>the server says: {{msg}}</div>
    </div>`
export class AppComponent {
  msg:any;
  constructor (private http:Http){}
  callServer(){
    this.http.get("localhost/testing.php")
             .subscribe(res=> this.msg= res.text());
  }
}

When I run this code it shows after 5 seconds: (19:59:47 Loading data!Ready to run!).

Upvotes: 0

Views: 557

Answers (2)

matrmawi
matrmawi

Reputation: 121

After studying rxjs and reading Angular2 source code, I came up with this solution

I found it's better to make custom_backend, I think this is the recommended approach by angular Dev team.

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);
  }
}

In the main component we have to provide the custom bakend like this:

providers:  [
    HTTP_PROVIDERS,
    PostSrevice,
    MyBackend,
    provide(XHRBackend, {useExisting:MyBackend})
]

Now when we use http.get() it will return a stream of Observable

Upvotes: 0

Thierry Templier
Thierry Templier

Reputation: 202286

You need to extend the BrowserXhr class to do that in order to configure the low level XHR object used:

@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:

Upvotes: 1

Related Questions