HTTP: Angular 2+TS How to use Observables in HTTP - http

I found an example from angular.io. This example is very similar to my app, with same kind of methods. This example is using Promises, but I'm using Observables. If I use this example as a reference, I have every method working in my app, except the getHero method in the service, and the ngOnInit in the HeroDetailComponent. So I'm wondering if someone can help and convert this method to an observable, because I'm having trouble with the syntax. Here is the codes I need converted to Observable and the plunker
//HeroService
getHero(id: number) { // my id is String
return this.getHeroes()
.then(heroes => heroes.filter(hero => hero.id === id)[0]);
}
//HeroDetailComponent
ngOnInit() {
if (this.routeParams.get('id') !== null) {
let id = +this.routeParams.get('id');
this.navigated = true;
this.heroService.getHero(id)
.then(hero => this.hero = hero);
} else {
this.navigated = false;
this.hero = new Hero();
}
}
So I want something like this:
//HeroService
public getHero(id: string) {
return this.getHeroes()
.subscribe(heroes => this.heroes.filter(hero => heroes.id === id)[0]); //BTW, what does this [0] mean??
}
EDIT: I had to actually retrieve the list directly, it didn't work with return this.heroes as suggested in answers below. Working example:
public getById(id: string) {
//return this.getHeroes() <---- didn't work
return this.http.get('someUrl') // WORKS!
.map(heroes => this.heroes.filter(hero => hero.id === id)[0]);
}
Now I'm still having trouble with my ngOnit, and I can't really understand why!
ngOnInit(){
let id = this._routeParams.get('id');
this.heroService.getById(id)
//console.log("retrieved id: ",id ) <----- gives correct id!
.subscribe(hero => this.hero = hero);
//console.log("hero: ", this.hero); <----- gives undefined!
}
EDIT2, still getting undefined when trying to move to the detail page :( I think you had one bracket to much in your answer, tried to look and get the correct places for the brackets?
ngOnInit(){
let id = this._routeParams.get('id');
this.heroService.getById(id)
.subscribe(heroes => {
// this code is executed when the response from the server arrives
this.hero = hero
});
// code here is executed before code from the server arrives
// even though it is written below
}

If you call subscribe() on an Observable a Subscription is returned. You can't call subscribe() on a subscription.
Instead use just an operator (map()) and use subscribe() on the call site:
public getHero(id: string) {
return this.getHeroes()
.map(heroes => this.heroes.filter(hero => heroes.id === id)[0]);
}
ngOnInit(){
let id = this._routeParams.get('id');
this.heroService.getHero(id)
.subscribe(hero => this.hero = hero);
}
In contrary to subscribe(), map() also operates on an Observable but also returns an Observable.
[0] means to just take the first item of the filtered heroes.
update
ngOnInit(){
let id = this._routeParams.get('id');
this._searchService.getById(id)
.subscribe(searchCase => {
// this code is executed when the response from the server arrives
this.searchCase = searchCase;
console.log("id: ", this.searchCase);
});
// code here is executed before code from the server arrives
// event though it is written below
}
This code is a function
searchCase => {
// this code is executed when the response from the server arrives
this.searchCase = searchCase);
console.log("id: ", this.searchCase);
}
that is passed to subscribe() and the Observable calls this function when it has new data for the subscriber. Therefore this code is not executed immediately but only when the observable emits new data.
Code that comes after subscribe() is executed immediately and therefore before above function and therefore this.searchCase does not yet have a value.

This is a way you can do it:
//HeroService
public getHero(id: string) {
return this.getHeroes()
.map(heroes => this.heroes.filter(hero => heroes.id === id)[0]);
}
//HeroDetailComponent
ngOnInit(){
let id = this._routeParams.get('id');
this.heroService.getHero(id)
.subscribe(hero => {
// your code here
});
}
The [0] is an array accessor. You're selecting the first element on array index 0 with it. You need this, because Array.filter() returns a new array with the filtered values, but you only want one hero.

Related

How to return boolean in Angular

