Late response from API - javascript

I am learning TypeScript and currently I am trying to use google API for books. Issue that I have, is that at the time when expect to get get a response I have null. However, I am logging response and data is presented console. Next strange this that, this data is logged and the end of all my code (like the last thing, but not when I am actually doing logging method).
Is there a way to wait for response? My code parts:
import { Component } from '#angular/core';
import { NavController } from 'ionic-angular';
import { OrderPage } from '../order/order';
import { BooksApiProvider } from '../../providers/books-api/books-api';
#Component({
selector: 'page-book',
templateUrl: 'book.html'
})
export class BookPage {
books:any;
constructor(public navCtrl: NavController, public booksProvide: BooksApiProvider) {
}
moveToOrder() {
debugger;
this.books = this.booksProvide.getBook();
}
}
import { Injectable } from '#angular/core';
import 'rxjs/add/operator/map';
#Injectable()
export class BooksApiProvider {
constructor() {
}
getBook(){
fetch('https://www.googleapis.com/books/v1/volumes?q=isbn:9780439139601')
.then(res => res.json())
.then(data => console.log(data))
}
}

Your getBook() method doesn't return anything, therefore, this.books will always be null.
As you're using fetch here, you can return the promise in your getBook() method allowing you to perform any action with the data retrieved.
getBook(): Promise<any> {
return fetch('https://www.googleapis.com/books/v1/volumes?q=isbn:9780439139601')
.then(res => res.json())
.then(data => {
console.log(data);
return Promise.resolve(data);
});
}
Then you can call .then on getBook() to do whatever you want with the data.
(Assign the data to this.books for example)
this.booksProvide.getBook()
.then(data => this.books = data);
You're using Angular thought, you should take a look at the HttpClient. It's pretty much the same as fetch unless it deals with RxJS and Observables which are widely used in Angular.

Related

TypeScript : Value assigned inside HTTPClient scope is not accessible from outside (within the same function)

On Load, I'm calling the function getData() which will read & map the response to Data[] model and returns back the result. But the function returning undefined object though it's value is assigned.
import { HttpClient} from '#angular/common/http';
import {Data} from "../model";
export class Service {
constructor(private http: HttpClient) {}
getData():Data[]{
var data: Data[];
this.http.get(url,{ responseType: 'blob'}).subscribe(response => {
response.text().then(value => {
data = <Data[]>JSON.parse(value);
console.log(data); ----> {Printing the values as expected i.e., Data Array}
});
});
console.log(data); ----> {Printing Undefined}
return testData;
}
onLoad(){
var data:Data[] = this.getData();
console.log(data) ---------> {Printing Undefined}
}
}
Data Class :
export class Data{
id:number;
name:string;
value:string;
}
Http calls are async calls. Subsequent call will get executed and will not wait for the promise to be resolve.
Your dependent code should be written once it resolved.
I'll just show what I would like to do, here's a little bit reconstruction for your codes:
import {from, Observable} from 'rxjs'; // <= several imports you might care
import {map, switchMap} from 'rxjs/operators'; // <= several imports you might care
export class Service {
constructor(private http: HttpClient) {}
getData(): Observable<Data[]> {
return this.http.get(someurl, {responseType: 'blob'})
.pipe(
switchMap((blob: Blob) => {
return from(blob.text());
}),
map(text => {
return JSON.parse(text) as Data[];
})
);
}
onLoad(): void {
this.getData().subscribe((data: Data[]) => {
console.log(data); // <=== this is where you can log data
});
}
}
The basic idea is you should return a aync(aka Observable) result for your http request, and pipe the first result(type of Blob) to another Observable(which is built from a promise) of string value, then map it to a Data array at last, in onLoad you should subscribe the async result and check the data in subscription. In a word, you should keep the data flow asynchronously until it is been resolved

Testing Service Angular

