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>`);
}
}
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;
}
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>
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>
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 ? "" : ", " }}</span
>
</p>
</mat-card-content>
</mat-card>
</div>
</div>
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;
}
}
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)" />
Of course I will now show you the onSearch method:
public onSearch(searchTerm: string): void {
this.searched.emit(searchTerm);
}
The output property called searched
looks like this:
@Output() searched = new EventEmitter<string>();
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>
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);
}
}
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)
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 letters
in the search field.The current codebase will break as the element span has also the letter 's'.
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.
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