Simple filter on array of RXJS Observable

JavascriptTypescriptAngularRxjsObservable

Javascript Problem Overview


I am starting my project with Angular2 and the developers seem to recommend RXJS Observable instead of Promises.

I have achieved to retrieve a list of elements (epics) from the server. But how can I filter the elments by using for example an id?

The following code is an extraction from my app and shows now the final working solution. Let's hope it helps someone.

@Injectable()
export class EpicService {

  private url = CONFIG.SERVER + '/app/';  // URL to web API

  constructor(private http:Http) {}

  private extractData(res:Response) {
	let body = res.json();
	return body;
  }

  getEpics():Observable<Epic[]> {
	return this.http.get(this.url + "getEpics")
	  .map(this.extractData)
	  .catch(this.handleError);
  }
  
  getEpic(id:string): Observable<Epic> {
	return this.getEpics()
	  .map(epics => epics.filter(epic => epic.id === id)[0]);
  }
}

export class EpicComponent {

  errorMessage:string;
  epics:Epic[];
  epic:Epic;

  constructor(
	private requirementService:EpicService) {
  }

  getEpics() {
	this.requirementService.getEpics()
	  .subscribe(
		epics => this.epics = epics,
		error => this.errorMessage = <any>error);
  }
  
  // actually this should be eventually in another component
  getEpic(id:string) {
	this.requirementService.getEpic(id)
		.subscribe(
		epic => this.epic = epic,
		error => this.errorMessage = <any>error);
  }
}

export class Epic {
  id: string;
  name: string;
}

Thank you in advance for your help.

Javascript Solutions


Solution 1 - Javascript

You'll want to filter the actual array and not the observable wrapped around it. So you'll map the content of the Observable (which is an Epic[]) to a filtered Epic.

getEpic(id: string): Observable<Epic> {
  return this.getEpics()
     .map(epics => epics.filter(epic => epic.id === id)[0]);
}

Then afterwards you can subscribe to getEpic and do whatever you want with it.

Solution 2 - Javascript

You can do this using the flatMap and filter methods of Observable instead of the JS array filter method in map. Something like:

this.getEpics() 
    .flatMap((data) => data.epics) // [{id: 1}, {id: 4}, {id: 3}, ..., {id: N}]
    .filter((epic) => epic.id === id) // checks {id: 1}, then {id: 2}, etc
    .subscribe((result) => ...); // do something epic!!!

flatMap will provide singular indices for filtering and then you can get on with whatever happens next with the results.

If TypeScript throws a error indicating you can't compare a string and a number regardless of your use of == in the filter just add a + before epic.id in the filter, per the Angular docs:

    .flatMap(...)
    .filter((epic) => +epic.id === id) // checks {id: 1}, then {id: 2}, etc
    .subscribe(...)

Example:

https://stackblitz.com/edit/angular-9ehje5?file=src%2Fapp%2Fapp.component.ts

Solution 3 - Javascript

original answer with a fix: Observables are lazy. You have to call subscribe to tell an observable to send its request.

  getEpic(id:number) {
    return this.getEpics()
           .filter(epic => epic.id === id)
           .subscribe(x=>...);
  }

Update to Rxjs 6:

import {filter} from 'rxjs/operators';

getEpic(id:number) {
        return this.getEpics()
               .pipe(filter(epic => epic.id === id))
               .subscribe(x=>...);
      }

Solution 4 - Javascript

You have to subscribe on Observables to get the data, since http calls are async in JavaScript.

getEpic(id: number, callback: (epic: Epic) => void) {
    this.getEpics().subscribe(
        epics: Array<Epic> => {
            let epic: Epic = epics.filter(epic => epic.id === id)[0];
            callback(epic);
        }
    );
}

You can call that method then like this:

this.someService.getEpic(epicId, (epic: Epic) => {
    // do something with it
});

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJohannesView Question on Stackoverflow
Solution 1 - JavascriptLuka JacobowitzView Answer on Stackoverflow
Solution 2 - JavascriptmtpultzView Answer on Stackoverflow
Solution 3 - JavascriptAndrei ZhytkevichView Answer on Stackoverflow
Solution 4 - JavascriptrinukkusuView Answer on Stackoverflow