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>
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;
}
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>
Try Angular UI
Filters -> Highlite (filter).
There is also Keypress directive.
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/
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>
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>
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.
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.
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">
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?
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 ! ;-)
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.
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
Related
Need to remove comma if value is empty works good if I have value
present at start or middle; But same doesn't work in this scenario.
app.filter('isCSV', function() {
return function(data) {
return (data !== '') ? data + ', ' : '';
};
});
Angularjs ng repeat for addressline - Plunker
I would instead operate on arrays of properties and use a pair of filters, one to remove empty values, and one to join the array.
This way it's very explicit about what properties you are displaying.
<body ng-controller="MainCtrl">
<ul>
<li ng-repeat="item in details">
{{ [ item.address0, item.address1, item.address2, item.address3] | removeEmpties | joinBy:', ' }}
</li>
</ul>
</body>
With the following filters:
app.filter('removeEmpties', function () {
return function (input,delimiter) {
return (input || []).filter(function (i) { return !!i; });
};
});
app.filter('joinBy', function () {
return function (input,delimiter) {
return (input || []).join(delimiter || ',');
};
});
Here's the updated Plunkr
Tricky but should work in your case Also no filter need
{{ item.address0 }} <span ng-if="item.address1">,
</span> {{ item.address1}}<span ng-if="item.address2">,</span>{{
item.address2}}
<span ng-if="item.address3">,</span>{{ item.address3}}
Here is working example
I would prefer writing a function instead of adding a filter so many times.
$scope.mergeAddresses = function(item) {
var address = item.address0;
[1,2,3].forEach(function(i) {
var add = item["address"+i];
if (!add) return;
address += (address ? ", " : "") + add;
});
if (address) address += ".";
return address;
}
Plunker
I'm learning angularjs and got an exercise that wants me to Use angular filter to show a title in the following format :
first letter of each word upper cased and each other letter lower cased also
remove any non-English letters from the title. For example:
A title with the name
“##THIS is a Title!!”
should be changed to
“This Is A Title”
I'm getting each title from an array of objects and present them like so.
<div ng-repeat="obj in objects">
<h3 class="panel-title">{{obj.Title}}</h3>
</div>
i understand that filter receives an array and filters through it . but this requires me to filter the string.
been searching for a while, how can i do this?
please refer below fiddle
http://jsfiddle.net/HB7LU/28315/
<div ng-controller="MyCtrl">
Hello, {{ name | ordinal|capitalize }}
</div>
var myApp = angular.module('myApp',[]);
//myApp.directive('myDirective', function() {});
//myApp.factory('myService', function() {});
function MyCtrl($scope) {
$scope.name = 'Super hero!!12##3';
}
myApp.filter('ordinal', function() {
// Create the return function
// set the required parameter name to **number**
return function(strTitle) {
// Ensure that the passed in data is a number
// If the data is not a number or is less than one (thus not having a cardinal value) return it unmodified.
strTitle=strTitle.replace(/[^a-zA-Z ]/g, "")
return strTitle;
}
});
myApp.filter('capitalize', function() {
return function(input){
if(input.indexOf(' ') !== -1){
var inputPieces,
i;
input = input.toLowerCase();
inputPieces = input.split(' ');
for(i = 0; i < inputPieces.length; i++){
inputPieces[i] = capitalizeString(inputPieces[i]);
}
return inputPieces.toString().replace(/,/g, ' ');
}
else {
input = input.toLowerCase();
return capitalizeString(input);
}
function capitalizeString(inputString){
return inputString.substring(0,1).toUpperCase() + inputString.substring(1);
}
};
});
angular.module('app', []).filter('myFilter', function(){
return function(input){
if(!input)
return;
var out = '';
var english = /^[A-Za-z0-9 ]*$/;
for(var letter of input)
if(english.test(letter))
out += letter;
var result = '';
for(var i = 0; i < out.length; i++)
result += out[i][(i === 0 || out[i-1] == ' ') ? 'toUpperCase' : 'toLowerCase']();
return result;
}
})
<script src="//code.angularjs.org/snapshot/angular.min.js"></script>
<body ng-app="app">
<input ng-init='text="##THIS is a Title!!"' type='text' ng-model='text'>
<p>{{text | myFilter}}</p>
</body>
I want to use the filter in angular and want to filter for multiple values, if it has either one of the values then it should be displayed.
I have for example this structure:
An object movie which has the property genres and I want to filter for Action and Comedy.
I know I can do filter:({genres: 'Action'} || {genres: 'Comedy'}), but what to do if I want to filter it dynamically. E.g. filter: variableX
How do I set variableX in the $scope, when I have an array of the genres I have to filter?
I could construct it as a string and then do an eval() but I don't want to use eval()...
I would just create a custom filter. They are not that hard.
angular.module('myFilters', []).
filter('bygenre', function() {
return function(movies,genres) {
var out = [];
// Filter logic here, adding matches to the out var.
return out;
}
});
template:
<h1>Movies</h1>
<div ng-init="movies = [
{title:'Man on the Moon', genre:'action'},
{title:'Meet the Robinsons', genre:'family'},
{title:'Sphere', genre:'action'}
];" />
<input type="checkbox" ng-model="genrefilters.action" />Action
<br />
<input type="checkbox" ng-model="genrefilters.family" />Family
<br />{{genrefilters.action}}::{{genrefilters.family}}
<ul>
<li ng-repeat="movie in movies | bygenre:genrefilters">{{movie.title}}: {{movie.genre}}</li>
</ul>
Edit here is the link: Creating Angular Filters
UPDATE: Here is a fiddle that has an exact demo of my suggestion.
You can use a controller function to filter.
function MoviesCtrl($scope) {
$scope.movies = [{name:'Shrek', genre:'Comedy'},
{name:'Die Hard', genre:'Action'},
{name:'The Godfather', genre:'Drama'}];
$scope.selectedGenres = ['Action','Drama'];
$scope.filterByGenres = function(movie) {
return ($scope.selectedGenres.indexOf(movie.genre) !== -1);
};
}
HTML:
<div ng-controller="MoviesCtrl">
<ul>
<li ng-repeat="movie in movies | filter:filterByGenres">
{{ movie.name }} {{ movie.genre }}
</li>
</ul>
</div>
Creating a custom filter might be overkill here, you can just pass in a custom comparator, if you have the multiples values like:
$scope.selectedGenres = "Action, Drama";
$scope.containsComparator = function(expected, actual){
return actual.indexOf(expected) > -1;
};
then in the filter:
filter:{name:selectedGenres}:containsComparator
Here is the implementation of custom filter, which will filter the data using array of values.It will support multiple key object with both array and single value of keys. As mentioned inangularJS API AngularJS filter Doc supports multiple key filter with single value, but below custom filter will support same feature as angularJS and also supports array of values and combination of both array and single value of keys.Please find the code snippet below,
myApp.filter('filterMultiple',['$filter',function ($filter) {
return function (items, keyObj) {
var filterObj = {
data:items,
filteredData:[],
applyFilter : function(obj,key){
var fData = [];
if (this.filteredData.length == 0)
this.filteredData = this.data;
if (obj){
var fObj = {};
if (!angular.isArray(obj)){
fObj[key] = obj;
fData = fData.concat($filter('filter')(this.filteredData,fObj));
} else if (angular.isArray(obj)){
if (obj.length > 0){
for (var i=0;i<obj.length;i++){
if (angular.isDefined(obj[i])){
fObj[key] = obj[i];
fData = fData.concat($filter('filter')(this.filteredData,fObj));
}
}
}
}
if (fData.length > 0){
this.filteredData = fData;
}
}
}
};
if (keyObj){
angular.forEach(keyObj,function(obj,key){
filterObj.applyFilter(obj,key);
});
}
return filterObj.filteredData;
}
}]);
Usage:
arrayOfObjectswithKeys | filterMultiple:{key1:['value1','value2','value3',...etc],key2:'value4',key3:[value5,value6,...etc]}
Here is a fiddle example with implementation of above "filterMutiple" custom filter.
:::Fiddle Example:::
If you want to filter on Array of Objects then you can give
filter:({genres: 'Action', key :value }.
Individual property will be filtered by particular filter given for that property.
But if you wanted to something like filter by individual Property and filter globally for all properties then you can do something like this.
<tr ng-repeat="supp in $data | filter : filterObject | filter : search">
Where "filterObject" is an object for searching an individual property and "Search" will search in every property globally.
~Atul
I've spent some time on it and thanks to #chrismarx, I saw that angular's default filterFilter allows you to pass your own comparator. Here's the edited comparator for multiple values:
function hasCustomToString(obj) {
return angular.isFunction(obj.toString) && obj.toString !== Object.prototype.toString;
}
var comparator = function (actual, expected) {
if (angular.isUndefined(actual)) {
// No substring matching against `undefined`
return false;
}
if ((actual === null) || (expected === null)) {
// No substring matching against `null`; only match against `null`
return actual === expected;
}
// I edited this to check if not array
if ((angular.isObject(expected) && !angular.isArray(expected)) || (angular.isObject(actual) && !hasCustomToString(actual))) {
// Should not compare primitives against objects, unless they have custom `toString` method
return false;
}
// This is where magic happens
actual = angular.lowercase('' + actual);
if (angular.isArray(expected)) {
var match = false;
expected.forEach(function (e) {
e = angular.lowercase('' + e);
if (actual.indexOf(e) !== -1) {
match = true;
}
});
return match;
} else {
expected = angular.lowercase('' + expected);
return actual.indexOf(expected) !== -1;
}
};
And if we want to make a custom filter for DRY:
angular.module('myApp')
.filter('filterWithOr', function ($filter) {
var comparator = function (actual, expected) {
if (angular.isUndefined(actual)) {
// No substring matching against `undefined`
return false;
}
if ((actual === null) || (expected === null)) {
// No substring matching against `null`; only match against `null`
return actual === expected;
}
if ((angular.isObject(expected) && !angular.isArray(expected)) || (angular.isObject(actual) && !hasCustomToString(actual))) {
// Should not compare primitives against objects, unless they have custom `toString` method
return false;
}
console.log('ACTUAL EXPECTED')
console.log(actual)
console.log(expected)
actual = angular.lowercase('' + actual);
if (angular.isArray(expected)) {
var match = false;
expected.forEach(function (e) {
console.log('forEach')
console.log(e)
e = angular.lowercase('' + e);
if (actual.indexOf(e) !== -1) {
match = true;
}
});
return match;
} else {
expected = angular.lowercase('' + expected);
return actual.indexOf(expected) !== -1;
}
};
return function (array, expression) {
return $filter('filter')(array, expression, comparator);
};
});
And then we can use it anywhere we want:
$scope.list=[
{name:'Jack Bauer'},
{name:'Chuck Norris'},
{name:'Superman'},
{name:'Batman'},
{name:'Spiderman'},
{name:'Hulk'}
];
<ul>
<li ng-repeat="item in list | filterWithOr:{name:['Jack','Chuck']}">
{{item.name}}
</li>
</ul>
Finally here's a plunkr.
Note: Expected array should only contain simple objects like String, Number etc.
you can use searchField filter of angular.filter
JS:
$scope.users = [
{ first_name: 'Sharon', last_name: 'Melendez' },
{ first_name: 'Edmundo', last_name: 'Hepler' },
{ first_name: 'Marsha', last_name: 'Letourneau' }
];
HTML:
<input ng-model="search" placeholder="search by full name"/>
<th ng-repeat="user in users | searchField: 'first_name': 'last_name' | filter: search">
{{ user.first_name }} {{ user.last_name }}
</th>
<!-- so now you can search by full name -->
You can also use ngIf if the situation permits:
<div ng-repeat="p in [
{ name: 'Justin' },
{ name: 'Jimi' },
{ name: 'Bob' }
]" ng-if="['Jimi', 'Bob'].indexOf(e.name) > -1">
{{ p.name }} is cool
</div>
The quickest solution that I've found is to use the filterBy filter from angular-filter, for example:
<input type="text" placeholder="Search by name or genre" ng-model="ctrl.search"/>
<ul>
<li ng-repeat="movie in ctrl.movies | filterBy: ['name', 'genre']: ctrl.search">
{{movie.name}} ({{movie.genre}}) - {{movie.rating}}
</li>
</ul>
The upside is that angular-filter is a fairly popular library (~2.6k stars on GitHub) which is still actively developed and maintained, so it should be fine to add it to your project as a dependency.
I believe this is what you're looking for:
<div>{{ (collection | fitler1:args) + (collection | filter2:args) }}</div>
Please try this
var m = angular.module('yourModuleName');
m.filter('advancefilter', ['$filter', function($filter){
return function(data, text){
var textArr = text.split(' ');
angular.forEach(textArr, function(test){
if(test){
data = $filter('filter')(data, test);
}
});
return data;
}
}]);
Lets assume you have two array, one for movie and one for genre
Just use the filter as: filter:{genres: genres.type}
Here genres being the array and type has value for genre
I wrote this for strings AND functionality (I know it's not the question but I searched for it and got here), maybe it can be expanded.
String.prototype.contains = function(str) {
return this.indexOf(str) != -1;
};
String.prototype.containsAll = function(strArray) {
for (var i = 0; i < strArray.length; i++) {
if (!this.contains(strArray[i])) {
return false;
}
}
return true;
}
app.filter('filterMultiple', function() {
return function(items, filterDict) {
return items.filter(function(item) {
for (filterKey in filterDict) {
if (filterDict[filterKey] instanceof Array) {
if (!item[filterKey].containsAll(filterDict[filterKey])) {
return false;
}
} else {
if (!item[filterKey].contains(filterDict[filterKey])) {
return false;
}
}
}
return true;
});
};
});
Usage:
<li ng-repeat="x in array | filterMultiple:{key1: value1, key2:[value21, value22]}">{{x.name}}</li>
Angular Or Filter Module
$filter('orFilter')([{..}, {..} ...], {arg1, arg2, ...}, false)
here is the link: https://github.com/webyonet/angular-or-filter
I had similar situation. Writing custom filter worked for me. Hope this helps!
JS:
App.filter('searchMovies', function() {
return function (items, letter) {
var resulsts = [];
var itemMatch = new RegExp(letter, 'i');
for (var i = 0; i < items.length; i++) {
var item = items[i];
if ( itemMatch.test(item.name) || itemMatch.test(item.genre)) {
results.push(item);
}
}
return results;
};
});
HTML:
<div ng-controller="MoviesCtrl">
<ul>
<li ng-repeat="movie in movies | searchMovies:filterByGenres">
{{ movie.name }} {{ movie.genre }}
</li>
</ul>
</div>
Here is my example how create filter and directive for table jsfiddle
directive get list (datas) and create table with filters
<div ng-app="autoDrops" ng-controller="HomeController">
<div class="row">
<div class="col-md-12">
<h1>{{title}}</h1>
<ng-Multiselect array-List="datas"></ng-Multiselect>
</div>
</div>
</div>
my pleasure if i help you
Too late to join the party but may be it can help someone:
We can do it in two step, first filter by first property and then concatenate by second filter:
$scope.filterd = $filter('filter')($scope.empList, { dept: "account" });
$scope.filterd = $scope.filterd.concat($filter('filter')($scope.empList, { dept: "sales" }));
See the working fiddle with multiple property filter
OPTION 1:
Using Angular providered filter comparator parameter
// declaring a comparator method
$scope.filterBy = function(actual, expected) {
return _.contains(expected, actual); // uses underscore library contains method
};
var employees = [{name: 'a'}, {name: 'b'}, {name: 'c'}, {name: 'd'}];
// filter employees with name matching with either 'a' or 'c'
var filteredEmployees = $filter('filter')(employees, {name: ['a','c']}, $scope.filterBy);
OPTION 2:
Using Angular providered filter negation
var employees = [{name: 'a'}, {name: 'b'}, {name: 'c'}, {name: 'd'}];
// filter employees with name matching with either 'a' or 'c'
var filteredEmployees = $filter('filter')($filter('filter')(employees, {name: '!d'}), {name: '!b'});
My solution
ng-repeat="movie in movies | filter: {'Action'} + filter: {'Comedy}"
the best answer is :
filter:({genres: 'Action', genres: 'Comedy'}
I have angular 1.3, and i have the following array:
data : [
{
id :2,
name : "danny davids",
age :9
},
{
id :3,
name : "sanny gordon",
age :9
}
]
I want the filter to do the follwing:
When i start writing the word "s", i want the danny davids to disappear, right now the default behavior is, both of them are still shown (the s is in the end of the last name of danny).
strict mode is something that i dont want to use, the behavior i want is:
if there is no value in the input, i want to see all, if i start to write i want to see the exact one by firstName/lastName.
is there a default filter for this in angular 1.3?
You can filter match by any characters:
Sample condition:
yourDataList.display.toLowerCase().indexOf(searchData) !== -1;
Example:
function createFilterForAnycharacters(searchData) {
var lowercaseQuery = query.toLowerCase();
return function filterFn(yourDataList) {
return (yourDataList.display.toLowerCase().indexOf(searchData) !== -1);
};
}
I suggest using $filter by a custom filter function for you ng-repeat. According to the documentation, $filter expects
function(value, index, array): A predicate function can be used to write arbitrary filters. The function is called for each element of the array, with the element, its index, and the entire array itself as arguments.
And only elements that return true with be shown. So all you have to do is write that function.
Your filter function might look like this:
$scope.filterData = function (obj) {
return anyNameStartsWith(obj.name, $scope.searchFilter);
};
function anyNameStartsWith (fullname, search) {
//validate if name is null or not a string if needed
if (search === '')
return true;
var delimeterRegex = /[ _-]+/;
//split the fullname into individual names
var names = fullname.split(delimeterRegex);
//do any of the names in the array start with the search string
return names.some(function(name) {
return name.toLowerCase().indexOf(search.toLowerCase()) === 0;
});
}
Your HTML might look something like this:
<input type="text" ng-model="searchFilter" />
<div ng-repeat="obj in data | filter : filterData">
Id: {{obj.id}}
Name: {{obj.name}}
</div>
A demo via plnkr
Use this custom filter to get result match starting characters
app.filter('startsWithLetter', function () {
return function (items, letter) {
var filtered = [];
for (var i = 0; i < items.length; i++) {
var item = items[i];
if (item.substr(0,letter.length).toLowerCase() == letter.toLowerCase()) {
filtered.push(item);
}
}
return filtered;
};
});
it works for your scenario, you can create custom filter
below is html code
<div ng-app="app">
<div ng-controller="PersonCtrl as person">
<input type="text" ng-model="letter" placeholder="Enter a letter to filter">
<ul>
<li ng-repeat="a in person.data | startsWithLetter:letter">
{{a.name}}
</li>
</ul>
</div>
</div>
js code
var app = angular.module('app', []);
app.filter('startsWithLetter', function () {
return function (items, letter) {
var filtered = [];
var letterMatch = new RegExp(letter, 'i');
for (var i = 0; i < items.length; i++) {
var item = items[i];
if (letterMatch.test(item.name.substring(0, 1))) {
filtered.push(item);
}
}
return filtered;
};
});
app.controller('PersonCtrl', function () {
this.data = [
{
id :2,
name : "danny davids",
age :9
},
{
id :3,
name : "sanny gordon",
age :9
}
]
});
Need to create a custom filter function to do this. There is no default method to match first character in angular.
https://docs.angularjs.org/guide/filter
I'm wondering if there's an easy way in Angular to filter a table using ng-repeat on specific columns using or logic, rather than and. Right now, my filter is searching everything in the table (10+ columns of data), when it really only needs to filter on 2 columns of data (ID and Name).
I've managed to get it down to look only at those 2 columns when filtering (by using an object in the filter expression as per the docs and looking at this SO answer), but it's using and logic, which is too specific. I'd like to get it to use or logic, but am having trouble.
My HTML
<input type="text" ng-model="filterText" />
<table>
<tr ng-repeat="item in data"><td>{{ item.id }}</td><td>{{ item.name }}</td>...</tr>
</table>
My filter logic:
$filter('filter')(data, {id:$scope.filterText, name:$scope.filterText})
The filtering works, but again, it's taking the intersection of the matching columns rather than the union. Thanks!
It's not hard to create a custom filter which allows you to have as many arguments as you want. Below is an example of a filter with one and two arguments, but you can add as many as you need.
Example JS:
var app = angular.module('myApp',[]);
app.filter('myTableFilter', function(){
// Just add arguments to your HTML separated by :
// And add them as parameters here, for example:
// return function(dataArray, searchTerm, argumentTwo, argumentThree) {
return function(dataArray, searchTerm) {
// If no array is given, exit.
if (!dataArray) {
return;
}
// If no search term exists, return the array unfiltered.
else if (!searchTerm) {
return dataArray;
}
// Otherwise, continue.
else {
// Convert filter text to lower case.
var term = searchTerm.toLowerCase();
// Return the array and filter it by looking for any occurrences of the search term in each items id or name.
return dataArray.filter(function(item){
var termInId = item.id.toLowerCase().indexOf(term) > -1;
var termInName = item.name.toLowerCase().indexOf(term) > -1;
return termInId || termInName;
});
}
}
});
Then in your HTML:
<tr ng-repeat="item in data | myTableFilter:filterText">
Or if you want to use multiple arguments:
<tr ng-repeat="item in data | myTableFilter:filterText:argumentTwo:argumentThree">
Use this to search on All Columns (can be slow): search.$
AngularJS API: filter
Any Column Search:
<input ng-model="search.$">
<table>
<tr ng-repeat="friendObj in friends | filter:search:strict">
...
To expand on the excellent answer by #charlietfl, here's a custom filter that filters by one column(property) which is passed to the function dynamically instead of being hard-coded. This would allow you to use the filter in different tables.
var app=angular.module('myApp',[]);
app.filter('filterByProperty', function () {
/* array is first argument, each addiitonal argument is prefixed by a ":" in filter markup*/
return function (dataArray, searchTerm, propertyName) {
if (!dataArray) return;
/* when term is cleared, return full array*/
if (!searchTerm) {
return dataArray
} else {
/* otherwise filter the array */
var term = searchTerm.toLowerCase();
return dataArray.filter(function (item) {
return item[propertyName].toLowerCase().indexOf(term) > -1;
});
}
}
});
Now on the mark-up side
<input type="text" ng-model="filterText" />
<table>
<tr ng-repeat="item in data |filterByProperty:filterText:'name'"><td>{{ item.id }}</td><td>{{ item.name }}</td>...</tr>
</table>
I figured it out- I had to write my own custom filter. Here is my solution:
var filteredData;
filteredData = $filter('filter')(data, function(data) {
if ($scope.filter) {
return data.id.toString().indexOf($scope.filter) > -1 || data.name.toString().indexOf($scope.filter) > -1;
} else {
return true;
}
});
I created this filter to perform search in several fields:
var find = function () {
return function (items,array) {
var model = array.model;
var fields = array.fields;
var clearOnEmpty = array.clearOnEmpty || false;
var filtered = [];
var inFields = function(row,query) {
var finded = false;
for ( var i in fields ) {
var field = row[fields[i]];
if ( field != undefined ) {
finded = angular.lowercase(row[fields[i]]).indexOf(query || '') !== -1;
}
if ( finded ) break;
}
return finded;
};
if ( clearOnEmpty && model == "" ) return filtered;
for (var i in items) {
var row = items[i];
var query = angular.lowercase(model);
if (query.indexOf(" ") > 0) {
var query_array = query.split(" ");
var x;
for (x in query_array) {
query = query_array[x];
var search_result = true;
if ( !inFields(row,query) ) {
search_result = false;
break;
}
}
} else {
search_result = inFields(row,query);
}
if ( search_result ) {
filtered.push(row);
}
}
return filtered;
};
};
How to use:
<tr repeat="item in colletion
| find: {
model : model, // Input model
fields : [ // Array of fields to filter
'FIELD1',
'FIELD2',
'FIELD3'
],
clearOnEmpty: true // Clear rows on empty model (not obligatory)
} "></tr>
Easily We can do this type Following written code according you will easily create another field filter....
var myApp = angular.module('myApp',[]);
myApp.filter('myfilter',myfilter);
function myfilter(){
return function (items, filters) {
if (filters == null) {
return items;
}
var filtered = [];
//Apply filter
angular.forEach(items, function (item) {
if ((filters.Name == '' || angular.lowercase(item.Name).indexOf(angular.lowercase(filters.Name)) >= 0)
)
{
filtered.push(item);
}
});
return filtered;
};
}
myApp.controller('mycontroller',['$scope',function($scope){
$scope.filters={Name:'',MathsMarks:''};
$scope.students=[];
var i=0;
for(i=0;i<5;i++){
var item={Name:'',Marks:[]};
item.Name='student' + i;
item.Marks.push({Maths:50-i,Science:50 +i});
$scope.students.push(item);
}
}]);
<html ng-app='myApp'>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.21/angular.min.js"></script>
</head>
<body ng-controller='mycontroller'>
<input type='text' name='studentName' ng-model="filters.Name" placeholder='Enter Student Name'>
<div ng-repeat="student in students | myfilter: filters">
Name : {{student.Name}} Marks == >
<span ng-repeat="m in student.Marks">Maths:{{m.Maths}} Science:{{m.Science}}</span>
</div>
</body>
</html>
Here is my solution, it's very lazy, it will search on all strings in array on first level, you could update this to recusively go down the tree, but this should be good enough...
app.filter('filterAll', function () {
return function (dataArray, searchTerm, propertyNames) {
if (!dataArray) return;
if (!searchTerm) {
return dataArray;
} else {
if (propertyNames == undefined) {
propertyNames = [];
for (var property in dataArray[0]) {
if(typeof dataArray[0][property] == "string" &&
property != "$$hashKey" &&
property != "UnitName" )
propertyNames.push(property);
}
}
console.log("propertyNames", propertyNames);
var term = searchTerm.toLowerCase();
return dataArray.filter(function (item) {
var found = false;
propertyNames.forEach(function(val) {
if (!found) {
if (item[val] != null && item[val].toLowerCase().indexOf(term) > -1)
found = true;
}
});
return found;
});
}
}
});
see this link Filter multiple object properties together in AngularJS