DEV Community

Cover image for Creating a Search Filter in Angular
Idris Rampurawala
Idris Rampurawala

Posted on • Updated on

Creating a Search Filter in Angular

Consider a scenario where we have a long list of data being displayed to the user on UI. It would be cumbersome for the user to search for any particular keyword in this long list without any search functionality provided. Hence, to make our users' life easy, we would usually implement search filters on our UI.

Angular filter demo

So now the question is, how to implement it? It's quite easy though 😉 All we want is a filter that takes an array as input and returns a subset of that array based on the term we supply. In Angular, this way of transforming data to some other form is implemented with Pipes. Let's first understand a bit more about pipes before we start the implementation.


Pipes in Angular

A pipe takes in data as input and transforms it into the desired output. A pipe can be used in both the HTML template expression and in a component. Angular does provide us with some built-in pipes such as CurrencyPipe, DatePipe, DecimalPipe, etc. Check this code snippet below to see it in action.

dateObj = Date.now();

// HTML template expression syntax using pipe operator (|)
{{ dateObj | date }}               // output is 'Jun 15, 2015'
{{ dateObj | date:'medium' }}      // output is 'Jun 15, 2015, 9:43:11 PM'
{{ dateObj | date:'shortTime' }}   // output is '9:43 PM'
{{ dateObj | date:'mm:ss' }}       // output is '43:11'

// Using in component
constructor(private datePipe: DatePipe) { 
    console.log(datePipe.transform(Date.now(),'yyyy-MM-dd'));
    //2019-07-22
}
Enter fullscreen mode Exit fullscreen mode

Pipes are of 2 types - Pure and Impure. For more information on Angular pipes, visit this link.


Implementing Search Filter

1. Create the Filter Pipe

Let's populate the pipe with code for the filter. Copy and paste this code into filter.pipe.ts:

// filter.pipe.ts

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

