Http request made multiple times in Angular2 service - http

I have created a service that makes a simple GET request:
private accountObservable = null;
constructor(private _http: Http) {
}
getAccount () {
// If we have account cached, use it instead
if (this.accountObservable === null) {
this.accountObservable = this._http.get('http://localhost/api/account')
.map(res => <Account> res.json().data)
.catch(this.handleError);
}
return this.accountObservable;
}
I have added that service in my bootstrap function to provide it globally (my hope is to provide the same instance to all components):
provide(AccountService, { useClass: AccountService })
The problem is when I call this service in different components, a GET request is made every time. So if I add it to 3 components, 3 GET requests will be made even though I check if an observable already exist.
ngOnInit() {
this._accountService.getAccount().subscribe(
account => this.account = account,
error => this.errorMessage = <any>error
);
}
How can I prevent the GET request to be made multiple times?

Use Observable.share():
if (this.accountObservable === null) {
this.accountObservable = this._http.get('./data/data.json')
.share()
.map(res => res.json())
.catch(this.handleError);
}
Plunker
In the Plunker, AppComponent and Component2 both call getAccount().subscribe() twice.
With share(), the Chrome Developer tools Network tab shows one HTTP request for data.json. With share() commented out, there are 4 requests.

There are two types of observables.
Cold Observable : each subscriber receive all the events ( from the begining )
Hot observable : each subscriber receive the events that are emited after subscription.
Cold Observables are the default one. That's what the WS calling is triggered many times.
To make an Observable Hot you have to use following Rx's operators chain :
.publish().refCount()
In your case :
getAccount () {
let accountObservable = this._http.get('http://localhost/api/account')
.map(res => <Account> res.json().data)
.catch(this.handleError);
return accountObservable.publish().refCount();
}

In my case it was because of form post and button clik was set to same listener

The updated solution is:
1) Change your getAccount() method to use share:
getAccount () {
// If we have account cached, use it instead
if (this.accountObservable === null) {
this.accountObservable = this._http.get('http://localhost/api/account')
.pipe(share())
.map(res => <Account> res.json().data)
.catch(this.handleError);
}
return this.accountObservable;
}
2) Add import { share } from 'rxjs/operators'; to the top of your .ts file to get rid of the error on share.

Related

Firebase: Get notified just after the firebase-function trigger promise completed

In my app I pushed some object to my firebase-database and immediately after that (after the then-promise fully filled) I fetch the object (with the returned key) from the database (with the on-value method).
In addition, I make some changes on the pushed object using the firebase-functions.
How can I receive the object (in the app) just after the changes and not before? (like other ordinary backend services)
I hope this helps you, I have not tested this piece of code but it should help you in the right direction.
Also dont use this exact code in production, there is plenty room for improvement, this is just an example code.
exports.testFunction = functions.https.onRequest((req, res) => {
if (req && req.body) {
if (
req.body.hasOwnProperty('name') &&
req.body.hasOwnProperty('age')
) {
const person = {
name: req.body['name'],
age: req.body['age']
}
// Make some changes to the person object
person['hobby'] = 'Programmer';
// Add object to FireStore
admin
.firestore()
.collection('/persons')
.add(person)
.then((success) => {
// Return the added & changed person
res.status(201).send(JSON.stringify(person));
})
.catch((error) => {
// Error
console.error('Something broke', error)
res.status(500).send();
});
}
else {
// Error
res.status(500).send({err: 'Missing property'});
}
}
else {
// Error
res.status(500).send({err: 'Missing something'});
}
});

Angular 2 loads HTTP request 5 times even though i am calling only once

