Dynamic filtering with RxJs and Angular forms — Angular tutorial

Alain Chautard
Angular Training
Published in
4 min readFeb 18, 2019

--

In this tutorial, we’re going to see how to implement a data filter using RxJs operators and some Angular Reactive forms features.

What we want to achieve is relatively simple: We have a list of states on the screen, and we want to filter that list out as the user types some letters in a text input, as illustrated above.

First, we’re going to load the data needed for our example, using the HttpClient to get a list of states from a server. Of course, this is just an example, and any Observable or static data would work as well:

states$: Observable<State[]>;constructor(private http: HttpClient) {
this.states$ = http.get<State[]>('http://localhost:8000/states');
}

Then, we want to be notified whenever the user types a new letter in the text input. To do this, we are going to use FormControl from the @angular/forms module.

The reason why we need a FormControl is that such objects expose a valueChanges observable that we can subscribe to. Every change in the input value gets published to that subscription:

states$: Observable<State[]>;
filter: FormControl;
filter$: Observable<string>;
constructor(private http: HttpClient) {
this.states$ = http.get<State[]>('http://localhost:8000/states');
this.filter = new FormControl('');
this.filter$ = this.filter.valueChanges;
}

The FormControl can then be bound to your text input in your component’s template as follows:

<input type="text" [formControl]="filter" placeholder="Filter states...">

At this point, we have two different observable streams of data: One that gives us data from a server and a second one that provides us with filter information from an input field.

Our next step is to combine these two streams into one, so we get a unified stream that just publishes filtered data.

This is where RxJs operators are going to help us. In our case, we’re going to use combineLatest, which takes two observables and creates a new one that returns the latest value emitted from each stream:

Illustration of the behavior of combineLatest with an example that appends the values from both input observables

Using combineLatest, we have a way to create a new stream of data. That data is going to be a tuple that contains data from both input observables. Here’s a simplified signature of the combineLatest method that I’m going to use:

combineLatest(o1: Observable<T1>, o2: Observable<T2>): Observable<[T1, T2]>;

The next step is to apply a transformation to that resulting stream so that it does become an observable of filtered data. We are going to use the map operator to achieve this since what it does is take an observable stream and turns it into a customized stream of data:

Illustration of the behavior of map with an example that multiplies the input value by 10

Using these two operators, we can now start filtering our data:

combineLatest(this.states$, this.filter$).pipe(
map(([states, filterString]) => // FILTER HERE)
);

This leads us to the following code, using the array filter function only to keep data that contains the filterString. Note that we use .toLowerCase() for the comparison, so we make the search case-insensitive:

combineLatest(this.states$, this.filter$).pipe(
map(([states, filterString]) =>
states.filter(state => state.name.toLowerCase().indexOf(filterString.toLowerCase()) !== -1))
);

Here is the full picture of our component code so far:

states$: Observable<State[]>;
filteredStates$: Observable<State[]>;
filter: FormControl;
filter$: Observable<string>;
constructor(private http: HttpClient) {
this.states$ = http.get<State[]>('http://localhost:8000/states');
this.filter = new FormControl('');
this.filter$ = this.filter.valueChanges;
this.filteredStates$ = combineLatest(this.states$, this.filter$).pipe(
map(([states, filterString]) => states.filter(state => state.name.toLowerCase().indexOf(filterString.toLowerCase()) !== -1))
);
}

And of course, here is the full HTML template that renders both the filter text input and the list of resulting filtered states:

<input type="text" [formControl]="filter" placeholder="Filter states..."><ul>
<li *ngFor="let state of filteredStates$ | async">{{state.name}}</li>
</ul>

This works correctly with one caveat, though: We have to enter at least one character to see anything happen. That’s because combineLatest gets the latest value from both observables, and as a result, it won’t do anything unless we enter at least one filtering character.

This might be a desirable behavior, but let’s say that in our case, we want to see the full list of states on the screen before any filter is entered.

In that case, all we have to do is make the filter$ observable emit one initial value. And guess what: There is an operator for that too!

startWith takes an observable and sets a start value for that stream

All we need is to start with an empty filter, which means an empty string, so we’re going to use startWith(‘’):

this.filter$ = this.filter.valueChanges.pipe(startWith(''));

And that is it! Now we can filter our data with the exact behavior that we would expect, all of that with just six lines of code thanks to RxJS operators:

this.states$ = http.get<State[]>('http://localhost:8000/states');
this.filter = new FormControl('');
this.filter$ = this.filter.valueChanges.pipe(startWith(''));
this.filteredStates$ = combineLatest(this.states$, this.filter$).pipe(
map(([states, filterString]) => states.filter(state => state.toLowerCase().name.indexOf(filterString.toLowerCase()) !== -1))
);

In this tutorial, we saw how to combine three different operators (combineLatest, map, and startWith) to achieve reactive filtering on a dataset using user input as a filtering criterion.

The full code for this tutorial can be found here (with a slight change to use local data, so you don’t need a server to run that code):

My name is Alain Chautard. I am a Google Developer Expert in Angular, as well as founding consultant and trainer at Angular Training where I help development teams learn and become proficient with Angular.

Need help with Angular? Let’s schedule some time to talk.

If you enjoyed this Angular tutorial, please clap for it or share it. Your help is always appreciated.

--

--