I'm new to RxJS, any help will be appreciated!
In my component html template, I would like to generate a radio button list.
The list is populated with values from an observable, via the async pipe.
This is the template part for the radio button list:
<div *ngIf="entities$ | async as entities">
<mat-radio-group
aria-labelledby="entities-radio-group-label"
<mat-radio-button *ngFor="let entity of entities" [value]="entity">
{{entity.description}}
</mat-radio-button>
</mat-radio-group>
</div>
On the component class I have an observable that retrieve the entities data, using a service. I also filter the entities, by entity type.
entities$: Observable<Entity[]> = this.eventsService.entities$
.pipe(
map(items => items.filter(item => item.type.toLowerCase().indexOf("set") > -1)),
)
A retrieved entities object have this structure:
[
{"id":"34534534643364",
"type":"SET",
"description":"some description",
"link":"/conf/sets/34534534643364"},
{"id":"5474745744457547",
"type":"SET",
"description":"other description",
"link":"/conf/sets/5474745744457547"}
]
This part works and I can display the "SET" type entities.
But I also need to filter the entities list based on additional value that I need to retrieve with an API call. For each entity in this source observable I need to issue a request that uses the specified entity link
This is how a request looks like (I'm using a service)
restService.call<any>(entityUrl)
.pipe(finalize(()=>this.loading=false))
.subscribe(
apidata => console.log(`data: ${JSON.stringify(apidata)}`),
error => this.alert.error('Failed to retrieve entity: ' error.message)
);
This returns an observable and the data is basically an object like this:
{
"id": "34534534643364",
"name": "some name",
"description": null,
"type": {
"value": "LOGICAL",
"desc": "Logical"
},
"content": {
"value": "IEP",
"desc": "This it the value I need"
},
"status": {
"value": "ACTIVE",
"desc": "Active"
}
}
I need to use the value of "desc", to preform the additional filtering.
I attempted to use a function to preform the additional filtering, and add it to the source observable.
The Observable:
entities$: Observable<Entity[]> = this.eventsService.entities$
.pipe(
tap((items) => console.log("started pipe", items)),
map(items => items.filter(item => item.type.toLowerCase().indexOf("set") > -1)),
tap((items) => console.log("after set filtered pipe", items)),
map(items => items.filter(item => this.descFilter(item.link))),
tap((items) => console.log("after descFilter: ", items)),
tap(() => this.clear())
);
The function:
descFilter(link: string): boolean{
let testedEnt:any = [];
resObs$ = this.restService.call<any>(link)
.pipe(finalize(()=>this.loading=false))
.subscribe(
next => {console.log(`checkTitleSet api call result:, ${JSON.stringify(next)}`);
testedEnt = next.content.desc;
console.log("desc from inside next: ",testedEnt);}, // this retrieved the value
error => this.alert.error('Failed to retrieve entity: ' error.message)
);
console.log("desc: ",testedEnt); // does not retrieve the value (the request did not complete)
if (testedEnt === "some string"){return true;} else {return false;}
}
It did not work, because the api needs also time to process.
I also thought of an additional option:
Use only the api results for my template radio button group. I was able to create an array of observables (all the api results) But I don't know of to use this array in my template.
Any advice will be appreciated!
CodePudding user response:
If I understand correctly, the initial situation is as follows:
- You fetch several items from your API
- For each item you have to fetch a filter object from the API, which decides if the item will be filtered out or not
The main problem with your code is: In order to chain observables you cannot use the map
operator. Instead I suggest to apply switchMap
in combination with forkJoin
.
I suggest to modify your async pipe as follows (for better understanding, please see my comments in the code):
entities$: Observable<Entity[]> = this.eventsService.entities$
.pipe(
tap((items) => console.log("started pipe", items)),
map(items => items.filter(item => item.type.toLowerCase().indexOf("set") > -1)),
tap((items) => console.log("after set filtered pipe", items)),
switchMap(items => {
// Create an array of filter observables that include an api-call
// and return the item itself if the condition is met, or NULL otherwise
const filterObs = items.map(item => this.descFilter(item));
// Use forkJoin to execute the array with the http-requests
return filterObs.length ? forkJoin(filterObs) : of([]);
}),
// Filter out the NULL values:
map(items => items.filter(i => i !== null)),
tap((items) => console.log("after descFilter: ", items)),
tap(() => this.clear())
);
Also the descFilter()
method needs to be changed, otherwise you cannot integrate it into the async pipe:
/* Based on whether the condition is met, the item itself or NULL will be returned */
descFilter(item: Entity): Observable<Entity | null> {
return this.restService.call<any>(item.link).pipe(
map(res => res.content.desc === "some string" ? item : null)
);
}
Important: Eventually you might have to subscribe to entities$
to get the code running.