I am facing a problem with I have no clue how to solve it beacause I am so noobie with testing in front.
Right now I am testing a service that have this code:
import { Injectable } from '#angular/core';
import { EndpointLocatorService } from './endpointLocator.service';
import { Http, Headers } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
#Injectable()
export class CancelAppService {
constructor(private readonly http: Http,
private readonly _endPointLocator: EndpointLocatorService) { }
getScopeSignature(signatureToken: string) {
const url = this._endPointLocator.locate('cancelApp');
const language = sessionStorage.getItem('languageSession');
const headers = new Headers({
'Content-Type': 'application/json',
'Accept-Language': language,
'X-B3-TraceId': sessionStorage.getItem('requestID')
});
const body = JSON.stringify({ 'signatureToken': signatureToken });
return this.http.post(url, body, { headers }).map(data => {
return data.json();
}).catch((error: any) => {
return Observable.throw(new Error(error.status));
});
}
}
The testing file contains this:
import { TestBed } from '#angular/core/testing';
import { CancelAppService } from './cancelApp.service';
import { HttpModule } from '#angular/http';
import { EndpointLocatorService } from './endpointLocator.service';
import { AppConfig } from '../app.config';
import 'jasmine';
fdescribe('CancelAppService', () => {
let cancelService: CancelAppService; // Add this
let endLocator: EndpointLocatorService;
const mockData = {
"signature_token": "returnedToken"
};
const body = JSON.stringify({ 'signatureToken': 'givenToken' });
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
HttpModule
],
providers: [
CancelAppService,
AppConfig,
EndpointLocatorService
]
});
cancelService = TestBed.get(CancelAppService); // Add this
endLocator = TestBed.get(EndpointLocatorService);
});
it('should be created', () => { // Remove inject()
expect(cancelService).toBeDefined();
});
it('should call the service', () => {
spyOn(endLocator, 'locate').and.returnValue('someUrl');
spyOn(cancelService, 'getScopeSignature').and.callThrough();
cancelService.getScopeSignature(body);
expect(cancelService.getScopeSignature).toHaveBeenCalled();
});
it('should return a object', () => {
(done: DoneFn) => {
spyOn(endLocator, 'locate').and.returnValue('someUrl');
spyOn(cancelService, 'getScopeSignature').and.returnValue(mockData);
cancelService.getScopeSignature(body).subscribe(data => {
expect(data).toEqual(mockData);
done();
});
}
});
});
The problem is that, when I try to test the returned data map, appears me like a success test, but the coverage says that I have no coveraged the lines of map, and catch.
Any idea what am I doing wrong? and how to solve it?
Thank you so much!!
You are mocking the service being tested, you should mock the service being used by your service EndpointLocatorService and maybe even HttpClient to minimize mocking. You can verify that by stepping through your code.
Let me explain in the code why those lines are not hit.
// In this example, the lines are hit, but after the test exited.
it('should call the service', () => {
spyOn(endLocator, 'locate').and.returnValue('someUrl');
spyOn(cancelService, 'getScopeSignature').and.callThrough();
// Here you are calling a method yourself and checking that it was
// called, it doesn't make sense, this test should make sure
// You should check that your endLocator was called.
// Note that because you are not waiting for the promise to resolve,
// The test finishes before either callback is handled.
cancelService.getScopeSignature(body);
expect(cancelService.getScopeSignature).toHaveBeenCalled();
});
// In this example the lines in your test are never hit because you
// mocked it
it('should return a object', () => {
(done: DoneFn) => {
spyOn(endLocator, 'locate').and.returnValue('someUrl');
// Here you are mocking getScopeSignature completely so it doesn't
// Run any of your lines that you are trying to test
// It just tests that you mocked it correctly
spyOn(cancelService, 'getScopeSignature').and.returnValue(mockData);
cancelService.getScopeSignature(body).subscribe(data => {
expect(data).toEqual(mockData);
done();
});
}
});
I can't rewrite your test so it actually tests it because it depends on how the EndpointLocatorService works and you may choose to mock it at that level also. I typically choose to mock at the HttpClient level since I can copy paste responses from the network tab.
The point is that you need to mock services used by your service, not the service itself. You have the option to mock the direct services used by your component, or you can mock at a deeper level. For example, your EndpointLocatorService may need to call HttpClient and you could choose to mock either service.
See https://angular.io/guide/testing#httpclienttestingmodule and https://alligator.io/angular/testing-httpclient/

Observable.interval AJAX Call - How to Make Call Before Interval

