Highlighting a filtered result in AngularJS

AngularjsAngularjs Ng-Repeat

Angularjs Problem Overview


I'm using a ng-repeat and filter in angularJS like the phones tutorial but I'd like to highlight the search results in the page. With basic jQuery I would have simply parsed the page on key up on the input, but I'm trying to do it the angular way. Any ideas ?

My code :

<input id="search" type="text" placeholder="Recherche DCI" ng-model="search_query" autofocus>
<tr ng-repeat="dci in dcis | filter:search_query">
            <td class='marque'>{{dci.marque}} ®</td>
            <td class="dci">{{dci.dci}}</td>
 </tr>

Angularjs Solutions


Solution 1 - Angularjs

In did that for AngularJS v1.2+

HTML:

<span ng-bind-html="highlight(textToSearchThrough, searchText)"></span>

JS:

$scope.highlight = function(text, search) {
    if (!search) {
        return $sce.trustAsHtml(text);
    }
    return $sce.trustAsHtml(text.replace(new RegExp(search, 'gi'), '<span class="highlightedText">$&</span>'));
};

CSS:

.highlightedText {
    background: yellow;
}

Solution 2 - Angularjs

angular ui-utils supports only one term. I'm using the following filter rather than a scope function:

app.filter('highlight', function($sce) {
  return function(str, termsToHighlight) {
    // Sort terms by length
    termsToHighlight.sort(function(a, b) {
      return b.length - a.length;
    });
    // Regex to simultaneously replace terms
    var regex = new RegExp('(' + termsToHighlight.join('|') + ')', 'g');
    return $sce.trustAsHtml(str.replace(regex, '<span class="match">$&</span>'));
  };
});

And the HTML:

<span ng-bind-html="theText | highlight:theTerms"></span>

Solution 3 - Angularjs

Try Angular UI

Filters -> Highlite (filter). There is also Keypress directive.

Solution 4 - Angularjs

index.html

<!DOCTYPE html>
<html>
  <head>
    <script src="angular.js"></script>
    <script src="app.js"></script>
    <style>
      .highlighted { background: yellow }
    </style>
  </head>

  <body ng-app="Demo">
    <h1>Highlight text using AngularJS.</h1>

    <div class="container" ng-controller="Demo">
      <input type="text" placeholder="Search" ng-model="search.text">

      <ul>
        <!-- filter code -->
        <div ng-repeat="item in data | filter:search.text"
           ng-bind-html="item.text | highlight:search.text">
        </div>
      </ul>
    </div>
  </body>
</html>

app.js

angular.module('Demo', [])
  .controller('Demo', function($scope) {
    $scope.data = [
      { text: "<< ==== Put text to Search ===== >>" }
    ]
  })
  .filter('highlight', function($sce) {
    return function(text, phrase) {
      if (phrase) text = text.replace(new RegExp('('+phrase+')', 'gi'),
        '<span class="highlighted">$1</span>')

      return $sce.trustAsHtml(text)
    }
  })

Reference : http://codeforgeek.com/2014/12/highlight-search-result-angular-filter/ demo : http://demo.codeforgeek.com/highlight-angular/

Solution 5 - Angularjs

I hope my light example will make it easy to understand:

  app.filter('highlight', function() {
    return function(text, phrase) {
      return phrase 
        ? text.replace(new RegExp('('+phrase+')', 'gi'), '<kbd>$1</kbd>') 
        : text;
    };
  });

  <input type="text" ng-model="search.$">

  <ul>
    <li ng-repeat="item in items | filter:search">
      <div ng-bind-html="item | highlight:search.$"></div>
    </li>
  </ul>

enter image description here

Solution 6 - Angularjs

There is standart Highlight filter in the angular-bootstrap: typeaheadHighlight

Usage

<span ng-bind-html="text | typeaheadHighlight:query"></span>

With scope {text:"Hello world", query:"world"} renders in

<span...>Hello <strong>world</strong></span>

Solution 7 - Angularjs

Going off of @uri's answer in this thread, I modified it to work with a single string OR a string array.

Here's the TypeScript version

module myApp.Filters.Highlight {
    "use strict";

	class HighlightFilter {
		//This will wrap matching search terms with an element to visually highlight strings
		//Usage: {{fullString | highlight:'partial string'}}
		//Usage: {{fullString | highlight:['partial', 'string, 'example']}}

		static $inject = ["$sce"];