For example:
ngOnInit()
{
this.user = new User();
this.result = new Result();
this.route.params
.switchMap((params: Params) => this.user.id = params['id'])
.subscribe((user: User) => this.getUser());
}
getUser()
{
this.result.updateInfo("getting records...")
this.user.id = this.user.id.toLowerCase();
this._serivce.getUser(this.user.id).subscribe(userobj =>
{
this.user = userobj;
console.log(userobj);//five times
},error => this.result.updateError(error));
}
the useronj gets printed five times in console.
Service:
getUser(id: string) {
return this.http.get(<url>, { headers: this.headers })
.map(this.extractData)
.catch(this.handleError);
}
Update: Only the ngOnInit calls getUser, that too only once!
Is there a reason why you are using switchMap? You are also doing a lot of subscribing there, which could cause the issue. What you are trying to achieve, which seems to be getting a user by the id parameter, so this should do the trick:
ngOnInit() {
this.route.params
.subscribe(params => {
let id = params['id'];
this._service.getUser(id.toLowerCase())
.subscribe(userobj => {
console.log(userObj);
});
});
}
EDIT: As a sidenote, per comment from OP (since comments tend to go unnoticed) Turned out that switchMap was the sole issue in this case. So my suspicion that all the subscribing could be an additional cause for this issue, turned out to not be true :)

Angular2 http get result empty for custom post types wordpress toolset

I am using wordpress rest api and angular2 to build simple one page app. I created a custom post type with toolset. All is working fine and I can see the data returning fine.
http://tncorp.biffapps.com/wp-json/wp/v2/downloads/?filter[product]=Pico
Screenshot
When try to request the data in my Angular application, the response body is empty.
Response
_body
:
"[]"
headers
:
Headers
ok
:
true
status
:
200
statusText
Does anyone know why this is happing?
Code for the http get:
getProductDownloads($id): Observable<any> {
this.ProductsDownloads = this.http.get(this.wpurl + 'downloads/?filter[product]='+$id)
.map(res => {
console.log('GET DOWNLOAD',res)
return this.ProductsDownloads = this.extractData(res)
})
.catch(this.handleError);
return this.ProductsDownloads;
}
You want to return the Observable itself:
getProductDownloads($id): Observable<any> {
return this.http.get(this.wpurl + 'downloads/?filter[product]='+$id)
.map(res => {
console.log('GET DOWNLOAD',res)
return this.ProductsDownloads = this.extractData(res)
})
.catch(this.handleError);
}
Then when you're calling it, you subscribe to the Observable.
// I'm using "this" here, but if you have it in a -service- you'd put that there
this.getProductDownloads(id).subscribe(result => {
this.products = result;
});
Also, you could do it the following way and incorporate the | async pipe in your template instead of manually subscribing & unsubscribing.
this.products = this.getProductDownloads(id);
// template:
{{ products | async }}

Angular 2 map http response to instance of class

