Creating a Search Filter in Angular
Tags: angular, beginners, javascript, webdev
Published: 2020-05-07 12:16:53

---
title: Creating a Search Filter in Angular
published: true
description: Wondering how to create a search filter in Angular without using any external libraries? Check out this post and also learn about pipes and directives along with it.
cover_image: https://res.cloudinary.com/idr/image/upload/q_auto,f_auto/dev.to/angular-text-search/angular-text-search_rvzzib.gif
tags: angular, beginner, javascript, webdev
---
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](https://res.cloudinary.com/idr/image/upload/q_auto,f_auto/dev.to/angular-text-search/angular-text-search_rvzzib.gif)

So now the question is, how to implement it? It's quite easy though :wink: 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.

```ts
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
}
```
Pipes are of 2 types - Pure and Impure. For more information on Angular pipes, [visit this link](https://angular.io/guide/pipes).

---

### 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`:

```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);
    });
  }
}
```
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:
```ts
// 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 { }
```

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.
```html
<!-- 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>
```

```ts
// 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',
    ...
  ]
}
```
That's it! Now when we run our app, we will see the following output:
![Angular Filter Output](https://res.cloudinary.com/idr/image/upload/q_auto,f_auto/dev.to/angular-text-search/angular-filter-preview_hzixr5.png)

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

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](https://angular.io/guide/attribute-directives).

---

### 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`:

```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>`);
  }
}
```
> 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:
```ts
// 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 { }
```
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:
```html
<!-- 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>
```

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

---

You check out my [GitHub repo](https://github.com/idris-rampurawala/angular-text-search-highlight) for a complete implementation of this post.

See ya! until my next post :yum: