subscribing two observables i.e. observable inside another observable HTTP - javascript

I am new to angular. I am trying to post and get data at a time. I am using map and subscribing the observable while the outer observable is subscribed in component.
add(data) {
return this.postModel(`pathHere`, data)
.pipe(map((resp) => {
if(resp.models){
return this.getRoomById(resp.models.id).subscribe();
}
}));
}

I guess you need to use flatMap instead of map as
add(data) {
return this.postModel(`pathHere`, data)
.pipe(flatMap((resp) => {
if(resp.models){
return this.getRoomById(resp.models.id);
}
}));
}

add(data) {
return this.postModel(`pathHere`, data)
.pipe(
switchMap((resp) => {
if (resp.models) {
return this.getRoomById(resp.models.id);
}
return of(resp.models);
})
);
}
You can use switchMap to emit another Observable.
As you used if statement for checking resp.models, you have to handle the case when resp.models is not truthy.

Related

Fetching data from multiple service on single component using RXJS in angular

I have two services with
ser1.
getdata1() {
this.http.get<{message:string,Data1:any}>('http://localhost:3000/api/1')
.pipe(map((data1)=>{
return Data1.Data1.map(data=>{
return {
id: data._id,
data1Title:data1.data1Title,
}
})
})).subscribe((data1) => {
this.data1=data1
this.serv1Subject.next([...this.data1])
})
}
getData1Listener() {
return this.serv1Subject.asObservable()
}
ser2
getdata2() {
this.http.get<{message:string,Data2:any}>('http://localhost:3000/api/2')
.pipe(map((data2)=>{
return Data2.Data2.map(data=>{
return {
id: data._id,
data2Title:data2.data1Title,
}
})
})).subscribe((data2) => {
this.data2=data2
this.serv2Subject.next([...this.data2])
})
}
getData2Listener() {
return this.serv2Subject.asObservable()
}
Now on componentx i need to fetch the data1 and data2 in nginit and after the data is available need to perform an functionY
How can i use subscribe to trigger an functionY?
In componentx.ts
ngOnInit() {
this.Service1OberableSubject = this.serv1.getData1Listener().subscribe((data1) => {
this.data1 = data1;
})
this.Service2OberableSubject = this.serv2.getData2Listener().subscribe((data2) => {
this.data2 = data2;
})
this.serv1.getdata1()
this.serv2.getdata2()
}
Possibly something like this. Use tap to tap into the response and do anything needed.
And forkJoin will merge the response and give it in an array, the first index will the be response of the first observable that was passed in.
getdata1() {
this.http.get<{message:string,Data1:any}>('http://localhost:3000/api/1').pipe(
map((data1)=>{
return Data1.Data1.map(data=>{
return {
id: data._id,
data1Title:data1.data1Title,
}
})
}),
tap((data1)=>{
//save to data1 if needed
this.serv1Subject.next([...this.data1]))
})
)
}
getdata2() {
this.http.get<{message:string,Data2:any}>('http://localhost:3000/api/2').pipe(
map((data2)=>{
return Data2.Data2.map(data=>{
return {
id: data._id,
data2Title:data2.data1Title,
}
})
}),
tap((data2)=>{
//save to data2 if needed
this.serv2Subject.next([...this.data2]))
})
)
}
forkJoin(
getdata1(),
getdata2()
).subscribe((x:any[])=>this.functionY(x));
functionY([a, b]){
console.log({a: a,b: b});
}
You can use forkJoin from rxjs.
import { forkJoin } from 'rxjs';
component.ts
ngOnInit() {
forkJoin([this.serv1.getData1Listener(), this.serv2.getData2Listener()]).subscribe(data => {
this.data1 = data[0];
this.data2 = data[1];
});
}
Here's an example of how you can have if implemented:
import { zip } from 'rxjs';
import { first } from 'rxjs/operators';
ngOnInit() {
this.serv1.getdata1()
this.serv2.getdata2()
zip(this.serv1.getData1Listener(), this.serv2.getData2Listener())
.pipe(first())
.subscribe(([data1, data2]) => {
this.data1 = data1;
this.data2 = data2;
functionY(data1, data2)
})
}
forkJoin should be the best operator in this scenario. It is equally important to understand other higher order operators, this will help to know when to use those.
For e.g.
concatMap — helps to map observables in sequence while waiting for previous observable to complete. The best use case would be a sequential form save.
mergeMap — helps mapping of observables running in parallel. All the previous Observables are kept alive.
switchMap — switchMap is the way to go if we want to cancel previous subscriptions. Type-ahead feature is best implemented using switchMap combined with deboundTime() and distinctUntilChanged().
exhaustMap — used for ignoring newly emitted values till ongoing observable is complete. Best used when we are saving data on click of a button, which has a probability of being clicked multiple times.

how to conditionally subscribe to another observable on success of first observable?

