Skip to content

Instantly share code, notes, and snippets.

@adamrecsko
Created May 1, 2016 20:28
Show Gist options
  • Star 50 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save adamrecsko/0f28f474eca63e0279455476cc11eca7 to your computer and use it in GitHub Desktop.
Save adamrecsko/0f28f474eca63e0279455476cc11eca7 to your computer and use it in GitHub Desktop.
Angular2 text highlight pipe
import {PipeTransform, Pipe} from 'angular2/core';
@Pipe({ name: 'highlight' })
export class HighLightPipe implements PipeTransform {
transform(text: string, [search]): string {
return search ? text.replace(new RegExp(search, 'i'), `<span class="highlight">${search}</span>`) : text;
}
}
/** Usage:
* <input type="text" [(ngModel)]="filter">
* <div [innerHTML]="myAwesomeText | highlight : filter"></div>
*
*/
@andreialecu
Copy link

andreialecu commented Oct 14, 2016

This seems to be the top link in Google, so here's an improved version for Angular 2.0 final. The original version had a problem with capitalized characters, replacing them with their lower case equivalent and also didn't escape regex control chars, resulting in other bugs.

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

@Pipe({ name: 'highlight' })
export class HighlightPipe implements PipeTransform {
  transform(text: string, search): string {
    var pattern = search.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
    pattern = pattern.split(' ').filter((t) => {
      return t.length > 0;
    }).join('|');
    var regex = new RegExp(pattern, 'gi');

    return search ? text.replace(regex, (match) => `<span class="highlight">${match}</span>`) : text;
  }
}

@rolandoldengarm
Copy link

Nice one, thanks! :)

@meiriko
Copy link

meiriko commented Oct 27, 2016

Nice indeed.
Note that the code will fail with error if the search param is null (due to the call to .replace()). It might be simpler to add a simple check instead of the ternary operator at the end.

@koelle25
Copy link

koelle25 commented Nov 15, 2016

That's exactly what I was searching for - excellent!
Only problem:
How can I style the span.highlight with a component-inheritent stylesheet? (styleUrl: [...])
Currently the styling is not applied because angular doesn't add the html-property (_ngcontent-...) to the Element when using [innerHtml]-binding... 😕

Edit: Ah, found a workaround (as described on StackOverflow)
:host >>> mySelector { /* some styling */ }

@CarstenHouweling
Copy link

<span [innerHTML]="text | highlight: search">

works perfectly, thanks a lot.

@vhogemann
Copy link

Awesome! Thank you!

@leomayer
Copy link

I change tiny bits to be compatible with tslint-checks and work in some undefined cases - thx for the code :-)

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

@Pipe({ name: 'highlight' })
export class HighlightPipe implements PipeTransform {
  transform(text: string, search): string {
    if (search && text) {
      let pattern = search.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
      pattern = pattern.split(' ').filter((t) => {
        return t.length > 0;
      }).join('|');
      const regex = new RegExp(pattern, 'gi');

      return text.replace(regex, (match) => `<span class="search-highlight">${match}</span>`);
    } else {
      return text;
    }
  }
}

@manas89
Copy link

manas89 commented Aug 1, 2017

This what I am looking for. Workers Perfectly fine.

@jculverwell
Copy link

jculverwell commented Aug 17, 2017

To get the styling to work you need to add one of the styles below (depends on whether you are using leomayer or original version)

:host ::ng-deep .highlight{
  background-color: #F2E366;
}

Or

:host ::ng-deep .search-highlight{
  background-color: #F2E366;
}

@nglasantha
Copy link

Thanks works fine

@nooot77
Copy link

nooot77 commented Oct 8, 2017

but what about : "WARNING: sanitizing HTML stripped some content (see http://g.co/ng/security#xss)."

@jboarman-ap
Copy link

