Angular: highlight search text

Igor Gonchar
3 min readNov 30, 2020

Let’s consider we have a task: to create an input element and highlight text in the paragraph, based on the user input.

The solution is quite simple: to create a pipe, that will get the searched text as an argument and will return a modified markdown based on the regular expression result.

Create component:

So, lets start with creation of the most simple component:

@Component({ 
selector: 'my-app',
template: `
<input type="text" [(ngModel)]="searchQuery" />
<p>This is a test paragraph where we will be looking for a search string</p>
`,
})
export class App {
searchQuery: string = '';
}

At this moment, for the sake of simplicity we are using a two-way binding. The component searchQuery property will immediately get the value, based on the user input.

The compiled result will look this:

Great. Prerequisites are done. Now let’s implement the main highlight logic.

Create pipe:

First of all, we need to create a pipe class.

ng g pipe highlight

Then the pipe itself will look as following:

@Pipe({ 
name: 'highlight'
})
export class HighlightPipe implements PipeTransform {
transform(wholeText: string, searchQuery: string): string {
if (!searchQuery) {
return wholeText;
}
const re = new RegExp(searchQuery, 'gi');
return wholeText.replace(re, '<mark>$&</mark>');
}
}

The mandatory implementation of transform method will take 2 arguments. They are quite descriptive from their names.

The transform function can be described as: return the whole text with all searchQuery occurrences, surrounded with <mark> html tags.

g modifier is used to perform a global match (find all matches rather than stopping after the first match).

i case-insensitive search

$&Inserts the matched substring.

What <mark> tag does? It just defines text that should be marked or highlighted. It’s done by browser engine and no .css is required. Unless, of course, you want it to look sexier.

Update component:

And finally, when the pipe is ready, we have to use it in component’s template:

@Component({ 
selector: 'my-app',
template: `
<input type="text" [(ngModel)]="searchQuery" />
<p [innerHtml]="'This is a test paragraph where we will be looking for a search string' | highlight: searchQuery">
</p>
`,
})

The result of user search looks like this:

Why do we use [innerHtml] one-way property binding instead of just simple handlebars usage like this?

<p> {{ "This is a test paragraph where we will be looking for a search string" | highlight: searchQuery }} </p>

The detailed answer is in this post but in 2 words: Angular have dropped`$compile` functionality and recognises the value as unsafe and automatically sanitises it. There are no ways now to insert HTML fragments into the component view except using innerHTML.

Improvement:

Just to make the example more real-life looking, the good way to improve our code will be to use stream approach with subjects and to run search only when user presses Enter:

@Component({ 
selector: 'my-app',
template: `
<input type="text"
[(ngModel)]="searchQuery"
(keydown.enter)="search()"/>
<p [innerHtml]="'This is a test paragraph where we will be looking for a search string' | highlight: (searchQuery$$ | async)">.
</p>
`,
})
export class App {
searchQuery: string = '';
searchQuery$$ = new BehaviorSubject<string>('');
search() {
this.searchQuery$$.next(this.searchQuery);
}
}

As you see, now the search highlight appears only after user has entered the whole string and clicked Enter:

Conclusion:

This is quite an easy and pretty looking way to visualise the result of user actions.

Pipes — a perfect Angular tool for updating data representation on UI and you may achieve maximum of advantage from it with minimum efforts required.

--

--