DEV Community

Cover image for Highlight Search Results with an Angular Pipe
Jason F
Jason F

Posted on

Highlight Search Results with an Angular Pipe

Alt Text

I remember a few months back (at my previous job) I needed to implement a feature that would highlight text that I searched for in an input. I cannot remember my exact implementation, but I do remember there being quite a few answers on StackOverflow of how I could accomplish this. I remember having a few issues with implementing a solution, but ultimately I was able to figure it out. Today I created a solution that works. Of course you can copy my code, tweak it to meet your needs, etc.

You can find the repository here.

Quick Rundown

I'll give you the full code snippet for the pipe and a rundown of how I used it.

The Pipe

Here is the code for the pipe.

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

@Pipe({
  name: 'highlightSearch',
})
export class HighlightSearchPipe implements PipeTransform {
  transform(value: any, args: any): any {
    if (!args) {
      return value;
    }

    const regex = new RegExp(args, 'gi');
    const match = value.match(regex);

    if (!match) {
      return value;
    }

    return value.replace(regex, `<span class='highlight'>${match[0]}</span>`);
  }
}
Enter fullscreen mode Exit fullscreen mode

As you can see, I have a highlight class in the return value. I defined this class in the global styles.scss file like so:

.highlight {
  background-color: violet;
  font-weight: bold;
}
Enter fullscreen mode Exit fullscreen mode

Implementation

search-text Component

First I'll show you the important parts of the component, then I'll share the full template and code.

In my search-text.component.html template I use the pipe like so:

  <p [innerHTML]="pet.description | highlightSearch: Search"></p>
Enter fullscreen mode Exit fullscreen mode

You'll notice that Search is the value that is passed to the pipe. The Search value is set in the OnSearched method. In the same file, on line 1, I get my search term from the searched event emitter, which calls the OnSearched method and gives me the value.

<app-search (searched)="OnSearched($event)"></app-search>
Enter fullscreen mode Exit fullscreen mode

Here is the full search-text.component.html file:

<app-search (searched)="OnSearched($event)"></app-search>
<div class="card-container">
  <div class="card" *ngFor="let pet of pets">
    <mat-card>
      <mat-card-header>
        <mat-card-title>{{ pet.name }}</mat-card-title>
        <mat-card-subtitle>{{ pet.species }}</mat-card-subtitle>
      </mat-card-header>
      <mat-card-content>
        <p [innerHTML]="pet.description | highlightSearch: Search"></p>
        <p>
          <strong>Nickname/s: </strong>
          <span *ngFor="let nickname of pet.nicknames; let i = index"
            >{{ nickname
            }}{{ i === pet.nicknames.length - 1 ? "" : ",&nbsp;" }}</span
          >
        </p>
      </mat-card-content>
    </mat-card>
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

And here is the full search-text.component.ts file:

import { Component, OnInit } from '@angular/core';
import * as data from './searchdata.json';

@Component({
  selector: 'app-search-text',
  templateUrl: './search-text.component.html',
  styleUrls: ['./search-text.component.scss'],
})
export class SearchTextComponent implements OnInit {
  public Search: string = null;
  public pets: any = (data as any).default;
  constructor() {}

  ngOnInit(): void {}

  public OnSearched(searchTerm: string) {
    this.Search = searchTerm;
  }
}
Enter fullscreen mode Exit fullscreen mode

search Component

Just like the search-text component, I'll give you the highlights first, then the full template and code.

In the search.component.html I get the input from the user like so:

<input matInput (input)="onSearch($event.target.value)" />
Enter fullscreen mode Exit fullscreen mode

Of course I will now show you the onSearch method:

 public onSearch(searchTerm: string): void {
    this.searched.emit(searchTerm);
  }
Enter fullscreen mode Exit fullscreen mode

The output property called searched looks like this:

@Output() searched = new EventEmitter<string>();
Enter fullscreen mode Exit fullscreen mode

As promised, here is the full search.component.html file:

<mat-toolbar>
  <span>My Pets</span>
  <span class="spacer"></span>
  <mat-icon aria-hidden="false" aria-label="Example home icon">search</mat-icon>
  <mat-form-field class="form-field">
    <input matInput (input)="onSearch($event.target.value)" />
  </mat-form-field>
</mat-toolbar>
Enter fullscreen mode Exit fullscreen mode

And here is the search.component.ts file:

import { Component, OnInit, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-search',
  templateUrl: './search.component.html',
  styleUrls: ['./search.component.scss'],
})
export class SearchComponent implements OnInit {
  @Output() searched = new EventEmitter<string>();

  constructor() {}

  ngOnInit(): void {}

  public onSearch(searchTerm: string): void {
    this.searched.emit(searchTerm);
  }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

I hope you found this interesting or helpful. Let me know your thoughts. If you want to see the code, please view it here.

Top comments (3)

Collapse
 
pranavkumar389 profile image
Pranav Kumar • Edited

I am curious about how you will handle the scenario when the innerHTML content has an HTML element.
When pet.description is <a href="#"><span>State</span></a>, and the user just enters the letter s in the search field.
The current codebase will break as the element span has also the letter 's'.

Collapse
 
juniordevforlife profile image
Jason F

Hi Pranar, thanks for taking the time to review my post. As I stated in the post, this solution met my requirements and if need be, you can tweak it. My use case did not call for having an html element within. I'd love to hear how you would approach this situation.

Collapse
 
micha_marchewa_55e033c9d profile image
Michał Marchewa

When you are using only first match (match[0]), then it can cause situation that letter case sizes will be different in output then in input, for example:

input: transform('Test test Test test', 'test')
output: Test Test Test Test

everything in output is upper case now like first match