I have a polling set up to make AJAX calls on an interval using an Observable. Here's my code:
import { Injectable } from '#angular/core';
import { Http, Response } from "#angular/http";
import { Observable } from "rxjs/Rx";
import 'rxjs/add/operator/map';
#Injectable()
export class HttpAPIService {
metrics: Object;
constructor(private http: Http) {
// https://thinkster.io/tutorials/angular-2-http
this.getMetrics('https://jsonplaceholder.typicode.com/posts/1/comments').subscribe(data => this.metrics = data);
}
getMetrics(url: string) {
return Observable.interval(5000)
.switchMap(() => this.http.get(url))
.map((res: Response) => res.json());
}
}
The code waits 5 seconds before doing the first AJAX call. How can I change it so that the first AJAX call happens immediately? Also, if I use the following command in another module, will the subscription make my variable data-binded to the API call that is being called every 5 seconds?
this.variableIWantToBind = this.httpAPIService.getMetrics('https://jsonplaceholder.typicode.com/posts/1/comments').subscribe(data => console.log(data));
Use .startWith(0) as #Zircon pointed out

Angular JS 2.0 getting JSON data and displaying with Ng for

I am trying to loop through JSON data and display it in angular. so far i can display in the console with the use of a function, but not bind it to a div or the loop. iv'e been looking through other posts, but their http requests don't work, this is the only one that does, i am assuming its something to do with the .map line any help or point in the right direction would be appreciated.
//footer.component.ts
import { Component } from '#angular/core';
import { Injectable } from '#angular/core';
import { Http, Response, Headers, RequestOptions } from '#angular/http';
import {Observable} from 'rxjs/Rx';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
#Component({
selector: 'main-footer',
templateUrl: 'footer.html'
// styleUrls: ['footer/footer.css']
})
#Injectable()
export class FooterComponent{
constructor (public http: Http) {}
getJson() {
this.http.get('app/testjson.json')
.map(res => res.text())
.subscribe(
data => this.showArray(data),
err => this.handleError(err),
() => console.log('Random Quote Complete')
);
}
//error handling
private handleError(error: Response) {
console.error(error);
return Observable.throw(error.json().error || 'Server error');
}
//pass the data to console
showArray(data){
console.log(data);
}
}
This is where i am not sure how to bind the data
// footer.html
<button (click)="getJson()">Get JSON!</button>
<div *ngFor="let key of data">{{key.league}}</div>
In the .map line you should get the json, since I presume that your data is in json format.
.map(res => res.json())
And to show your data, you need to store it first. I would suggest that you create a local variable to save it in and then bind that to the NgFor. For example:
let dataToShow = [];
getJson() {
this.http.get('app/testjson.json')
.map(res => res.text())
.subscribe(
data => this.dataToShow = data,
err => this.handleError(err),
() => console.log('Random Quote Complete')
);
}
And then you use "dataToShow" in the NgFor instead.
<div *ngFor="let key of dataToShow">{{key.league}}</div>

How to synchronise Angular2 http get?

