angular filtered dropdown model not updating - angularjs

I'm trying to update a filtered dropdowns(combobox) model on click of a button. The model value is changing but dropdown still shows an empty selected option. If I remove the filter everything works as it should (without filtering of course).
Plunker: http://plnkr.co/edit/a48JSEiiATrkcQKxfJjx?p=preview
JS:
$scope.tasks = [
{name:'Task1', taskid: 1, custid: 2},
{name:'Task2', taskid: 2, custid: 2},
{name:'Task3', taskid: 3, custid: 3}
];
$scope.myinfo = {};
$scope.updateinfo = {name:'Cust Name', custid: 3, taskid: 3};
$scope.setMyinfo = function(){
$scope.myinfo = $scope.updateinfo;
};
});
app.filter('taskFilter', function() {
return function(input, criteria) {
var result = [];
for (var i = 0; i < input.length; i++) {
if(input[i].custid == criteria || input[i].custid === undefined){
result.push(input[i]);
}
}
return result;
}
HTML:
<button ng-click="setMyinfo()">Button</button>
<br> <br>
Filtered:
<select ng-model="myinfo.taskid">
<option>---</option>
<option ng-repeat="task in tasks | taskFilter:myinfo.custid" value="{{task.taskid}}">{{task.name}}</option>
</select>
Unfiltered:
<select ng-model="myinfo.taskid">
<option>---</option>
<option ng-repeat="task in tasks" value="{{task.taskid}}">{{task.name}}</option>
</select>
<br><br>
Value: {{myinfo.taskid}}
Thanks you for helping!

pre-filter the list by key and value:
<div ng-repeat="(k,v) in filterCustId(tasks)">
{{k}} {{v.pos}}
</div>
And on the Controller:
$scope.filterCustId = function(items) {
var result = {};
angular.forEach(items, function(value, key) {
if (!value.hasOwnProperty('custid')) {
result[key] = value;
}
});
return result;
}
From:
https://stackoverflow.com/a/14789258/4668696

Please update code following line
if(input[i].custid == criteria || input[i].custid === undefined){
by
if(input[i].custid == criteria || input[i].custid === undefined || criteria == undefined){

Related

How to programmatically check checkbox with dynamic model name?

I have created a some checkboxes with dynamic model name like this:
<label ng-repeat="item in main.itemDetails">
<input type="checkbox" checklist-value="item.price"
ng-click="itemChanged(item)" ng-model="checkboxes[item.name]">
{{item.name}} - <b>{{item.price}} €</b>
<br>
</label>
In my controller, I want to check some checkboxes if their model's name exists in array $scope.selectedItems.
I'm trying to do something like this but it isn't working:
for (var i = 0, j = $scope.selectedItems.length; i<j; i++) {
$scope['selectedItems[i].name'].isChecked = true;
}
for example if item.name = 'Item1' I want the model to be named $scope.Item1 and later I want to call $scope.Item1.isChecked = true;
What Am I doing wrong here?
Your are storing the model in the $scope.checkboxes, so you can set its value to true like the code below.
for (var i = 0, j = $scope.selectedItems.length; i<j; i++) {
$scope.checkboxes[$scope.selectedItems[i].name] = true;
}
It looks like your using a string literal here:
for (var i = 0, j = $scope.selectedItems.length; i<j; i++) {
$scope['selectedItems[i].name'].isChecked = true;
}
Just change this to read:
for (var i = 0, j = $scope.selectedItems.length; i<j; i++) {
$scope[selectedItems[i].name].isChecked = true;
}
var myApp = angular.module('myApp',[]);
function MyCtrl($scope) {
$scope.main = [{
'name':'test1',
'price':'23',
'isChecked':false
},
{
'name':'test2',
'price': '25',
'isChecked':false
},
{
'name':'test3',
'price': '21',
'isChecked':false
}];
$scope.selectedItems = [{
'name':'test1'
},
{
'name':'test2'
}];
angular.forEach($scope.main, function(valueMain){
angular.forEach($scope.selectedItems,function(valueSelected){
if(valueMain.name=== valueSelected.name){
valueMain.isChecked = true;
}
})
});
$scope.toggleCheck = function(item){
if(item.isChecked === true){
item.isChecked === false;
}else{
item.isChecked === true;
};
}
}
<script src="https://code.angularjs.org/angular-1.0.1.js"></script>
<div ng-app="myApp">
<div ng-controller="MyCtrl">
<label ng-repeat="item in main">
<input type="checkbox" ng-model="item.isChecked" ng-checked="item.isChecked" ng-change="toggleCheck(item)">
{{item.name}} - <b>{{item.price}} €</b>
<br>
</label>
</div>
</div>
Angular Foreach is the easiest way to solve this kind of problem.

$filter with OR [duplicate]

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'}

highlighting previous row after ng-click

I have a dropdownlist which contains brand ids. acccording to the id im fetching corresponding products and showing it in a table. There are two buttons in each row that move the products up and down basically by interchanging the ranks. now i am able to do all the functionality of interchanging and re binding.The row is selected when it is clicked. my only problem is i am not able to select the row after it has moved up or down.
<div ng-app="myapp" ng-controller="prodctrl">
<select id="BrandDropdown" class="InstanceList" ng-change="GetBrandProd()" ng-model="Products">
<option>Select Brand</option> //Sample Data
<option value=1>Brand 1<option>
<option value=2>Brand 2<option>
</select>
<table id="prodtab" ng-model="Products">
<tr ng-repeat="P in Products track by $index" ng-click="setselected($index)" class="{{selected}}">
<td>{{P.Id}}</td>
<td>{{P.Rank}}</td>
<td>{{P.Name}}</td>
<td>
<input type="button" value="Move Up" id="moveup" ng-click="getval(P,$index)" /></td>
<td>
<input type="button" value="Move Down" /></td>
</tr>
</table>
</div>
this is the angularjs code
<script>
var app = angular.module('myapp', []);
var prod = null;
var mveup = null;
var mvedwn = null;
var ind = null;
app.controller('prodctrl', function ($scope, $http) {
//getting products for each brand
$scope.GetBrandProd = function () {
cursel = "B";
var Id = $('#BrandDropdown').val();
fetchtype = Id;
brid = Id;
$http({
method: "GET",
url: "/Home/GetProdBrand",
params: {
id: Id
}
})
.success(function (response) {
var data = response;
$scope.Products = data;
prod = data;
});
};
//changing color of row when clicked
$scope.setselected = function (index) {
if ($scope.lastSelected) {
$scope.lastSelected.selected = '';
}
if (mveup == null) {
this.selected = 'trselected';
$scope.lastSelected = this;
}
else {
mveup = null;
//this.selected = '';
$(this).closest('tr').prev().prop('Class', 'trselected');
}
};
//function to move product up in ranking
$scope.getval = function (p, index) {
var Idcur = p.Id;
var Rankcur = p.Rank;
ind = index;
if ($scope.Products[index - 1] != null) {
var IdPrev=$scope.Products[index - 1].Id;
var Rankprev = $scope.Products[index - 1].Rank;
mveup = null;
$scope.lastSelected = this;
if (cursel == "B") {
fetchtype = brid;
}
else if (cursel == "C") {
}
mveup = true;
$http({
method: "GET",
url: "/Home/MoveProd",
params: {
Curid: Idcur,
CurRank: Rankcur,
ChngId: IdPrev,
ChngRnk: Rankprev,
Type: cursel,
Id: fetchtype
}
})
.success(function (response) {
// ranks are interchanged and the data is returned.
var data = response;
$scope.Products = data;
prod = data;
});
}
}
})
</script>
It seems, the way you are handling the row selection is not correct.
I have just changed the way of handling selection here.
<tr ng-repeat="P in Products track by $index" ng-click="setselected($index)" ng-class="{selected: selectedIndex == $index}">
//JS
$scope.setselected = function(index) {
$scope.selectedIndex = index;
};
Also, I have done a plunker with some sample values to imitate your requirement, you can ask more, if it is not fit to your requirement.
Plunker
You already have the id of the product that was clicked on (I think from looking at your code, it's Idcur), so you could loop over your results in the success block of the /Home/MoveProd GET request and set the record with the matching id to selected? Something like
var products = $scope.Products.filter(function(product) {
return product.id == Idcur;
})
if (products && products.length > 0) {
products[0].selected = 'trselected';
}
then, in your page, just update the ng-repeat slightly to pick the selected class from the product, instead of the scope, so:
<tr ng-repeat="P in Products track by $index" ng-click="setselected($index)" class="{{selected}}">
becomes
<tr ng-repeat="P in Products track by $index" ng-click="setselected($index)" class="{{P.selected}}">
or something like that :)

How to disable angular template trimming?

I'm trying to create a tree structure in the select element. I make indention by filter. As a result this indentation trims after output. Is that possible to disable trimming?
<select id="cat">
<option value="{{category.id}}" ng-repeat="category in categories">{{category | intent}}</option>
</select>
app.filter('intent', function() {
return function(category) {
var INTENT_SIZE = 4;
if (category == null) {
return '';
}
var result = "";
for (var i = 0; i < category.intent * INTENT_SIZE; i++) {
result += " ";
}
result += category.name;
return result;
};
})
For angular 2+ I use this pipe to 'untrim' template spaces :
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'untrim'
})
export class UntrimPipe implements PipeTransform {
transform(value: any, args?: any): any {
return typeof value === 'string' ? value.replace(/\s/g, ' ') : value;
}
}
Related:
Add space to <select> with ng-options
var app = angular.module('app', []);
app.filter('intent', function() {
return function(category) {
var INTENT_SIZE = 4;
if (category == null) {
return '';
}
var result = "";
for (var i = 0, len = category.intent * INTENT_SIZE; i < len; i++) {
result += String.fromCharCode(160);
}
result += category.name;
return result;
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app='app'>
<div ng-init="categories = [{id: 0, name:'bob', intent: 0},
{id: 1, name:'chris', intent: 1},
{id: 2, name:'mike', intent: 2}]"></div>
<select id="cat">
<option value="{{category.id}}" ng-repeat="category in categories">
{{ category | intent }}
</option>
</select>
</div>
You need to use the HTML character to render a space that will not be ignored by the browser.
But then you need to make sure that Angular "trusts" the HTML you are trying to use.
You can accomplish that by changing your markup to:
<select id="cat">
<option value="{{category.id}}" ng-repeat="category in categories" ng-bind-html="category | intent"></option>
</select>
Then change your filter code to:
app.filter('intent', function($sce) {
return function(category) {
var INTENT_SIZE = 4;
if (category == null) {
return '';
}
var result = "";
for (var i = 0; i < category.intent * INTENT_SIZE; i++) {
result += " ";
}
result += category.name;
return $sce.trustAsHtml(result);
};
});
Working Plunkr
NB: I only tested this in Chrome version 41. I'm not sure if all browsers allow having s in the option text.
To get space characters in your options use ng-bind-html to render the space.
<select id="cat">
<option value="{{category.id}}" ng-repeat="category in categories" ng-bind-html="category | intent"></option>
</select>
In your Filter:
app.filter('intent', function($sce) {
return function(category) {
var INTENT_SIZE = 4;
if (category == null) {
return '';
}
var result = "";
for (var i = 0; i < category.intent * INTENT_SIZE; i++) {
result += " ";
}
result += category.name;
return $sce.trustAsHtml(result);
};
});
DEMO

How to make ng-repeat filter out duplicate results

I'm running a simple ng-repeat over a JSON file and want to get category names. There are about 100 objects, each belonging to a category - but there are only about 6 categories.
My current code is this:
<select ng-model="orderProp" >
<option ng-repeat="place in places" value="{{place.category}}">{{place.category}}</option>
</select>
The output is 100 different options, mostly duplicates. How do I use Angular to check whether a {{place.category}} already exists, and not create an option if it's already there?
edit: In my javascript, $scope.places = JSON data, just to clarify
You could use the unique filter from AngularUI (source code available here: AngularUI unique filter) and use it directly in the ng-options (or ng-repeat).
<select ng-model="orderProp" ng-options="place.category for place in places | unique:'category'">
<option value="0">Default</option>
// unique options from the categories
</select>
Or you can write your own filter using lodash.
app.filter('unique', function() {
return function (arr, field) {
return _.uniq(arr, function(a) { return a[field]; });
};
});
You can use 'unique'(aliases: uniq) filter in angular.filter module
usage: colection | uniq: 'property'
you can also filter by nested properties: colection | uniq: 'property.nested_property'
What you can do, is something like that..
function MainController ($scope) {
$scope.orders = [
{ id:1, customer: { name: 'foo', id: 10 } },
{ id:2, customer: { name: 'bar', id: 20 } },
{ id:3, customer: { name: 'foo', id: 10 } },
{ id:4, customer: { name: 'bar', id: 20 } },
{ id:5, customer: { name: 'baz', id: 30 } },
];
}
HTML: We filter by customer id, i.e remove duplicate customers
<th>Customer list: </th>
<tr ng-repeat="order in orders | unique: 'customer.id'" >
<td> {{ order.customer.name }} , {{ order.customer.id }} </td>
</tr>
result
Customer list:
foo 10
bar 20
baz 30
this code works for me.
app.filter('unique', function() {
return function (arr, field) {
var o = {}, i, l = arr.length, r = [];
for(i=0; i<l;i+=1) {
o[arr[i][field]] = arr[i];
}
for(i in o) {
r.push(o[i]);
}
return r;
};
})
and then
var colors=$filter('unique')(items,"color");
If you want to list categories, I think you should explicitly state your
intention in the view.
<select ng-model="orderProp" >
<option ng-repeat="category in categories"
value="{{category}}">
{{category}}
</option>
</select>
in the controller:
$scope.categories = $scope.places.reduce(function(sum, place) {
if (sum.indexOf( place.category ) < 0) sum.push( place.category );
return sum;
}, []);
Here's a straightforward and generic example.
The filter:
sampleApp.filter('unique', function() {
// Take in the collection and which field
// should be unique
// We assume an array of objects here
// NOTE: We are skipping any object which
// contains a duplicated value for that
// particular key. Make sure this is what
// you want!
return function (arr, targetField) {
var values = [],
i,
unique,
l = arr.length,
results = [],
obj;
// Iterate over all objects in the array
// and collect all unique values
for( i = 0; i < arr.length; i++ ) {
obj = arr[i];
// check for uniqueness
unique = true;
for( v = 0; v < values.length; v++ ){
if( obj[targetField] == values[v] ){
unique = false;
}
}
// If this is indeed unique, add its
// value to our values and push
// it onto the returned array
if( unique ){
values.push( obj[targetField] );
results.push( obj );
}
}
return results;
};
})
The markup:
<div ng-repeat = "item in items | unique:'name'">
{{ item.name }}
</div>
<script src="your/filters.js"></script>
I decided to extend #thethakuri's answer to allow any depth for the unique member. Here's the code. This is for those who don't want to include the entire AngularUI module just for this functionality. If you're already using AngularUI, ignore this answer:
app.filter('unique', function() {
return function(collection, primaryKey) { //no need for secondary key
var output = [],
keys = [];
var splitKeys = primaryKey.split('.'); //split by period
angular.forEach(collection, function(item) {
var key = {};
angular.copy(item, key);
for(var i=0; i<splitKeys.length; i++){
key = key[splitKeys[i]]; //the beauty of loosely typed js :)
}
if(keys.indexOf(key) === -1) {
keys.push(key);
output.push(item);
}
});
return output;
};
});
Example
<div ng-repeat="item in items | unique : 'subitem.subitem.subitem.value'"></div>
I had an array of strings, not objects and i used this approach:
ng-repeat="name in names | unique"
with this filter:
angular.module('app').filter('unique', unique);
function unique(){
return function(arry){
Array.prototype.getUnique = function(){
var u = {}, a = [];
for(var i = 0, l = this.length; i < l; ++i){
if(u.hasOwnProperty(this[i])) {
continue;
}
a.push(this[i]);
u[this[i]] = 1;
}
return a;
};
if(arry === undefined || arry.length === 0){
return '';
}
else {
return arry.getUnique();
}
};
}
UPDATE
I was recomending the use of Set but sorry this doesn't work for ng-repeat, nor Map since ng-repeat only works with array. So ignore this answer. anyways if you need to filter out duplicates one way is as other has said using angular filters, here is the link for it to the getting started section.
Old answer
Yo can use the ECMAScript 2015 (ES6) standard Set Data structure, instead of an Array Data Structure this way you filter repeated values when adding to the Set. (Remember sets don't allow repeated values). Really easy to use:
var mySet = new Set();
mySet.add(1);
mySet.add(5);
mySet.add("some text");
var o = {a: 1, b: 2};
mySet.add(o);
mySet.has(1); // true
mySet.has(3); // false, 3 has not been added to the set
mySet.has(5); // true
mySet.has(Math.sqrt(25)); // true
mySet.has("Some Text".toLowerCase()); // true
mySet.has(o); // true
mySet.size; // 4
mySet.delete(5); // removes 5 from the set
mySet.has(5); // false, 5 has been removed
mySet.size; // 3, we just removed one value
It seems everybody is throwing their own version of the unique filter into the ring, so I'll do the same. Critique is very welcome.
angular.module('myFilters', [])
.filter('unique', function () {
return function (items, attr) {
var seen = {};
return items.filter(function (item) {
return (angular.isUndefined(attr) || !item.hasOwnProperty(attr))
? true
: seen[item[attr]] = !seen[item[attr]];
});
};
});
Here's a template-only way to do it (it's not maintaining the order, though). Plus, the result will be ordered as well, which is useful in most cases:
<select ng-model="orderProp" >
<option ng-repeat="place in places | orderBy:'category' as sortedPlaces" data-ng-if="sortedPlaces[$index-1].category != place.category" value="{{place.category}}">
{{place.category}}
</option>
</select>
None of the above filters fixed my issue so I had to copy the filter from official github doc. And then use it as explained in the above answers
angular.module('yourAppNameHere').filter('unique', function () {
return function (items, filterOn) {
if (filterOn === false) {
return items;
}
if ((filterOn || angular.isUndefined(filterOn)) && angular.isArray(items)) {
var hashCheck = {}, newItems = [];
var extractValueToCompare = function (item) {
if (angular.isObject(item) && angular.isString(filterOn)) {
return item[filterOn];
} else {
return item;
}
};
angular.forEach(items, function (item) {
var valueToCheck, isDuplicate = false;
for (var i = 0; i < newItems.length; i++) {
if (angular.equals(extractValueToCompare(newItems[i]), extractValueToCompare(item))) {
isDuplicate = true;
break;
}
}
if (!isDuplicate) {
newItems.push(item);
}
});
items = newItems;
}
return items;
};
});
If you want to get unique data based on the nested key:
app.filter('unique', function() {
return function(collection, primaryKey, secondaryKey) { //optional secondary key
var output = [],
keys = [];
angular.forEach(collection, function(item) {
var key;
secondaryKey === undefined ? key = item[primaryKey] : key = item[primaryKey][secondaryKey];
if(keys.indexOf(key) === -1) {
keys.push(key);
output.push(item);
}
});
return output;
};
});
Call it like this :
<div ng-repeat="notify in notifications | unique: 'firstlevel':'secondlevel'">
Add this filter:
app.filter('unique', function () {
return function ( collection, keyname) {
var output = [],
keys = []
found = [];
if (!keyname) {
angular.forEach(collection, function (row) {
var is_found = false;
angular.forEach(found, function (foundRow) {
if (foundRow == row) {
is_found = true;
}
});
if (is_found) { return; }
found.push(row);
output.push(row);
});
}
else {
angular.forEach(collection, function (row) {
var item = row[keyname];
if (item === null || item === undefined) return;
if (keys.indexOf(item) === -1) {
keys.push(item);
output.push(row);
}
});
}
return output;
};
});
Update your markup:
<select ng-model="orderProp" >
<option ng-repeat="place in places | unique" value="{{place.category}}">{{place.category}}</option>
</select>
This might be overkill, but it works for me.
Array.prototype.contains = function (item, prop) {
var arr = this.valueOf();
if (prop == undefined || prop == null) {
for (var i = 0; i < arr.length; i++) {
if (arr[i] == item) {
return true;
}
}
}
else {
for (var i = 0; i < arr.length; i++) {
if (arr[i][prop] == item) return true;
}
}
return false;
}
Array.prototype.distinct = function (prop) {
var arr = this.valueOf();
var ret = [];
for (var i = 0; i < arr.length; i++) {
if (!ret.contains(arr[i][prop], prop)) {
ret.push(arr[i]);
}
}
arr = [];
arr = ret;
return arr;
}
The distinct function depends on the contains function defined above. It can be called as array.distinct(prop); where prop is the property you want to be distinct.
So you could just say $scope.places.distinct("category");
Create your own array.
<select name="cmpPro" ng-model="test3.Product" ng-options="q for q in productArray track by q">
<option value="" >Plans</option>
</select>
productArray =[];
angular.forEach($scope.leadDetail, function(value,key){
var index = $scope.productArray.indexOf(value.Product);
if(index === -1)
{
$scope.productArray.push(value.Product);
}
});

Resources