Angular, how to create a highlighter that conditionally styles the text

Angular highlight search text
In this example we want to highlight some text inside a page or inside a table.
In the image you can see that the text searched ‘einzelbillet’ is dynamically highlighted in the list of answers to better visually find the relevant information.
This filter is applied on thousand of results and reduces the time required for the user to 'scan' the list of results.
In our tutorial we will build something much simpler:
How it works
The idea is to create a pipe that searches in each result the text used for the filter. If the result is found the original text is modified adding a <span class=“highlight”>
tag before the searched text.
import { Pipe, PipeTransform } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
/*
In the decorator we define the name of the Pipe
*/
@Pipe({
name: 'highlighter',
})
export class HighlighterPipe implements PipeTransform {
// a Pipe must implement PipTransform ... not a lot of choice here
/*
text: the text that contains the string to be searched for
search: the text to be found
cssClass: we created a styling class to highlight the text, this can be overriden
*/
transform(
originalText: string,
textToFind,
cssClass: string = 'highlighter'
): string {
// check the parameters, if something is missing we simply return the original text
if (typeof originalText !== 'string' || !textToFind) {
return originalText;
}
const pattern = textToFind
.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&')
.split(' ')
.filter((t) => t.length > 0)
.join('|');
const regex = new RegExp(pattern, 'gi');
let result = textToFind
? originalText.replace(
regex,
(match) => `<span class=${cssClass}">${match}</span>`
)
: originalText;
return result; // no need to sanitize
}
}
Transform method
Our transform
method (mandatory implementing PipeTransform) receives 3 parameters
transform(originalText: string,textToFind,cssClass: string = 'highlighter'): string {
originalText
is the text that has to analyzed and could contain the string to find
textToFind
is the string to be searched in the text
cssClass
in our case we want to be able to customize the style of the string found. By default we create an highlighter
CSS class
Pattern to use in Regex
In our case we want to be able to search multiple words. To do this we create a a pattern to use in a regular expression
const pattern = textToFind
.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&')
.split(' ')
.filter((t) => t.length > 0)
.join('|');
For example, if the textToFind is Angular Rocks
the resulting patter will be Angular|Rocks
.
Regex
const regex = new RegExp(pattern, 'gi');
The regex searches the pattern previously defined. The parameter ’gi’
means that the pattern has to be searched g globally and can be i insensitive to the case.
In our example Angular 13 rocks
will be found and valid.
Return the result
let result = textToFind
? originalText.replace(
regex,
(match) => `<span class=${cssClass}">${match}</span>`
)
: originalText;
return result; // no need to sanitize
In the original text, we will search the regex previously built. If there is a match the text will surround the match with a <span>
tag that adds a CSS class and return the result as a simple string
.
If no match is found then the original text is sent as result.
For information, we are returning some text with HTML, because our code is basic HTML we don’t need to sanitize the DOM. If you want to use style
to modify the color of the text avoiding the external CSS, the result has to be sanitized.
Usage in HTML
Here you have an example of usage of the Pipe
<input type="text" [(ngModel)]="filterText" />
<br />
<p
[innerHtml]="
textToShowAsResult | highlighter: filterText:getHighlighterClass()
"
></p>
Because we could add some html tag to the original text we need to show the result in an element with the [innerHtml]
property.
In this example, we call our highlighter
pipe with multiple parameters: | highlighter: filterText:getHighlighterClass()
.
filterText
is the text contained in the ngModel
bind to the input text
, this is the text that will be searched in the textToShowAsResult
variable.
getHighlighterClass()
to complicate a bit our example we use a method to dynamically retrieve the CSS class name to be used to color the text.
CSS Style
We use only one class in our example. Very basic.
.highlighter {
color: red;
font-size: 1em;
text-decoration: underline;
}
Component
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
encapsulation: ViewEncapsulation.None,
})
export class AppComponent {
public filterText: string = 'Angular rocks';
textToShowAsResult = 'Text to be highlighted: Angular 13 Rocks!!!';
getHighlighterClass(): string {
return "'highlighter'";
}
}
Our component is pretty straightforward.
What it is important to note is that we use ViewEncapsulation.None
, this allows the defined style to be used in every element present on the page. With the default ViewEncapsulation.Emulated
the component cannot access the styles with innerHtml
.
Stackblitz
You can test and play with this implementation published on Stackblitz