I have a backend method called LikeExists() to verify if a certain user has liked a certain post.
public async Task<bool> LikeExists(int postId)
{
var post = await _postRepository.GetPostByIdAsync(postId);
var user = await _userRepository.GetUserByUsernameAsync(User.GetUsername());
if (_context.Likes.Where(i => i.PostId == post.Id && i.UserId == user.Id).FirstOrDefault() != null) return true;
return false;
}
The method works fine in Postman, but it does not do the job in Angular. If a user presses a like button I first want to check if this user has already liked this post. If he has, he will unlike it and the like will be deleted from the database. If he hasn't liked it, he will like it and the like will be saved in the database.
likeExists(){
this.postService.likeExists(this.post.id).subscribe((response: boolean) =>{
this.like = response;
});
}
likePost() {
if(this.likeExists){
this.postService.likePost(this.post.id, this.model).subscribe((response: Like) => {
this.likee = response;
console.log(response);
this.toastr.success('Liked');
}, error => {
console.log(error);
this.toastr.error(error.error);
});
} else {
this.postService.deleteLike(this.post.id).subscribe(() => {
this.toastr.success('Unliked');
}, error => {
console.log(error);
})
}
}
The problem is it always enters the if{} clause and never the else{} clause. The method below returns an Observable. I think the problem is that it must return a boolean. How can I make this work?
This is the method in the postService:
likeExists(postId: number) {
return this.http.get(this.baseUrl + 'like/exists/' + postId);
}
Try avoiding nested subscription since it will result in an unreadable and hard to maintain code, use rxjs pipes with operators instead, try something like this:
let id= this.post.id;
let likeExists$ = this.postService.likeExists(id);
likeExists$
.pipe(
switchMap(likeExists => {
if (likeExists) {
// delete like
return this.postService.deleteLike(id);
}
// otherwise addlike
return this.postService.addLike(id);
})
).subscribe(
res=> this.toastr.success('Success'),
err=> this.toastr.error('Failed')
);
or even shorter
let id= this.post.id;
let likeExists$ = this.postService.likeExists(id);
likeExists$
.pipe(switchMap(
liked => liked ? this.postService.deleteLike(id) : this.postService.addLike(id)}))
.subscribe(
res=> this.toastr.success('Success'),
err=> this.toastr.error('Failed')
);
The problem is that this.http.get is asynchronous, which means that likeExists returns before this.like is being set. You need to wait for the value to be returned in your observable. Refactor your code to something along these lines:
likePost() {
// Check to see if like exists and wait for response from server
this.postService.likeExists(this.post.id).subscribe((response: boolean) => {
this.like = response;
if (this.like) {
this.postService.likePost(this.post.id, this.model).subscribe((response: Like) => {
this.likee = response;
console.log(response);
this.toastr.success('Liked');
}, error => {
console.log(error);
this.toastr.error(error.error);
});
} else {
this.postService.deleteLike(this.post.id).subscribe(() => {
this.toastr.success('Unliked');
}, error => {
console.log(error);
})
}
});
}
Also, this is a nice guide to asynchronous concepts in general. And the RxJS docs have a bunch of helpful information to get started.

Async Await with four nested loops