@nooot77
For me, the key phrase from Angular's documentation regarding XSS vulnerabilities is "binding a value that an attacker might control into innerHTML normally causes an XSS vulnerability". If you must display html from an untrusted source, ng still attempts to mitigate the risk by stripping <script> tags. So, while it might strip some content, it's hopefully a good thing when it does. I think the warning would be to indicate that if you intend to insert scripts, then you should do it a more traditional way. What do you think? Does that make any sense given the context you are seeing this warning?

@teslovych
Copy link

@nooot77

import { PipeTransform, Pipe } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser'

@Pipe({ name: 'highlight' })
export class HighlightPipe implements PipeTransform {
    constructor(public sanitizer: DomSanitizer) {
    }
    transform(text: string, search): SafeHtml {
        if (search && text) {
            let pattern = search.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
            pattern = pattern.split(' ').filter((t) => {
                return t.length > 0;
            }).join('|');
            const regex = new RegExp(pattern, 'gi');
            return this.sanitizer.bypassSecurityTrustHtml(
                text.replace(regex, (match) => `<span class="search-highlight">${match}</span>`)
            );

        } else {
            return text;
        }
    }
}

@Deepakchawla
Copy link

Thanks it works fine...

@ddramone
Copy link

ddramone commented Dec 4, 2017

Test here, if you care about coverage:

import { DomSanitizer } from '@angular/platform-browser'
import { TestBed, inject } from '@angular/core/testing';

import { HighlightPipe } from './highlight.pipe';

fdescribe('AutoComplete Component - Highlight pipe', () => {
    let pipe: HighlightPipe;

    beforeEach(() => {
        TestBed.configureTestingModule({
            providers: [ {
                provide: DomSanitizer,
                useValue: {
                    bypassSecurityTrustHtml: v => v
                }
            }, HighlightPipe
            ]
        });
    });

    beforeEach(inject([HighlightPipe], p => {
        pipe = p;
      }));

    it('highlights search term in the text', () => {
      let result = pipe.transform('search text', 'text');
        expect(result).toBe('search <span class="search-highlight">text</span>')
    });

    it('shoudl return same text', () => {
        let result = pipe.transform('search text', '');
        expect(result).toBe('search text', 'search text')
      });
});

@rex-ya
Copy link

rex-ya commented Mar 4, 2018

Perfect! Thank you! 👍

@kkumar981
Copy link

kkumar981 commented Apr 13, 2018

Hi Its working properly.

what ever the user enters in the input-field getting displayed with highlighted.

How to highlight the search text on plain html page. I tried with below code

My html page:-
<input type="text" [(ngModel)]="search">
<div [innerHTML]="data | highlight: search">

  </div> 

text1 xyz abc
text2 xyz abc
text3 xyz abc

---------------------------------------------------------------------------------------------------

highlight.component.ts

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

@pipe({ name: 'highlight' })
export class HighlightPipe implements PipeTransform {
transform(text: string, search, content): string {

console.log("search value here!! "+ search);
let pattern = search.replace(/[-[]/{}()*+?.\^$|]/g, '\$&');
pattern = pattern.split(' ').filter((t) => {
return t.length > 0;
}).join('|');
const regex = new RegExp(pattern, 'gi');
return search.replace(regex, (match) => <span class="highlight">${match}</span>);
}
}

@ParVisual
Copy link

Thanks!

@ma7moud-abdallah
Copy link

how can i do it without splitting the text to two parts, i need the matched pattern get the style in it's same position of the original text

@ankitgrover
Copy link

Below is the implementation which enables you to do a little more things:

  1. Highlight single match, global match, single match which starts with search string.
  2. Make the matching case sensitive.
  3. Pass a highlighting style class.

Implementation:

import {Pipe, PipeTransform} from "@angular/core";
import {SafeHtml} from "@angular/platform-browser";