@Pipe({ name: 'appFilter' })
export class FilterPipe implements PipeTransform {
  /**
   * Pipe filters the list of elements based on the search text provided
   *
   * @param items list of elements to search in
   * @param searchText search string
   * @returns list of elements filtered by search text or []
   */
  transform(items: any[], searchText: string): any[] {
    if (!items) {
      return [];
    }
    if (!searchText) {
      return items;
    }
    searchText = searchText.toLocaleLowerCase();

    return items.filter(it => {
      return it.toLocaleLowerCase().includes(searchText);
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

This pipe definition reveals the following key points:

  • A pipe is a class decorated with pipe metadata.
  • The pipe class implements the PipeTransform interface's transform method that accepts an input value followed by optional parameters and returns the -transformed value. In our filter pipe, it takes 2 inputs - an array and the search text to filter the array with.
  • To tell Angular that this is a pipe, we apply the @Pipe decorator, which we import from the core Angular library.
  • The @Pipe decorator allows us to define the pipe name that we'll use within template expressions. It must be a valid JavaScript identifier. Our pipe's name is appFilter.

2. Using Pipe

To use the pipe, first, we need to import it into the app module. Our app.module.ts file would now look like this:

// app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';

import { AppComponent } from './app.component';

import { FilterPipe } from './pipes/filter.pipe'; // -> imported filter pipe

@NgModule({
  declarations: [
    AppComponent,
    FilterPipe // -> added filter pipe to use it inside the component
  ],
  imports: [
    BrowserModule,
    FormsModule
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }
Enter fullscreen mode Exit fullscreen mode

Now we can use the filter pipe in our App Component. Let's assume that in our app.component.html we have an input box where we can enter our searchText and a list that makes use of this pipe to filter the results.

<!-- app.component.html -->

<div class="content" role="main">
  <div class="card">
    <div class="form-group">
      <label for="search-text">Search Text</label>
      <input type="email" class="form-control" id="search-text" aria-describedby="search-text" 
        [(ngModel)]="searchText" placeholder="Enter text to search" 
        autofocus>
    </div>
    <ul class="list-group list-group-flush">
      <!-- results of ngFor is passed to appFilter with argument searchText -->
      <li class="list-group-item" *ngFor="let c of characters | appFilter: searchText">
        {{c}}
      </li>
    </ul>
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode
// app.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  title = 'angular-text-search-highlight';
  searchText = '';
  characters = [
    'Ant-Man',
    'Aquaman',
    'Asterix',
    'The Atom',
    'The Avengers',
    'Batgirl',
    'Batman',
    'Batwoman',
    ...
  ]
}
Enter fullscreen mode Exit fullscreen mode

That's it! Now when we run our app, we will see the following output:
Angular Filter Output

But hey! our search results are not being highlighted as it was shown at the beginning 😟

The reason is that Pipes in angular only transforms the data passed to it into the desired output. It won't manipulate the HTML associated with it. To highlight the search results, we would be required to manipulate the HTML to highlight the searchText part of it. This can be achieved using Directives.


Directives in Angular

Angular directives are used to extend the power of the HTML by giving it new syntax. There are 3 types of directives:

  1. Components — directives with a template.
  2. Structural directives — change the DOM layout by adding and removing DOM elements.
  3. Attribute directives — change the appearance or behavior of an element, component, or another directive.

Covering directives is outside the scope of this post. If you want to learn more about angular directives, visit this link.


Implementing Directive in our Application

In our case, we will be using the attribute directive to highlight the searchText in the result list.

1. Creating highlight directive

An attribute directive minimally requires building a controller class annotated with @Directive, which specifies the selector that identifies the attribute. The controller class implements the desired directive behavior.

Let's populate the directive with code for the highlighting. Copy and paste this code into highlight.pipe.ts:

// highlight.directive.ts

import { Directive, Input, SimpleChanges, Renderer2, ElementRef, OnChanges } from '@angular/core';

@Directive({
  selector: '[appHighlight]'
})
export class HighlightDirective implements OnChanges {
  @Input() searchedWord: string; // searchText
  @Input() content: string; // HTML content
  @Input() classToApply: string; //class to apply for highlighting
  @Input() setTitle = false; //sets title attribute of HTML

  constructor(private el: ElementRef, private renderer: Renderer2) { }

  ngOnChanges(changes: SimpleChanges): void {
    if (!this.content) {
      return;
    }

    if (this.setTitle) {
      this.renderer.setProperty(
        this.el.nativeElement,
        'title',
        this.content
      );
    }

    if (!this.searchedWord || !this.searchedWord.length || !this.classToApply) {
      this.renderer.setProperty(this.el.nativeElement, 'innerHTML', this.content);
      return;
    }

    this.renderer.setProperty(
      this.el.nativeElement,
      'innerHTML',
      this.getFormattedText()
    );
  }

  getFormattedText() {
    const re = new RegExp(`(${this.searchedWord})`, 'gi');
    return this.content.replace(re, `<span class="${this.classToApply}">$1</span>`);
  }
}
Enter fullscreen mode Exit fullscreen mode

The logic is to manipulate the current HTML element by adding <span> tag in between the searchText and applying the highlighting class to it.

2. Using directive

To use the pipe, first, we need to import it into the app module. Our app.module.ts file would now look like this:

// app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';

import { AppComponent } from './app.component';

import { HighlightDirective } from './directives/highlight.directive'; // ->  imported directive
import { FilterPipe } from './pipes/filter.pipe';

@NgModule({
  declarations: [
    AppComponent,
    HighlightDirective, // -> added directive
    FilterPipe
  ],
  imports: [
    BrowserModule,
    FormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
Enter fullscreen mode Exit fullscreen mode

To use this directive in our HTML file, we will add it as a normal HTML attribute with all its parameters. It would look like this:

<!-- app.component.html -->

<div class="content" role="main">
  <div class="card">
    <div class="form-group">
      <label for="search-text">Search Text</label>
      <input type="email" class="form-control" id="search-text" aria-describedby="search-text" 
        [(ngModel)]="searchText" placeholder="Enter text to search" 
        autofocus>
    </div>
    <ul class="list-group list-group-flush">
      <li class="list-group-item" *ngFor="let c of characters | appFilter: searchText"
        appHighlight [searchedWord]="searchText" [content]="c"  
        [classToApply]="'font-weight-bold'" [setTitle]="'true'">
        {{c}}
      </li>
    </ul>
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

Now, we would be able to see the desired output! 😌


You check out my GitHub repo for a complete implementation of this post.

See ya! until my next post 😋

Oldest comments (17)

Collapse
 
gusgonnet profile image
Gustavo

wow, Idris, you explained it so well and I just implemented this on my app in minutes thanks to your post - THANK YOU! you rock

Collapse
 
idrisrampurawala profile image
Idris Rampurawala

Hey Gustavo,

Thanks for reading the article and for the compliments 😁

Collapse
 
othmangueddana profile image
Othman-Gueddana

thank you man <3

Collapse
 
inderoffcial profile image
inderoffcial

Hi can you explain how can we create Filter pipe for multiple columns and if some columns have null value.
TIA

Collapse
 
charleshenricastaing profile image
charleshenricastaing • Edited

Hi, thanks for your post.
I'd tried to implement it but console return me this.
How can i do to fix this problem?

Collapse
 
idrisrampurawala profile image
Idris Rampurawala

Hi,

Thanks for reading the article. Could you please share the screenshot of the console error to understand the problem you are facing? Also, try cloning this repo in your machine and check if it's working and you can then replicate the same in your project 😁

Collapse
 
afifalfiano profile image
Afif Alfiano

Tenkyu

Collapse
 
ownvrgs24 profile image
Owen Jasper Vargas

Thank you!

Collapse
 
vickyadi3 profile image
vickyadi3

after text filter it need to be selected in input box while selecting search result, how we achieve in this?

Collapse
 
raulriv profile image
Raúl Rivero

Hey! Nice tutorial mate, learned a lot about it. The highlight feature does not work for angular 12(no idea why). Cloned the project and tested it in Angular 11 and 12 and it only works in <12. Is there anything I can do for it to work in Angular 12?

Collapse
 
idrisrampurawala profile image
Idris Rampurawala

Thanks for reading it. Glad you liked it. I'll try to check this issue. You can log it as an issue in GitHub.

Collapse
 
johnabraham1 profile image
johnabraham1

Nice tutorial. How about the pagination and showing filtered records count? Can we achieve it?

Collapse
 
ajinkyakatre profile image
Ajinkya Katre

if there are no results found how to show it.

Some comments may only be visible to logged-in visitors. Sign in to view all comments.