I am currently trying to return a JSON object array that requires me to do one asynchronous function and then four nested asynchronous map functions in order to populate an array of entities. Basically, each user has an array of orders, each order has an array of items, each item has an array of options and each option has an array of values. I am using loopback4 framework and therefore cannot do res.send once all things have been populated. The function seems to return on the first await, but any await after that, it does not wait on it and instead runs to the end of the function. I have tried using Promises and .thens(), but cannot seem to figure out how to populate each entity fully nested, and then return the array of populated entities. I keep getting an empty array. Below is only one nest of maps, but I cannot get it to even populate up to the first nest and return this, so I decided not to go any further. This is the code:
async getUserOrders2(#param.path.number('id') id: number): Promise<any> {
if ( !this.user) {
throw new HttpErrors.Unauthorized(AuthErrorKeys.ClientInvalid);
}
else if (this.user.id != id) {
throw new HttpErrors.Unauthorized(AuthErrorKeys.ClientInvalid);
}
else {
let restaurantId = this.user.restaurantId
let orderFrameArray = new Array<OrderFrame>()
return this.restaurantRepository.orders(restaurantId as string).find()
.then(async orders => {
orders.map(async (val, key)=> {
let orderFrame = new OrderFrame(val)
orderFrame.itemArray = await this.orderRepository.orderItems(val.id).find()
orderFrameArray.push(orderFrame)
})
orderFrameArray = await Promise.all(orderFrameArray)
return orderFrameArray
})
}
}
The function is returning before the orderFrameArray has been populated. I need four nested map loops and this first one is not working, so I am not sure how to do the rest. Any help would be extremely appreciated.
Based on #Tomalaks solution I tried the following, but its still only returning the top level array and nothing is nested:
async getUserOrders2(#param.path.number('id') id: number): Promise<any> {
if ( !this.user) {
throw new HttpErrors.Unauthorized(AuthErrorKeys.ClientInvalid);
}
else if (this.user.id != id) {
throw new HttpErrors.Unauthorized(AuthErrorKeys.ClientInvalid);
}
else {
let restaurantId = this.user.restaurantId
let orderFrameArray = new Array<OrderFrame>()
return this.restaurantRepository.orders(restaurantId as string).find()
.then(orders => {Promise.all(orders.map(
order => {
let orderFrame = new OrderFrame(order)
orderFrame.itemArray = new Array<Item>()
this.orderRepository.orderItems(order.id).find()
.then(orderItems => Promise.all(orderItems.map(
orderItem => {
let itemFrame = new Item(orderItem)
itemFrame.options = new Array<Option>()
this.orderItemRepository.orderItemOptions(orderItem.id).find()
.then(orderItemOptions => Promise.all(orderItemOptions.map(
orderItemOption => {
let optionFrame = new Option(orderItemOption)
optionFrame.values = new Array<Value>()
this.orderItemOptionRepository.orderItemOptionValues(orderItemOption.id).find()
.then(orderItemOptionValues => Promise.all(orderItemOptionValues.map(
orderItemOptionValue => {
let valueFrame = new Value(orderItemOptionValue)
optionFrame.values.push(valueFrame)})))
itemFrame.options.push(optionFrame)})))
orderFrame.itemArray.push(itemFrame)})))
orderFrameArray.push(orderFrame)}))
return orderFrameArray})
}
}
I apologize for the formatting I wasn't sure how best to format it. Is there something else I'm doing wrong?
Thanks to everyone for their response. The answer that was posted by #Tomalak was correct. I just had to surround the entire function in brackets, and put a .then to return the populated entity I had made
You only need to use async when you are using await in the same function. If there's await in a nested function, the parent function does not need async.
However, in your case, there is no function that should be made async in the first place.
There is no benefit in awaiting any results in your function, because no code inside depends on any intermediary result. Just return the promises as you get them.
There's no need for intermediary result variables like orderFrameArray, you're making things harder than they are with your approach of awaiting individual orders and pushing them to a top-level variable.
Using await in a loop like you do inside your .map() call is bad for performance. You are basically serializing database access this way – the next query will only be sent after the current one has returned. This kind of daisy-chaining nullifies the database's ability to process multiple concurrent requests.
getUserOrders2 is not Promise<any>, it's Promise<Array<OrderFrame>>.
throw terminates the function anyway, you can do multiple checks for error conditions without using else if. This reduces nesting.
So a fully asynchronous function would look like this:
getUserOrders2(#param.path.number('id') id: number): Promise<Array<OrderFrame>> {
if (!this.user) throw new HttpErrors.Unauthorized(AuthErrorKeys.ClientInvalid);
if (this.user.id != id) throw new HttpErrors.Unauthorized(AuthErrorKeys.ClientInvalid);
return this.restaurantRepository
.orders(this.user.restaurantId).find().then(
orders => Promise.all(orders.map(
order => this.orderRepository.orderItems(order.id).find().then(
order => new OrderFrame(order)
)
))
);
}
The async/await equivalent of this function would be more complex.
You then would await the result in the calling code, as you would have to do anyway:
async test() {
const orders = await foo.getUserOrders2(someUserId);
// ...
}
// or
test() {
foo.getUserOrders2(someUserId).then(orders => {
// ...
});
}

Executing a http request based the result of a previous request in angular 2

