- read

Search Filter Input with Filter Pipe in Angular 16

The Software Line 61

Search Filter Input with Filter Pipe in Angular 16

Learn how to filter data on the client side using asynchronous and filter pipes together with Angular Material library

The Software Line
Level Up Coding
Published in
6 min readJust now

--

Photo by Shamin Haky on Unsplash

Introduction

In modern web development, it’s crucial to have appropriate data filtering mechanisms in order to deliver the best possible experience for the application’s end user. Without these mechanisms on the user interface with some kind of presentation, it’s very difficult for the user to find the target data item.

So when it comes to the data filtering approach inside the web app, we have two options:

  • Server-side (back-end) filtering, where we are sending search filter input values inside the request payload to the remote API (application programming interface) endpoint and then waiting for the response with filtered data
  • Client-side (front-end) filtering is where we fetch all the data from the remote API endpoint, and then we filter the data on the front-end when the filter changes.

In this article, we will explain the client-side approach, focusing on the functionality with some basic Cascade Sheet Styles (CSS).

Application setup

Before you start, be sure that you have the appropriate versions of Node and Node Package Manager to be able to create an Angular 16 project. To check, open the command line and execute the following commands:

$ node -v
v18.16.1
$ npm -v
9.7.2

Open the terminal (command line) inside the target directory, and then you need to run the following commands using the Angular CLI:

$ ng new search-filter

? Would you like to add Angular routing? Yes
? Which stylesheet format would you like to use? SCSS [
https://sass-lang.com/documentation/syntax#scss ]
CREATE search-filter/README.md (1066 bytes)
...

$ cd search-filter/
$ ng serve

...
** Angular Live Development Server is listening on localhost:4200,
open your browser on http://localhost:4200/ **


✔ Compiled successfully.

Your application is now up and running, so we can start with the next steps.

Create a to-do list component

We are creating the component using Angular CLI (Command Line Interface) from the project directory:

$ cd search-filter
$ ng generate component components/todo-list

CREATE src/app/components/todo-list/todo-list.component.scss (0 bytes)
CREATE src/app/components/todo-list/todo-list.component.html (24 bytes)
CREATE src/app/components/todo-list/todo-list.component.spec.ts (574 bytes)
CREATE src/app/components/todo-list/todo-list.component.ts (214 bytes)
UPDATE src/app/app.module.ts (496 bytes)

When we need some fake data inside the web application, we can use several fake APIs available online. Here, we will use a JSON placeholder.

In order to show the list of data, we are using Angular’s built-in asynchronous pipe. I already wrote a dedicated article about this topic, so check it out:

Create the search filter input

First, we need to install the Angular Material library with the following command:

$ npm install @angular/[email protected] --save

Now, we can import the mat input module and reactive forms modules to the main application module, and after that, we can create a simple form with material input. Below, you can find the whole main application module and the part related to the rendering of the search input:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

import { MatInputModule } from '@angular/material/input';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { TodoListComponent } from './components/todo-list/todo-list.component';
import { HttpClientModule } from '@angular/common/http';
import { ReactiveFormsModule } from '@angular/forms';
import { FilterPipe } from './pipes/filter.pipe';

@NgModule({
declarations: [
AppComponent,
TodoListComponent,
FilterPipe
],
imports: [
BrowserModule,
BrowserAnimationsModule,
AppRoutingModule,
HttpClientModule,
MatInputModule,
ReactiveFormsModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
<form [formGroup]="filterForm">
<mat-form-field class="example-full-width">
<input matInput placeholder="Search" formControlName="searchFilter">
</mat-form-field>
</form>

Inside the component, we have created a filter form group with a search filter form control, a filter form subscription object, and the current search filter value.

filterForm: FormGroup = new FormGroup({
searchFilter: new FormControl<string>('')
});
filterFormSubsription: Subscription;
searchFilter: string = '';

Create custom Filter Pipe

Now, we are ready to create the filter pipe responsible for filtering the data when the search filter input changes. I wrote a lot about angular pipes, so if you are more interested, you can check out the articles below:

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
name: 'filter'
})
export class FilterPipe implements PipeTransform {

transform(data: any[], filterProperty: string, filter: string): any[] {
const filterValue = filter.toLowerCase();
return filterValue
? data.filter(item => item[filterProperty].toLowerCase().includes(filterValue))
: data;
}

}

Inside the pipe transform function, we are filtering the input array of objects by a specific input property using lower case for both values (the target property value from the list item and the filter value). If the search filter input value is not present, the output is the original array. Once again, for the pipe inputs, we have array, filter property (key) and filter value.

Let’s put it all together

Here, we have a to-do list component ready with an asynchronous pipe, filter pipe and material search filter input. We don’t want to execute the pipe function for every typed character inside the input (for every change), so we have 300. Milliseconds inside the debounce time operator from RxJS library.

import { HttpClient } from '@angular/common/http';
import { Component, OnDestroy } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { Observable, Subscription, debounceTime, map } from 'rxjs';

export type TodoItem = {
id: number;
userId: number;
title: string;
completed: boolean;
}

@Component({
selector: 'app-todo-list',
template: `
<div class="list-container">
<form [formGroup]="filterForm">
<mat-form-field class="example-full-width">
<input matInput placeholder="Search" formControlName="searchFilter">
</mat-form-field>
</form>
<ng-container
*ngFor="let todoItem of todoData$ | async | filter: 'title' : searchFilter;
let i = index; let isEven = even; let isOdd = odd">
<div
class="todo-item"
[ngClass]="{'even-todo-item': isEven, 'odd-todo-item': isOdd}">
{{ todoItem.title }}
</div>
</ng-container>
</div>
`,
styles: [`
.list-container {
padding: 25px;
}

.todo-item {
padding: 10px;
}
.even-todo-item {
background-color: bisque;
}
.odd-todo-item {
background-color: whitesmoke;
}
`]
})
export class TodoListComponent implements OnDestroy {

todoData$: Observable<TodoItem[]>;

filterForm: FormGroup = new FormGroup({
searchFilter: new FormControl<string>('')
});
filterFormSubsription: Subscription;
searchFilter: string = '';

constructor(private httpClient: HttpClient) {
const todoDataUrl = 'https://jsonplaceholder.typicode.com/todos';
this.todoData$ = this.httpClient.get(todoDataUrl)
.pipe(map(response => {
const data = response as TodoItem[];
return data;
}));

this.filterFormSubsription = this.filterForm.valueChanges
.pipe(debounceTime(400)).subscribe(changes => {
this.searchFilter = changes.searchFilter;
})
}

ngOnDestroy(): void {
this.filterFormSubsription.unsubscribe();
}
}

Inside the constructor, we have creation for the observable and filter form subscription objects. It’s also important to notice the unsubscribe operation inside the destroy life-cycle hook. On the template, we have a pipe chain where we are using asynchronous and filter pipes.

Inside the main CSS file, we can import one of the default light themes from Angular Material.

/* src/styles.scss file content */
/* You can add global styles to this file, and also import other style files */

@import '@angular/material/prebuilt-themes/deeppurple-amber.css';

Now we are ready to show how the component works inside the application:

1. Complete to-do list data

ng16 — simple to-do list component

2. Filtered to-do list data

ng16 — filtered to-do list

Conclusion

When it comes to the client (front-end) filtering inside the Web application, it’s very useful to have asynchronous pipes and filter pipes as the best friends if it’s possible. With asynchronous pipe, we are sure that we don’t have the subscription, which leads to memory leaks when components are destroyed, and with pure filter pipe, the pipe transform function is called only when input arguments change.

Thanks for reading.
I hope you enjoyed the article and learned something.