I understand using observable I can execute a method when the request is completed, but how can i wait till a http get is completed and return the response using in ng2 http?
getAllUser(): Array<UserDTO> {
this.value = new Array<UserDTO>();
this.http.get("MY_URL")
.map(res => res.json())
.subscribe(
data => this.value = data,
err => console.log(err),
() => console.log("Completed")
);
return this.value;
}
the "value" will is null when its returned because get is async..
your service class: /project/app/services/sampleservice.ts
#Injectable()
export class SampleService {
constructor(private http: Http) {
}
private createAuthorizationHeader() {
return new Headers({'Authorization': 'Basic ZXBossffDFC++=='});
}
getAll(): Observable<any[]> {
const url='';
const active = 'status/active';
const header = { headers: this.createAuthorizationHeader() };
return this.http.get(url + active, header)
.map(
res => {
return res.json();
});
}
}
your component: /project/app/components/samplecomponent.ts
export class SampleComponent implements OnInit {
constructor(private sampleservice: SampleService) {
}
ngOnInit() {
this.dataset();
}
dataset(){
this.sampleservice.getAll().subscribe(
(res) => {
// map Your response with model class
// do Stuff Here or create method
this.create(res);
},
(err) => { }
);
}
create(data){
// do Your Stuff Here
}
}
By looking at the angular source (https://github.com/angular/angular/blob/master/packages/http/src/backends/xhr_backend.ts#L46), it is apparent that the async attribute of the XMLHttpRequest is not getting used. The third parameter of XMLHttpRequest needs to be set to "false" for synchronous requests.
Please find code for your problem
Below is component and service file.And Code is Working fine for synchornize
import { Component, OnInit } from '#angular/core';
import { LoginserviceService } from '../loginservice.service';
#Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
model:any={};
constructor(private service : LoginserviceService) {
}
ngOnInit() {
}
save() {
this.service.callService(this.model.userName,this.model.passWord).
subscribe(
success => {
if(success) {
console.log("login Successfully done---------------------------- -");
this.model.success = "Login Successfully done";
}},
error => console.log("login did not work!")
);
}
}
Below is service file..
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import { UserData } from './UserData';
import 'rxjs/add/operator/map'
import 'rxjs/add/operator/toPromise'
import {Observable} from 'rxjs/Rx'
#Injectable()
export class LoginserviceService {
userData = new UserData('','');
constructor(private http:Http) { }
callService(username:string,passwrod:string):Observable<boolean> {
var flag : boolean;
return (this.http.get('http://localhost:4200/data.json').
map(response => response.json())).
map(data => {
this.userData = data;
return this.loginAuthentication(username,passwrod);
});
}
loginAuthentication(username:string,passwrod:string):boolean{
if(username==this.userData.username && passwrod==this.userData.password){
console.log("Authentication successfully")
return true;
}else{
return false;
}
}
}
Another solution would be to implement a priority queue of sort.
From what I understand http requests do not get executed until you add subscribers. Therefore, you can do something like this:
Observable<Response> observable = http.get("/api/path", new RequestOptions({}));
requestPriorityQueue.add(HttpPriorityQueue.PRIORITY_HIGHEST, observable,
successResponse => { /* Handle code */ },
errorResponse => { /* Handle error */ });
This assumes that requestPriorityQueue is a service injected into your component. The priority queue would store entries in an array in the following format:
Array<{
observable: Observable<Response>,
successCallback: Function,
errorCallback: Function
}>
You would have to decide how the elements are added to your array. Finally, the following will happen in the background:
// HttpPriorityQueue#processQueue() called at a set interval to automatically process queue entries
The processQueue method would do something like this:
protected processQueue() {
if (this.queueIsBusy()) {
return;
}
let entry: {} = getNextEntry();
let observable: Observable<Response> = entry.observable;
this.setQueueToBusy(); // Sets queue to busy and triggers an internal request timeout counter.
observable.subscribe()
.map(response => {
this.setQueueToReady();
entry.successCallback(response);
})
.catch(error => {
this.setQueueToReady();
entry.errorCallback(error);
});
}
If you are able to add new dependencies you could try using the following NPM package: async-priority-queue
I looked and I couldn't find any way to make an HTTP call sync instead of async.
So the only way around this: wrap your call in a while loop with a flag. Don't let the code continue until that flag has "continue" value.
Pseudo code as follows:
let letsContinue = false;
//Call your Async Function
this.myAsyncFunc().subscribe(data => {
letsContinue = true;
};
while (!letsContinue) {
console.log('... log flooding.. while we wait..a setimeout might be better');
}
as you see, first callback waiting for a data from request and
there you can go on with your logic (or use the third one)
example:
.. subscribe( data => {
this.value = data;
doSomeOperation;
},
error => console.log(error),
() => {console.log("Completed");
or do operations here..;
}
});
How about to use $.ajax(of jQuery) or XMLHttpRequest.
It can use as asynchornize.
You should not try to make http calls behave synchronously. Never a good idea.
Coming to your getAllUser implementation it should return an observable from the function and the calling code should subscribe instead of you creating a subscription inside the method itself.
Something like
getAllUser(): Observable<UserDTO> {
return this.http.get("MY_URL")
.map(res => res.json());
}
In you calling code, you should subscribe and do whatever you want.

Categories