I would like to handle asynchronous stream when using Observable in Angular 2.
In detail, before every user's request, I need to get service ticket, if that ticket is valid, user can get proper response. Therefore, I have to do http request first before actual request, as below, I call the method called getServiceTicket(), however, because of asynchronous stream, before I get the valid service ticket, the following http request (getDetail) is performed without valid service ticket. So I tried to use flag like isServiceTicket, but I realize it does not guarantee the sequential running of these two methods. I tried to do some research, but I could not find satisfactory answer. If someone has good solution for this, could you give some advice?
getServiceTicket() {
this.userAuthServie.getServiceTicket().subscribe(
(data: string) => {this.serviceTicket = data; this.isServiceTicket = true;}
);
}
getDetail(id: string) {
this.getServiceTicket();
return this.http.get('https://localhost/detail/' + id + '?ticket=' + this.serviceTicket)
.map( (responseData) => {
return <User>responseData.json();
}
);
}
You can change yor code like below:
user:User;
getDetail(id: string) {
this.userAuthServie.getServiceTicket()
.subscribe((data: string) => {
this.serviceTicket = data;
this.isServiceTicket = true;
this.http.get('https://localhost/detail/' + id + '?ticket=' + this.serviceTicket)
.map( (responseData) => {
this.user= <User>responseData.json();
});
});
}
You can place the second function that you want to run in the success part of the subscribe method of the first function.
getServiceTicket() {
this.userAuthServie.getServiceTicket().subscribe(
(data: string) => {
this.serviceTicket = data; this.isServiceTicket = true;
this.getDetail(id);
}
);
}
If you want to wait for the service ticket to be loaded before any "detail requests" are performed, you should wait on the response of service ticket to resolve. There are multiple ways, one way might be to add the details to a queue, or have getDetails wait until the service ticket is loaded.
getServiceTicket() {
// you need to return the observable from service ticket
return this.userAuthServie
.getServiceTicket()
// use map here to only intercept the value.
// remember that you need to subscribe somewhere else
// if called from elsewhere than getDetail
.map((data: string) => {
this.serviceTicket = data;
this.isServiceTicket = true;
return data;
});
}
getDetail(id: string) {
return this.getServiceTicket()
// we switch the observable from ticket to the one from detail using switchMap
.switchMap((data: string) => {
let url = 'https://localhost/detail/' + id + '?ticket=' + this.serviceTicket;
return this.http
.get(url)
.map((responseData) => {
return <User>responseData.json();
});
});
}
}

RxJS wait for second observable then retry original observable on error - TypeScript/Angular 2

I am fairly new to Angular 2, TypeScript and RxJS and I am creating a simple application that leverages the Salesforce Ajax Toolkit connections library.
I am trying to write a handler to catch when a token has expired any time a method from the connections library is called. I have created a service that essentially wraps the connections library to use observables. For example if we look at the insert function I have created my own wrapper function:
public insert(object: sforce.SObject): Observable<any> {
return new Observable(observer => {
// successfully inserted the record
let insertSuccess = (result) => {
observer.next(result);
observer.complete();
}
// An error occured inserting the record
let insertError = (result) => {
// This does not work yet
if (result.faultcode.indexOf('INVALID_SESSION_ID') != -1) {
this.refreshToken();
}
else {
observer.error(result);
}
}
let callback = { onSuccess: insertSuccess, onFailure: insertError };
sforce.connection.create([object], callback);
});
}
I have another function that refreshes the access token:
public refreshToken(): void {
this.loginService.login().subscribe(
response => {
Globals.SESSION_TOKEN = response.access_token;
//initialize the salesforce connection
this.init(Globals.SESSION_TOKEN, this.loginService.AuthParams.SOAP_URL);
},
error => {
}
);
}
I essentially want the original insert function to wait for refreshToken to complete. If it is successful I want to retry the same insert again, otherwise I want the original insert observable to call observer.error.
I've looked into retry and retryWhen, however I haven't been able to figure out how to implement it to wait for the refreshToken() function to complete. Any guidance or advice on this matter would be greatly appreciated. Thank you in advance.
The catch operator accepts a function which processes an error and the source Observable. This means that if you catch an error you can determine whether you want to resubscribe to the original source in the catch block:
public insert(object: sforce.SObject): Observable<any> {
return new Observable(observer => {
// successfully inserted the record
let insertSuccess = (result) => {
observer.next(result);
observer.complete();
}
// An error occured inserting the record
let insertError = (result) => observer.error(result);
let callback = { onSuccess: insertSuccess, onFailure: insertError };
sforce.connection.create([object], callback);
}).catch((err, source) => {
if (err.faultcode.indexOf('INVALID_SESSION_ID') != -1) {
//This waits for the refresh to complete and then resubscribes
//to the source
//If the refresh errors then it will skip the resubscribe
return this.refreshToken().flatMapTo(source);
}
//Non-authentication error
return Observable.throw(err);
});
}
Then make your refreshToken function into something like so:
public refreshToken(): Observable<any> {
return this.loginService.login()
.tap(response => {
Globals.SESSION_TOKEN = response.access_token;
//initialize the salesforce connection
this.init(Globals.SESSION_TOKEN, this.loginService.AuthParams.SOAP_URL);
});
}

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