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

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

Related

Read data from ngrx store directly

I saw in our codebase a lot of places using this pattern
getUrl() : string {
let url = ''
this.store.pipe(selectorConfig, take(1)).subscribe((config) => {
url = config.url
});
return url;
}
Just want to check if this is async? will the selectorConfig emit only when config changes so this is an async call? There are also someone saying ngrx store is a behaviourObject so the moment you subscribe, it emits last value so this effectively is a sync function.
Could anyone confirm which is correct?
Unless you make it asynchronous it's a synchronous operation. As is the Subject.next method.
// Synchronous
getUrl() : string {
let url = ''
console.log('START');
// considering that selectorConfig does not contain anything `asynchronous`, which it should not
this.store.pipe(selectorConfig, take(1)).subscribe((config) => {
url = config.url
console.log('URL:', config.url);
});
console.log('START');
return url;
}
/* OUTPUT
START
URL: <yourUrl>
END
*/
// Asynchronous
import { delay } from 'rxjs/operators';
getUrl() : string {
let url = ''
console.log('START');
// adding a delay operator makes it asynchronous
this.store.pipe(selectorConfig, delay(0), take(1)).subscribe((config) => {
url = config.url
console.log('URL:', config.url);
});
console.log('START');
return url;
}
/* OUTPUT
START
END
URL: <yourUrl>
*/
There's a nice explanation to your question here

Component method fails to assign data that has not yet been fetched by the service

officeView.component.ts
setSelectedPerson(id:number)
{
this.pservice.getPerson(id);
localStorage.setItem("selectedPerson", JSON.stringify(this.pservice.person));
}
person.service.ts
getPerson(id:number)
{
this.http.get(personUrl + id).subscribe(response => this.person = response )
}
person:Person;
I'm executing setSelectedPerson method from OfficeViewComponent and here's what I'm hoping to happen:
I ask PersonService to fetch the data from api and assign it to it's variable - PersonService.person;
Now that the response is assigned to the service person variable, I'm expecting it to be stringified and saved in localStorage.
But here's what actually happens:
I ask PersonService to fetch the data, PersonService reacts and proceeds with the request, but by the time PersonService.getPerson() is finished, localStorage has already attempted to collect the data from PersonService.person, which - at that time - was unassigned.
I know there is a way to wait until the service method finishes it's work, but I don't know exactly what should I use.
Return the subscription from the service and use it to set data inside it. You don't need any variable inside your service.
officeView.component.ts :
setSelectedPerson(id:number){
this.pservice.getPerson(id).subscribe(
response => {
localStorage.setItem("selectedPerson", JSON.stringify(response));
},error => {
console.log('Error :',error.error)
}
)
}
person.service.ts :
getPerson(id:number) : Observable<any>{
return this.http.get(personUrl + id);
}
You're right, you should wait until the result is ready.
By then you can call the setSelectedPerson func.
//service func
getPerson(id:number) {
return this.http.get(personUrl + id);
}
//component func
setSelectedPerson(id:number){
this.pservice.getPerson(id).subscribe(data => {
localStorage.setItem("selectedPerson", JSON.stringify(data ));
});
}
The problem with you code is the early subscribe in service itself, Ideally it should be in the component (at the caller)
officeView.component.ts :
setSelectedPerson( id : number ){
this.pservice.getPerson(id).subscribe(
response => {
localStorage.setItem("selectedPerson", JSON.stringify(response));
},error => {
console.log( 'Error :',error.error )
}
)
}
person.service.ts :
getPerson( id : number ) : Observable< any >{
return this.http.get( personUrl + id );
}
person.service.ts
getPerson(id:number) {
return this.http.get(personUrl + id);
person:Person;
and when you calling the HTTP service you should use subscribe
setSelectedPerson(id:number){
this.pservice.getPerson(id).subscribe(data=>{
console.log(data);
});
}

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

HTTP: Angular 2+TS How to use Observables in 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.

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