		constructor($sce: angular.ISCEService) {

			// The `terms` could be a string, or an array of strings, so we have to use the `any` type here
			/* tslint:disable: no-any */
			return (str: string, terms: any) => {
				/* tslint:enable */

				if (terms) {
					let allTermsRegexStr: string;

					if (typeof terms === "string") {
						allTermsRegexStr = terms;
					} else { //assume a string array
						// Sort array by length then  join with regex pipe separator
						allTermsRegexStr = terms.sort((a: string, b: string) => b.length - a.length).join('|');
					}

				//Escape characters that have meaning in regular expressions
				//via: http://stackoverflow.com/a/6969486/79677
				allTermsRegexStr = allTermsRegexStr.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");

					// Regex to simultaneously replace terms - case insensitive!
					var regex = new RegExp('(' + allTermsRegexStr + ')', 'ig');

					return $sce.trustAsHtml(str.replace(regex, '<mark class="highlight">$&</mark>'));
				} else {
					return str;
				}
			};
		}
	}

	angular
		.module("myApp")
		.filter("highlight", HighlightFilter);
};

Which translates to this in JavaScript:

var myApp;
(function (myApp) {
    var Filters;
    (function (Filters) {
        var Highlight;
        (function (Highlight) {
            "use strict";
            var HighlightFilter = (function () {
                function HighlightFilter($sce) {
                    // The `terms` could be a string, or an array of strings, so we have to use the `any` type here
                    /* tslint:disable: no-any */
                    return function (str, terms) {
                        /* tslint:enable */
                        if (terms) {
                            var allTermsRegexStr;
                            if (typeof terms === "string") {
                                allTermsRegexStr = terms;
                            }
                            else {
                                // Sort array by length then  join with regex pipe separator
                                allTermsRegexStr = terms.sort(function (a, b) { return b.length - a.length; }).join('|');
                            }

                            //Escape characters that have meaning in regular expressions
                            //via: http://stackoverflow.com/a/6969486/79677
                            allTermsRegexStr = allTermsRegexStr.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");

                            // Regex to simultaneously replace terms - case insensitive!
                            var regex = new RegExp('(' + allTermsRegexStr + ')', 'ig');
                            return $sce.trustAsHtml(str.replace(regex, '<mark class="highlight">$&</mark>'));
                        }
                        else {
                            return str;
                        }
                    };
                }
                //This will wrap matching search terms with an element to visually highlight strings
                //Usage: {{fullString | highlight:'partial string'}}
                //Usage: {{fullString | highlight:['partial', 'string, 'example']}}
                HighlightFilter.$inject = ["$sce"];
                return HighlightFilter;
            })();
            angular.module("myApp").filter("highlight", HighlightFilter);
        })(Highlight = Filters.Highlight || (Filters.Highlight = {}));
    })(Filters = myApp.Filters || (myApp.Filters = {}));
})(myApp|| (myApp= {}));
;

Or if you just want a simple JavaScript implementation without those generated namespaces:

app.filter('highlight', ['$sce', function($sce) {
  	return function (str, terms) {
		if (terms) {
			var allTermsRegexStr;
			if (typeof terms === "string") {
				allTermsRegexStr = terms;
			}
			else {
				// Sort array by length then  join with regex pipe separator
				allTermsRegexStr = terms.sort(function (a, b) { return b.length - a.length; }).join('|');
			}

			//Escape characters that have meaning in regular expressions
			//via: http://stackoverflow.com/a/6969486/79677
			allTermsRegexStr = allTermsRegexStr.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");

			// Regex to simultaneously replace terms - case insensitive!
			var regex = new RegExp('(' + allTermsRegexStr + ')', 'ig');
			return $sce.trustAsHtml(str.replace(regex, '<mark class="highlight">$&</mark>'));
		}
		else {
			return str;
		}
	};
}]);

EDITED to include a fix that would have previously broken this is someone searched for . or any other character that had meaning in a regular expression. Now those characters get escaped first.

Solution 8 - Angularjs

Use ng-class that is applied when the search term is related to the data the element contains.

So on your ng-repeated elements, you'd have ng-class="{ className: search_query==elementRelatedValue}"

which would apply class "className" to elements dynamically when the condition is met.

Solution 9 - Angularjs

My solution for highlight, used this with angular-ui-tree element: https://codepen.io/shnigi/pen/jKeaYG

