In a previous post earlier this week, we looked at how we can handle errors when using Angular Async Pipe. In this post, we are going to build a simple filter method for Angular Async Pipe.
We are going to use a custom pipe to filter results, then pass an observable back to the async pipe for subscription and rendering of the results. We will be simulating a call to a backend, where you take a search term and put together a HTTP request.
And then receive the results and return them to the caller. Since we don’t have a backend, we will be using a static list of countries, which we will filter and return matching countries. We will also be adding a delay of two seconds, to simulate calls to a backend. During the 2 seconds delay, we are going to show a loading animation or message.
Getting Started
As always, we will start by creating a new Angular project using Angular CLI:
$ ng new angular-pipe-filter
For the UI, we will be using bootstrap, so let’s install it and set it up. Feel free to use the UI Library of your choice.
$ yarn add bootstrap
// or with npm
$ npm install bootstrap
And then, import Bootstrap CSS into your angular project, under your app style section in angular.json
.
"styles": [
"src/styles.scss",
"node_modules/bootstrap/dist/css/bootstrap.min.css"
],
And that’s it for our project setup.
Filter Service
In a nutshell, the service will take a countries object, convert it into an observable using the of RXJS operator, filter the resulting object and return the filtered object. Let’s start by generating our service, using Angular CLI:
$ ng generate service filter
This will generate a filter service inside the root of our app directory. Then, let’s add a method to filter and return an observable of list of countries.
filterCountries(filter?: string): Observable<Country[]> {
}
NB:
Country
is a typescript interface that has country name and code fields. The method takes afilter
parameter, which is the phrase we will use to check if a country name has.
Then, inside the method, we need to start by converting the list of countries object, into an observable. And then we are going to pipe the resulting observable and add a delay of 200 seconds. After that, we shall then map the list of countries, and filter them by looping through each country and checking whether the country name contains the filter phrase.
return of(countries).pipe(
delay(2000),
map(c => {
// if filter is empty return all countries
if (!filter) {
return c;
}
// search for specific countries_
const filteredCountries: Country[] = [];
c.filter(function(country) {
if (country.name.toLowerCase().includes(filter.toLowerCase())) {
filteredCountries.push(country);
}
});
return filteredCountries;
})
);
NB: Ideally, all the filtering done in this method should be accomplished on the backend. The method should send a HTTP request query to and return the response as received or with minor modification.
You can find the complete code for the service here.
Filter Pipe
The pipe in this case, will act as a conduit between the component and the service. It will take in a filter term argument and pass it to the service and return the resulting observable. First, let’s generate a filter pipe:
$ ng generate pipe filter
This will generate a pipe – filter.pipe.ts – at the root of the app directory. Then, inside the constructor, we are going to inject the filter service we created above into our pipe.
constructor(private filterService: FilterService) {}
And then modify the transform method of our pipe, so that it returns an observable of our country. The transform method is also going to accept the current value to transform and the searchTerm as the second parameter. Then inside the method, we are going to return an observable of countries object (filtered) from our service:
transform(value: string, filterTerm: string): Observable<Country[]> {
return this.filterService.filterCountries(filterTerm);
}
And that’s it for our pipe. You can learn more about pipes here.
Putting Everything Together
App (Feature) Module
First, inside our app module (or feature module), we are going to import the FormsModule. This is because we will be using NgModel for two-way binding with a filter input.
// ...
@NgModule({
declarations: [
/* ... */
],
imports: [
/* ... */
FormsModule
],
providers: [],
bootstrap: [
/* ... */
]
})
export class AppModule {}
Component Class
Next, inside our component class, we are going to add two properties. The first property is for our sfilter field – filterField
. This is field where users will enter their search term. While the second property will be an observable for the list of countries - filterResults$
.
public filterField;
public filterResults$: Observable<Country[]> = null;
And finally, we will add a method to check whether our filter results returned an empty list. This will enable us to show an error message when the country list is empty.
dataLength(data: Country[]) {
return data.length > 0 ? true : false;
}
NB: You can also check whether the list of countries is empty at the filter service and throw an error at that point instead.
Component Template
And finally, to our template. First, we are going to need a container for our application:
<div class="container-fluid p-2">
<div class="col-md-12 col-lg-8 offset-lg-2 offset-md-0"></div>
</div>
Then, we are going to add a filter text input for users to enter the term to filter countries by:
<div class="col-12 form-group">
<label> Filter Countries </label>
<input type="text" [(ngModel)]="filterField" class="form-control" />
</div>
Then, we are going to use chain our pipe and angular async pipe to subscribe to observable returned by our custom pipe. When the request is yet to be resolved, we are going to show a loading message or animation.
NB: Please note, we are passing the filterField
ngModel when using our filter pipe.
<ng-container *ngIf="(filterResults$ | filter: filterField) | async as data; else loading">
<!-- code here -->
</div>
<ng-template #loading>
<div class="col-12 text-center">
<!-- Loading ... -->
</div>
</ng-template>
Then inside the tags, we shall check whether the list of countries is empty. If it’s empty, we are going to show an error, otherwise we shall loop over the list of countries.
<div class="col-12" *ngIf="dataLength(data); else error">
<div class="list-unstyled row">
<div
*ngFor="let x of data; let i = index"
class="list-item col-12 col-md-6 col-lg-4"
>
{{ i + 1 }} . {{ x.name | titlecase }}
</div>
</div>
</div>
<ng-template #error>
<div class="col-12">
<div class="alert alert-danger">
I could not find a country matching your filter criteria!
</div>
</div>
</ng-template>
Source Code and Demo
You can find the demo for this post here and the complete source code here.