Home > front end >  Filter an observable based on value comping from an API call
Filter an observable based on value comping from an API call

Time:12-11

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:

  1. You fetch several items from your API
  2. 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.

  • Related