angular.module('myApp').filter('highlightFilter', $sce =>
 function (element, searchInput) {
   element = element.replace(new RegExp(`(${searchInput})`, 'gi'),
             '<span class="highlighted">$&</span>');
   return $sce.trustAsHtml(element);
 });

Add css:

.highlighted {
  color: orange;
}

HTML:

<p ng-repeat="person in persons | filter:search.value">
  <span ng-bind-html="person | highlightFilter:search.value"></span>
</p>

And to add search input:

<input type="search" ng-model="search.value">

Solution 10 - Angularjs

About the problems with special caracter, I think just escaping you might lose regex search.

What about this:

function(text, search) {
	if (!search || (search && search.length < 3)) {
		return $sce.trustAsHtml(text);
	}
	
	regexp  = '';
	
	try {
		regexp = new RegExp(search, 'gi');
	} catch(e) {
		return $sce.trustAsHtml(text);
	}
	
	return $sce.trustAsHtml(text.replace(regexp, '<span class="highlight">$&</span>'));
};

An invalid regexp could be user just typing the text:

  • valid: m
  • invalid: m[
  • invalid: m[ô
  • invalid: m[ôo
  • valid: m[ôo]
  • valid: m[ôo]n
  • valid: m[ôo]ni
  • valid: m[ôo]nic
  • valid: m[ôo]nica

What do you think @Mik Cox?

Solution 11 - Angularjs

Another proposition:

app.filter('wrapText', wrapText);

function wrapText($sce) {
    return function (source, needle, wrap) {
        var regex;

        if (typeof needle === 'string') {
            regex = new RegExp(needle, "gi");
        } else {
            regex = needle;
        }

        if (source.match(regex)) {
            source = source.replace(regex, function (match) {
                return $('<i></i>').append($(wrap).text(match)).html();
            });
        }

        return $sce.trustAsHtml(source);
    };
} // wrapText

wrapText.$inject = ['$sce'];

// use like this
$filter('wrapText')('This is a word, really!', 'word', '<span class="highlight"></span>');
// or like this
{{ 'This is a word, really!' | wrapText:'word':'<span class="highlight"></span>' }}

I'm open to criticism ! ;-)

Solution 12 - Angularjs

Thanks for asking this as it was something I was dealing with as well.

Two things though:

First, The top answer is great but the comment on it is accurate that highlight() has problem with special characters. That comment suggests using an escaping chain which will work but they suggest using unescape() which is being phased out. What I ended up with:

$sce.trustAsHtml(decodeURI(escape(text).replace(new RegExp(escape(search), 'gi'), '<span class="highlightedText">$&</span>')));

Second, I was trying to do this in a data bound list of URLs. While in the highlight() string, you don't need to data bind.

Example:

<li>{{item.headers.host}}{{item.url}}</li>

Became:

<span ng-bind-html="highlight(item.headers.host+item.url, item.match)"></span>

Was running into problems with leaving them in {{ }} and getting all sorts of errors.

Hope this helps anybody running into the same problems.

Solution 13 - Angularjs

If you are using the angular material library there is a built in directive called md-highlight-text

From the documentation:

<input placeholder="Enter a search term..." ng-model="searchTerm" type="text">
<ul>
  <li ng-repeat="result in results" md-highlight-text="searchTerm">
    {{result.text}}
  </li>
</ul>

Link to docs: https://material.angularjs.org/latest/api/directive/mdHighlightText

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionLukmoView Question on Stackoverflow
Solution 1 - AngularjsDmitri AlgazinView Answer on Stackoverflow
Solution 2 - AngularjsUriView Answer on Stackoverflow
Solution 3 - AngularjsDmitriyView Answer on Stackoverflow
Solution 4 - AngularjsShaikh ShahidView Answer on Stackoverflow
Solution 5 - AngularjsmrdedView Answer on Stackoverflow
Solution 6 - Angularjsuser854301View Answer on Stackoverflow
Solution 7 - AngularjsFiniteLooperView Answer on Stackoverflow
Solution 8 - Angularjsholographic-principleView Answer on Stackoverflow
Solution 9 - AngularjsShnigiView Answer on Stackoverflow
Solution 10 - AngularjsAndre MedeirosView Answer on Stackoverflow
Solution 11 - AngularjsMonkey MonkView Answer on Stackoverflow
Solution 12 - AngularjsmattjayView Answer on Stackoverflow
Solution 13 - AngularjsAnthonyView Answer on Stackoverflow