I'm wondering what the best way is to map the http response from a get request to a class instead of a basic Javascript object.
In my current attempt I simple do new ClassName(data), but there might an obscure Angular specify and completely awesome way to do this that I don't know.
Here's my current code:
getPost(id:number){
return this._http.get(this._postsUrl+'/'+id)
.map(res => new Post(res.json().data))
.do(data => console.log(data))
.catch(this.handleError);
}
I need Post to be a class and not just an interface because I have methods inside.
I followed the HeroTutorial and the http "developer guide" along and in their getHeroes method they do:
getHeroes () {
return this.http.get(this._heroesUrl)
.map(res => <Hero[]> res.json().data)
.catch(this.handleError);
}
I somehow expected the <Hero[]> part to do just that: Take the Hero class and create new instances of it, but my tests show that it doesn't, this is pretty much just for Typescript to know what to expect.
Any ideas ? Thanks!
I think that you could use the map method of JavaScript objects:
getHeroes () {
return this.http.get(this._heroesUrl)
.map(res => {
return res.json().data.map((elt) => {
// Use elt to create an instance of Hero
return new Hero(...);
});
})
.catch(this.handleError);
}
By setting the prototype on the object being returned I was able to get an instance of my class.
getHero () {
return this.http.get(this._heroUrl)
.map(response => {
let res = <Hero> response.json();
Object.setPrototypeOf(res, Hero.prototype);
return res;
})
.catch(this.handleError);
}
My class was very simple with a parameterless constructor so this worked for me. For more complex classes I don't know if this would work.
Note: the above code is for getHero() not getHeroes(). I would assume I could do the same thing with a list by setting the prototype on each item in the array but I haven't tried/confirmed that.
Reference: I got the idea for this from this post by BMiner
Good practice is to consume data from GET response using
Observable<Model>
(regarding to Angular documentation https://angular.io/guide/http)
So...
// imports
import {HttpClient} from "#angular/common/http";
// in constructor parameter list
private http: HttpClient
// service method
getHeroes(): Observable<Hero[]> {return this.http.get<Hero[]>({url}, {options});}
You do not need to do anything more. I consider this approach as most friendly.

Angular2 - How to chain async service calls (http requests) in a component?

I have a component which first need to call a service that POST something. Then in the same component I want to wait until the POST is done, to call another service which GETs data.
How can I make the GET call wait for the POST call to finish?
In new-version.component.ts:
private createNewVersion(value) {
...
// create new version, then call on all available versions
// POST call
this._newVersionService.createNewVersion(vnr);
// GET call
this._versionService.getAvailableVersions();
...
}
In new-version.service.ts:
export class NewVersionService {
response$: Subject<any>;
constructor(private _http: Http) {
this.response$ = new BehaviorSubject<any>(null);
}
public createNewVersion(versionNr) {
this._http.post('http://localhost:8080/services/' + versionNr, null, {
method: 'POST',
})
.subscribe(response => {
this.response$.next(response.status);
},
error => console.error(error));
}
Thanks!
When a call returns a Promise chain the calls with
someFunction() {
return returnsPromise()
.then(result => doSomethingNext())
.then(result => doSomethingAfterThat());
}
Ensure you have a return that returns the Promise of that chain so the caller of someFunc() also has a chance to time additional work to execute after doSomethingAfterThat() is completed.
When a call returns an Observable then use the complete callback
someFunction() {
return returnsObservable()
.subscribe(
event => doForEachEvent(),
error => handleError(),
() => doSomethingNext()
.then(result => doSomethingAfterThat());
}
doSomethingNext() is executed after the last event and doSomethingAfterThat() is again chained with then() to show how to mix observable and promise. doSomething().
You should be able to concat to achieve sequence, and reduce to collect the emitted values:
var a = this._newVersionService.createNewVersion(vnr);
var b = this._versionService.getAvailableVersions();
Rx.Observable.concat(a, b).reduce((acc:Array<any>, x:any) => {
acc.push(x); return acc;
}, []).subscribe(t=> {
var firstEmitted = t[0];
var secondEmitted = t[1];
});
You can do like this:
Change createNewVersion to:
public createNewVersion(versionNr) {
return this._http.post('http://localhost:8080/nod_inspection_plugin/services/' + versionNr, null, {
method: 'POST',
});
}
Then in your call:
this._newVersionService.createNewVersion(vnr).subscribe(response=> {
this._versionService.getAvailableVersions();
}, error => console.error(error));
Another way to do the same is to subscribe in the new-version.component.ts and call you GET request from within the POST request i.e check whether your POST request is done Correctly or not
if yes POST is done Properly then call you GET request. As below:
In new-version.component.ts:
private createNewVersion(value) {
...
// create new version, then call on all available versions
// POST call
this._newVersionService.createNewVersion(vnr)
.subscribe((res) => {
if(res){
console.log(res);
if (---Post request done properly check via status or something else here----{
CALL YOUR GET REQUEST HERE.....
// GET call
this._versionService.getAvailableVersions();
}
else {
DO something else whatever you want....
}
}
});
...
}
In new-version.service.ts:
export class NewVersionService {
response$: Subject<any>;
constructor(private _http: Http) {
this.response$ = new BehaviorSubject<any>(null);
}
public createNewVersion(versionNr) {
this._http.post('http://localhost:8080/nod_inspection_plugin/services/' + versionNr, null, {
method: 'POST',
})
.map(response => {
return [{status: response.status, json: response.json()}];
},
error => console.error(error));
}
for more info related to http request you can read here.
Better use switchMap() here.
const versions$ = this._newVersionService.createNewVersion(vnr)
.switchMap(response => this._versionService.getAvailableVersions());
versions$.subscribe(response2 => this.versions = response2)
But the problem will be if you make another POST request before first has been resolved, the previous request will get cancelled.

Resources