Highlight the search text - angular 2

A messenger displays the search results based on the input given by the user. Need to highlight the word that is been searched, while displaying the result. These are the html and component that is been used.

Component.html

 <div *ngFor = "let result of resultArray">
<div>Id : result.id </div>
<div>Summary : result.summary </div>
<div> Link : result.link </div>
</div>

Component.ts

resultArray : any = [{"id":"1","summary":"These are the results for the searched text","link":"http://www.example.com"}]

This resultArray is fetched from hitting the backend service by sending the search text as input. Based on the search text, the result is fetched. Need to highlight the searched text, similar to google search. Please find the screenshot,

enter image description here

If I search for the word "member", the occurence of the word "member" gets highlighted. How to achieve the same using angular 2. Please suggest an idea on this.

Answers:

Answer

You can do that by creating a pipe and applying that pipe to the summary part of array inside ngfor. Here is the code for Pipe:

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

@Pipe({
    name: 'highlight'
})

export class HighlightSearch implements PipeTransform {

    transform(value: any, args: any): any {
        if (!args) {return value;}
        var re = new RegExp(args, 'gi'); //'gi' for case insensitive and can use 'g' if you want the search to be case sensitive.
        return value.replace(re, "<mark>$&</mark>");
    }
}

and then in markup apply it on a string like this:

<div innerHTML="{{ str | highlight : 'search'}}"></div>

Replace 'search' with the word you want to highlight.

Hope this will help.

Answer

The selected answer has the following issues:

  1. It will return undefined if there is nothing provided in the search string
  2. The search should be case insensitive but that should not replace the original string case.

i would suggest the following code instead

transform(value: string, args: string): any {
    if (args && value) {
        let startIndex = value.toLowerCase().indexOf(args.toLowerCase());
        if (startIndex != -1) {
            let endLength = args.length;
            let matchingString = value.substr(startIndex, endLength);
            return value.replace(matchingString, "<mark>" + matchingString + "</mark>");
        }

    }
    return value;
}
Answer

One difficulty the innerHTML method has is in styling the <mark> tag. Another method is to place this in a component, allowing for much more options in styling.

highlighted-text.component.html

<mark *ngIf="matched">{{matched}}</mark>{{unmatched}}

highlighted-text.component.ts

import { Component, Input, OnChanges, OnInit } from "@angular/core";

@Component({
    selector: "highlighted-text",
    templateUrl: "./highlighted-text.component.html",
    styleUrls: ["./highlighted-text.component.css"]
})
export class HighlightedTextComponent implements OnChanges {
    @Input() needle: String;
    @Input() haystack: String;
    public matched;
    public unmatched;

    ngOnChanges(changes) {
        this.match();
    }

    match() {
        this.matched = undefined;
        this.unmatched = this.haystack;
        if (this.needle && this.haystack) {
            const needle = String(this.needle);
            const haystack = String(this.haystack);
            const startIndex = haystack.toLowerCase().indexOf(needle.toLowerCase());
            if (startIndex !== -1) {
                const endLength = needle.length;
                this.matched = haystack.substr(startIndex, endLength);
                this.unmatched = haystack.substr(needle.length);
            }
        }
    }
}

highlighted-text.component.css

mark {
    display: inline;
    margin: 0;
    padding: 0;       
    font-weight: 600;
}

Usage

<highlighted-text [needle]=searchInput [haystack]=value></highlighted-text>
Answer

If you have multiple words in your string than use pipe which accepts array and highlight each word in result.

You can use following pipe for multiple search words:-

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

@Pipe({
    name: 'highlight'
})

export class HighlightText implements PipeTransform {

    transform(value: any, args: any): any {
        if (!args) {return value;}
        for(const text of args) {
            var reText = new RegExp(text, 'gi');
            value = value.replace(reText, "<mark>" + text + "</mark>");
            //for your custom css
            // value = value.replace(reText, "<span class='highlight-search-text'>" + text + "</span>"); 


        }
        return value;
    }
}

Split you string to generate array of strings.

var searchTerms = searchKey.split(' ');

usage:

<div [innetHTML]="result | highlight:searchTerms"></div>

If you wanted to use custom class :

.highlight-search-text {
  color: black;
  font-weight: 600;
}

All the best!

Answer

To expand on Kamal's answer,

The value coming into the transform method, could be a number, perhaps a cast to string String(value) would be safe thing to do.

transform(value: string, args: string): any {
    if (args && value) {
        value = String(value); // make sure its a string
        let startIndex = value.toLowerCase().indexOf(args.toLowerCase());
        if (startIndex != -1) {
            let endLength = args.length;
            let matchingString = value.substr(startIndex, endLength);
            return value.replace(matchingString, "<mark>" + matchingString + "</mark>");
        }

    }
    return value;
}
Answer

I would suggest to escape the search String like this

 RegExp.escape = function(string) {
  if(string !== null){ return string.toString().replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
  } else return null
};

@Pipe({
    name: 'highlight'
})
export class HighlightPipe implements PipeTransform {
  constructor(private sanitizer: DomSanitizer){ }

  transform(value: any, args: any): any {
    if (!args || value == null) {
      return value;
    }
    // Match in a case insensitive maneer
    const re = new RegExp(RegExp.escape(args), 'gi');

      const  match = value.match(re);



    // If there's no match, just return the original value.
    if (!match) {
      return value;
    }

    const replacedValue = value.replace(re, "<mark>" + match[0] + "</mark>")
    return this.sanitizer.bypassSecurityTrustHtml(replacedValue)
  }
}
Answer

Building on a previous answer (HighlightedText-Component) I ended up with this:

import { Component, Input, OnChanges } from "@angular/core";

@Component({
    selector: 'highlighted-text',
    template: `
        <ng-container *ngFor="let match of result">
            <mark *ngIf="(match === needle); else nomatch">{{match}}</mark>
            <ng-template #nomatch>{{match}}</ng-template>
        </ng-container>
    `,
})
export class HighlightedTextComponent implements OnChanges {
    @Input() needle: string;
    @Input() haystack: string;
    public result: string[];

    ngOnChanges() {
        const regEx = new RegExp('(' + this.needle + ')', 'i');
        this.result = this.haystack.split(regEx);
    }
}

This way also multiple matches of the needle are highlighted. The usage of this component is similar to the one in the previous answer:

<highlighted-text [needle]="searchInput" [haystack]="value"></highlighted-text>

For me this approach using a component feels more secure, since I do not have to use "innerHtml".

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.