I m tring to use concatmap and complete the observables sequentially when queryParams has id it should only call getbearer tokens or else it should navigate to error with securekey but in reality its going to error page with bearer-token
const result = this.activatedRoute.queryParams.pipe(
concatMap((params) => {
if (params.hasOwnProperty('id')) {
sessionStorage.setItem('secureKey', params.id);
return this.httpServiceService.getBearerTokens();
} else {
this.router.navigate(['error'], {
queryParams: { 'error-key': 'secure-key' },
});
}
})
);
result.subscribe(
(response: any) => {
if (response.access_token) {
sessionStorage.setItem('bearerToken', response.access_token);
this.router.navigate(['summary']);
} else {
this.router.navigate(['error'], {
queryParams: { 'error-key': 'bearer-token' },
});
}
},
(error) => {
this.router.navigate(['error'], {
queryParams: { 'error-key': 'bearer-token' },
});
}
);
I think concatMap is just asking for a race condition (I don't know how often and how easy the query parameters change, though). I would use switchMap instead, because I imagine that the moment we navigate from "?id=alice" to "?id=bob", the matter of Alice's token becomes immaterial.
Generally, conditionally subscribing to another stream should work just fine with switchMap and filter:
firstStream$.pipe(
filter(result => someCondition(result)),
switchMap(result => getAnotherStream(result))
).subscribe(//...
Oh, and by the way, your concatMap returns a stream only if (params.hasOwnProperty('id')). Does that even compile? Return an observable in the else branch as well, it could even be EMPTY, but probably should be throwError.

Rxjs map and filter array- Return single object instead of array of one?

I have a method on my service that gets all Templates, and returns an Observable array of Template objects. I am attempting to add a new method to my service that gets the array of Templates, but filters it to get a specific template by name, in an array.
Is there a way to get this to just return a single Template Observable, rather than an Observable Template array with one item? I am still so new to rxjs syntax that I don't understand how to use things like reduce.
Here's my working method to get all templates.
getAllTemplates(): Observable<Template[]>{
const uri = this.baseService.rootTemplatesUri;
return this.http.get<Template[]>(uri)
.pipe(
catchError((error: HttpErrorResponse) => { return throwError(error); })
);
}
Here's my attempt that is filtering by name, which returns an array with one element if there's a matching name:
getTemplateByName(name:string):Observable<Template[]>{
const uri = this.baseService.rootTemplatesUri;
return this.http.get<Template[]>(uri)
.pipe(
map(templates => {
return templates.filter(template => {
return template.Name === name;
})
}),
catchError((error: HttpErrorResponse) => { return throwError(error); })
);
}
Is there a way to get a single Observable returned by filtering this?
When the map RxJS operator is used, the returned observable has the type returned inside the map operator. The JavaScript filter method returns a new array as stated in its documentation. That's why your getTemplateByName method returns an observable of Template[] elements.
If you need to make the getTemplateByName method return an observable of Template elements, you need to return a single Template element inside your map operator. The JavaScript find method does this as stated in Array​.prototype​.find(). Just use it to achieve the desired behavior.
getTemplateByName(name:string):Observable<Template>{
const uri = this.baseService.rootTemplatesUri;
return this.http.get<Template[]>(uri)
.pipe(
map(templates => {
return templates.find(template => {
return template.Name === name;
})
}),
catchError((error: HttpErrorResponse) => { return throwError(error); })
);
}

Conditionally chain observable

For the following TypeScript (using rxjs):
getRegularData(): Observable<MyData> {
return WS.loadRegularData();
}
getAlternateData(): Observable<MyData> {
return WS.loadAlternateData();
}
how can a new method be implemented to satisfy the following pseudocode:
getData(): Observable<MyData> {
// try to use getRegularData, and return observable for result.
// if getRegularData returns null, get data from getAlternateData()
// instead and return observable for result.
}
There are many ways you can implement this, one would be to use a switchMap that contains your condition:
getData(): Observable<MyData> {
return getRegularData()
.switchMap(data => {
if (data != null) {
return Observable.of(data);
} else {
return getAlternateData();
}
});
}

Angular function in controller not getting called after promise

I am building an Angular 1.5 app using the component structure. After the promise comes back from the $http call in the service, I am trying to call another function to filter the dataset before it is displayed on the UI.
However, the filterApps function is not getting called.
Also...in the filterApps function I am trying to compare to arrays of objects and return back the ones that have the same name. Is this the best way to go about this or is there a cleaner way?
Controller :
import allApps from '../../resources/data/application_data.js';
class HomeController {
/*#ngInject*/
constructor(ItemsService) {
this.itemsService = ItemsService;
this.displayApps = [];
}
$onInit() {
this.itemsService
.getItems()
.success((apps) => this.filterApps(apps));
}
filterApps(siteApps) {
this.displayApps = allApps.applications.filter((app) => {
siteApps.applications.map((siteApp) => {
if(siteApp.name === app.name) {
return app;
}
})
});
}
}
export default HomeController;
I don't see any reason that filterApps method isn't geting call(as you already commented that success function is getting called). I guess you're just checking nothing has been carried in displayApps variable. The real problem is you have not return internal map function result to filter. So that's why nothing gets return.
Code
filterApps(siteApps) {
this.displayApps = allApps.applications.filter((app) => {
//returning map function result.
return siteApps.applications.map((siteApp) => {
if(siteApp.name === app.name) {
return app;
}
})
});
}

Categories

Resources