@pipe({ name: "highlightText" })
export class HighlightPipe implements PipeTransform {

/* use this for single match search */
static SINGLE_MATCH:string = "Single-Match";
/* use this for single match search with a restriction that target should start with search string */
static SINGLE_AND_STARTS_WITH_MATCH:string = "Single-And-StartsWith-Match";
/* use this for global search */
static MULTI_MATCH:string = "Multi-Match";

constructor() {
}
transform(data: string,
          highlightText: string,
          option:string = "Single-And-StartsWith-Match",
          caseSensitive:boolean = false,
          highlightStyleName:string = "search-highlight"): SafeHtml {
    if (highlightText && data && option) {
        let regex:any = "";
        let caseFlag:string = !caseSensitive ? "i" : "";
        switch (option) {
            case "Single-Match": {
                regex = new RegExp(highlightText, caseFlag);
                break;
            }
            case "Single-And-StartsWith-Match": {
                regex = new RegExp("^" + highlightText, caseFlag);
                break;
            }
            case "Multi-Match": {
                regex = new RegExp(highlightText, "g" + caseFlag);
                break;
            }
            default: {
                // default will be a global case-insensitive match
                regex = new RegExp(highlightText, "gi");
            }
        }
        return data.replace(regex, (match) => `<span class="${highlightStyleName}">${match}</span>`);

    } else {
        return data;
    }
}

}

@calebeaires
Copy link

Great, but how can I add an event click to this highlighted text after it is rendered into innerhtml?

@wuenping
Copy link

this regex pattern mean what? thanks

@andreevsm
Copy link

This code is not quite correct. For example:

input - xaxa
users: [ 'xaxalololo', 'nonoxaxaxaxa', 'wtfxaxaxaxaxaxa' ]

Should works only to first occurrence, the code below is correct:

const regex = new RegExp(`${pattern}(.*?)`, 'i');

After that:
users: [ 'xaxalololo', 'nonoxaxaxaxa', 'wtfxaxaxaxaxaxa' ]

@rafcontreras
Copy link

rafcontreras commented May 15, 2019

In case someone is having problems with @ankitgrover 's code (Thank you!) :

import { Pipe, PipeTransform } from "@angular/core";
import { SafeHtml } from "@angular/platform-browser";

@Pipe({ name: "highlightText" })
export class HighlightPipe implements PipeTransform {
    /* use this for single match search */
    static SINGLE_MATCH: string = "Single-Match";
    /* use this for single match search with a restriction that target should start with search string */
    static SINGLE_AND_STARTS_WITH_MATCH: string = "Single-And-StartsWith-Match";
    /* use this for global search */
    static MULTI_MATCH: string = "Multi-Match";

    constructor() {}
    transform(
        contentString: string = null,
        stringToHighlight: string = null,
        option: string = "Single-And-StartsWith-Match",
        caseSensitive: boolean = false,
        highlightStyleName: string = "search-highlight"
    ): SafeHtml {
        if (stringToHighlight && contentString && option) {
            let regex: any = "";
            let caseFlag: string = !caseSensitive ? "i" : "";
            switch (option) {
                case "Single-Match": {
                    regex = new RegExp(stringToHighlight, caseFlag);
                    break;
                }
                case "Single-And-StartsWith-Match": {
                    regex = new RegExp("^" + stringToHighlight, caseFlag);
                    break;
                }
                case "Multi-Match": {
                    regex = new RegExp(stringToHighlight, "g" + caseFlag);
                    break;
                }
                default: {
                    // default will be a global case-insensitive match
                    regex = new RegExp(stringToHighlight, "gi");
                }
            }
            const replaced = contentString.replace(
                regex,
                (match) => `<span class="${highlightStyleName}">${match}</span>`
            );
            return replaced;
        } else {
            return contentString;
        }
    }
}

@rafcontreras
Copy link

rafcontreras commented May 15, 2019

Usage:

<div [innerHTML]="
  string
    | highlightText
      : string
      : 'string'
      : boolean
      : 'string'
"></div>
<td>
  <div [innerHTML]="
    row.content
    | highlightText
      : searchInputValue
      : 'MULTI_MATCH'
      : true
      : 'your-class'
  "></div>
</td>

@Superkarl
Copy link

@rafcontreras use this, so you can search for special characters too:
stringToHighlight = stringToHighlight.replace(/([-[\]{}()*+?.\\^$|#,])/g,'\\$1');

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment