Angularjs summary row filter - angularjs

I am trying to build a custom filter which would add a summary (avg) row to the array.
I'd like to use it like this:
<tr ng-repeat="item in items | summaryRow : ['col1', 'col1'] | orderBy : 'name'"
Here's what my filter looks like:
summaryRow.$inject = ['$parse', 'sumFilter'];
function summaryRow($parse, sumFilter) {
return function(array, fields, nameLabel) {
var row = { id: 0 }, l = array.length, setter;
row[nameLabel || 'name'] = 'Summary';
for (var i = 0, j = fields.length; i < j; i++) {
setter = $parse(fields[i]).assign;
setter(row, sumFilter(array, fields[i]) / l);
}
return array.concat(row);
}
}
The thing is I want this extra row to be sortable and aware of changes within items. The problem is I'm running into the "10 $digest() iterations reached. Aborting!" issue. Any help would be appreciated, thanks!

Try using $filter in your controller. Here is a great post written by Todd Motto:
https://toddmotto.com/use-controller-filters-to-prevent-digest-performance-issues/

Found the solution, here's what I came up with:
summaryRow.$inject = ['$parse', 'sumFilter'];
function summaryRow($parse, sumFilter) {
var row = {};
return function(array, fields, nameLabel) {
clearObject(row);
var l = array.length, setter;
row.id = 0;
row[nameLabel || 'name'] = 'Summary';
for (var i = 0, j = fields.length; i < j; i++) {
setter = $parse(fields[i]).assign;
setter(row, sumFilter(array, fields[i]) / l);
}
return array.concat(row);
}
}
And here's the clearObject implementation:
function clearObject(obj) {
for (var k in obj)
delete obj[k];
}

Related

reorder list randomly in ng-repeat

I have an ng-repeat as following :
<ul>
<li ng-repeat="o in villes | limitTo:5">{{o.nomVille}}</li>
</ul>
I want to reorder the villes list randomly before I limit it to 5, so every time I open my page I get 5 different villes each time.
is there a filter in angularjs who can do that for me ?
edit :
I created a costum filter to randomize that list as following :
.filter('random', function() {
return function(val) {
let shuffle = (a) => {
let r = [];
while (arr.length)
r.push(
arr.splice( (Math.floor(Math.random() * arr.length)) , 1)[0]
);
return shuffle(val);
}
};
});
and in ng-repeat I did this :
<li ng-repeat="o in villes | random | limitTo:5">{{o.nomVille}}</li>
but I cant no longer see anything in my page.
this is the example on jsfiddle : https://jsfiddle.net/z10wwhcv/
You'd have to build a custom filter function that randomizes the order and then apply the filters in the correct order (random before limit).
Have a look at this for details:
Using AngularJS how could I randomize the order of a collection?
If you want the order to change each time you load the page you can't do it as a filter as that will presumably change on each digest cycle. You need to store villes on the scope somewhere and generate it in a random order when the page loads, e.g. in your controller function for that page.
use the filter in the controller (which is also a best practice performance boost):
$scope.shuffled = $filter('random',$scope.villes)
you'll need to inject the service in the controller
this is what your filter should look like ( not tested but should work ):
.filter('random', function() {
return function(a) {
var r = [];
while (a.length)
r.push(
a.splice((Math.floor(Math.random() * a.length)), 1)[0]
);
return r;
}
}
This is the solution I created :
shufflemodule.filter('shuffle', function() {
var shuffledArr = [],
shuffledLength = 0;
return function(arr) {
var o = arr.slice(0, arr.length);
if (shuffledLength == arr.length) return shuffledArr;
for(var j, x, i = o.length; i; j = parseInt(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
shuffledArr = o;
shuffledLength = o.length;
return o;
};
});
http://jsfiddle.net/aimad_majdou/6mngvo38/

ui-select multiselect is very slow in displaying the choices

I ran into this problem, and I don't know how to solve it. I have used a ui-select multiselect in my page. First, a http.get request is made to a url which gets the data, then the ui-select choices are populated. The data is big - the length of the data is 2100. This data is to be shown as choices. (The data is fetched at the beginning during the loading of the page and is stored in an array)
But the problem is each time I click on the multiselect to select a choice, it takes 4-5 seconds to populate the list and the page becomes very slow. What do I do to reduce this time?
The choices data is stored in an array, the datatype is array of strings.
<ui-select multiple ng-model="selectedFields.name" style="width: 100%;">
<ui-select-match placeholder="Select fields...">{{$item}}</ui-select-match>
<ui-select-choices repeat="fields in availableFields | filter:$select.search">
{{fields}}
</ui-select-choices>
</ui-select>
in the controller,
$scope.selectedFields = {};
$scope.selectedFields.name = [];
$scope.init = function() {
$http.get(url)
.success( function(response, status, headers, config) {
availableFields = response;
})
.error( function(err) {
});
};
$scope.init();
If not this way, is there any other options/choice I can work with which doesn't delay showing the select-choices?
This is a known issue in ui-select. I tried the following ways, both work
1) There is a workaround for this - use
| limitTo: 100
This limits the choice display to 100 but all the choices can be selected. Look at this thread for more details.
2) Since some of the time, there is a need to display the entire list in the choices, 1) is not a viable option. I used a different library - selectize.js. Here's a plunker demo given in the page
Here is complete solution that decorates uiSelectChoices directive.
Items are populated progressively as the user scrolls.
Also takes care of searches in the scrolls.
Also works for all values of position={auto, up, down}
Example
<ui-select-choices
position="up"
all-choices="ctrl.allTenThousandItems"
refresh-delay="0"
repeat="person in $select.pageOptions.people | propsFilter: {name: $select.search, age: $select.search} ">
<div ng-bind-html="person.name | highlight: $select.search"></div>
<small>
email: {{person.email}}
age: <span ng-bind-html="''+person.age | highlight: $select.search"></span>
</small>
</ui-select-choices>
Working Plnkr
Also with With v0.19.5
The directive
app.directive('uiSelectChoices', ['$timeout', '$parse', '$compile', '$document', '$filter', function($timeout, $parse, $compile, $document, $filter) {
return function(scope, elm, attr) {
var raw = elm[0];
var scrollCompleted = true;
if (!attr.allChoices) {
throw new Error('ief:ui-select: Attribute all-choices is required in ui-select-choices so that we can handle pagination.');
}
scope.pagingOptions = {
allOptions: scope.$eval(attr.allChoices)
};
attr.refresh = 'addMoreItems()';
var refreshCallBack = $parse(attr.refresh);
elm.bind('scroll', function(event) {
var remainingHeight = raw.offsetHeight - raw.scrollHeight;
var scrollTop = raw.scrollTop;
var percent = Math.abs((scrollTop / remainingHeight) * 100);
if (percent >= 80) {
if (scrollCompleted) {
scrollCompleted = false;
event.preventDefault();
event.stopPropagation();
var callback = function() {
scope.addingMore = true;
refreshCallBack(scope, {
$event: event
});
scrollCompleted = true;
};
$timeout(callback, 100);
}
}
});
var closeDestroyer = scope.$on('uis:close', function() {
var pagingOptions = scope.$select.pagingOptions || {};
pagingOptions.filteredItems = undefined;
pagingOptions.page = 0;
});
scope.addMoreItems = function(doneCalBack) {
console.log('new addMoreItems');
var $select = scope.$select;
var allItems = scope.pagingOptions.allOptions;
var moreItems = [];
var itemsThreshold = 100;
var search = $select.search;
var pagingOptions = $select.pagingOptions = $select.pagingOptions || {
page: 0,
pageSize: 20,
items: $select.items
};
if (pagingOptions.page === 0) {
pagingOptions.items.length = 0;
}
if (!pagingOptions.originalAllItems) {
pagingOptions.originalAllItems = scope.pagingOptions.allOptions;
}
console.log('search term=' + search);
console.log('prev search term=' + pagingOptions.prevSearch);
var searchDidNotChange = search && pagingOptions.prevSearch && search == pagingOptions.prevSearch;
console.log('isSearchChanged=' + searchDidNotChange);
if (pagingOptions.filteredItems && searchDidNotChange) {
allItems = pagingOptions.filteredItems;
}
pagingOptions.prevSearch = search;
if (search && search.length > 0 && pagingOptions.items.length < allItems.length && !searchDidNotChange) {
//search
if (!pagingOptions.filteredItems) {
//console.log('previous ' + pagingOptions.filteredItems);
}
pagingOptions.filteredItems = undefined;
moreItems = $filter('filter')(pagingOptions.originalAllItems, search);
//if filtered items are too many scrolling should occur for filtered items
if (moreItems.length > itemsThreshold) {
if (!pagingOptions.filteredItems) {
pagingOptions.page = 0;
pagingOptions.items.length = 0;
} else {
}
pagingOptions.page = 0;
pagingOptions.items.length = 0;
allItems = pagingOptions.filteredItems = moreItems;
} else {
allItems = moreItems;
pagingOptions.items.length = 0;
pagingOptions.filteredItems = undefined;
}
} else {
console.log('plain paging');
}
pagingOptions.page++;
if (pagingOptions.page * pagingOptions.pageSize < allItems.length) {
moreItems = allItems.slice(pagingOptions.items.length, pagingOptions.page * pagingOptions.pageSize);
}
for (var k = 0; k < moreItems.length; k++) {
pagingOptions.items.push(moreItems[k]);
}
scope.calculateDropdownPos();
scope.$broadcast('uis:refresh');
if (doneCalBack) doneCalBack();
};
scope.$on('$destroy', function() {
elm.off('scroll');
closeDestroyer();
});
};
}]);
As stated, ui-select is having quite a few performance issues, but there is a workaround for the limit issue.
If you follow akashrajkn's approach then you will notice that it will actually cut out important pieces of data because it will only render 100 at a time. There is a fix that has passed the unit tests and it can be found on the thread here:
https://github.com/angular-ui/ui-select/pull/716
Basically, if you are storing the javascript file locally, then you can adjust the unminified version. All you need to do is implement the changes he made in the pull request and it should help out significantly. In order to apply the limiting factor, take a look at the below, modified example:
<ui-select multiple ng-model="selectedFields.name" limit = "10" style="width: 100%;">
<ui-select-match placeholder="Select fields...">{{$item}}</ui-select-match>
<ui-select-choices repeat="fields in availableFields | filter:$select.search | limitTo:$select.limit ">
{{fields}}
</ui-select-choices>
</ui-select>
The above will limit your data in the drop down while also maintaining the level of consistency needed.
Because I cannot leave a comment (not enough rep) I write this as an answer and I am sorry it is no answer for the problem.
#bhantol I changed the following line of code to your solution which is working perfectly for me so far
for (var k = 0; k < moreItems.length; k++) {
pagingOptions.items.push(moreItems[k]);
}
for (var k = 0; k < moreItems.length; k++) {
if (pagingOptions.items.indexOf(moreItems[k]) == -1){
pagingOptions.items.push(moreItems[k]);
}
}
This prevents duplicated items from showing up if the user is starting to write a filter and then deletes it.
Also I just figured out that if the list is smaller than 20 items it will not work so I changed:
if (pagingOptions.page * pagingOptions.pageSize < allItems.length) {
moreItems = allItems.slice(pagingOptions.items.length, pagingOptions.page * pagingOptions.pageSize);
}
to:
if (pagingOptions.page * pagingOptions.pageSize < allItems.length) {
moreItems = allItems.slice(pagingOptions.items.length, pagingOptions.page * pagingOptions.pageSize);
}
else{ moreItems = allItems;}
Maybe this will help you somehow and sorry again for not answering the question.

How to count unique results based on a particular properties within ng-options

I'm working with AngularJS and trying to create a filter to search properties.
I've got a select box like this:
<select
class="selectBox"
multiple="multiple"
ng-model="selectedSubArea"
ng-options="property.SubArea as (property.SubArea + ' ('+ filtered.length +')') for property in filtered = (properties | unique:'SubArea') | orderBy:'SubArea'">
</select>
This is the unique function:
myApp.filter('unique', function() {
return function(input, key) {
var unique = {};
var uniqueList = [];
for(var i = 0; i < input.length; i++){
if(typeof unique[input[i][key]] == "undefined"){
unique[input[i][key]] = "";
uniqueList.push(input[i]);
}
}
return uniqueList;
}; });
How can I get filtered.length to work?
Here's my JSFiddle
Here's a solution:
http://jsfiddle.net/odpw6c6q/16/
Create a filter that returns objects with count (number of times they were in the original list):
myApp.filter('uniqueWithCount', function() {
return function(input, key) {
var unique = {};
var uniqueList = [];
var obj, val;
for(var i = 0; i < input.length; i++){
val = input[i][key];
obj = unique[val];
if (!obj) {
obj = unique[val] = {data: input[i], count: 0};
uniqueList.push(obj);
}
obj.count++;
}
return uniqueList;
};
});
Use that filter in your HTML:
<select class="form-control" multiple="multiple" ng-model="selectedSubArea"
ng-options="property.SubArea as (item.data.SubArea + ' ('+ item.count +')') for item in filtered = (properties | uniqueWithCount:'SubArea') | orderBy:'SubArea'"></select>

Nested ng repeat failure in angular js

Try to do a nest ng-repeat, parent ng-repeat works but not for its child
1.Created object and insert data when app init
2.display data by calling ng-repeat
angular.module('lipapp', []).controller('lipapp_Module_Control', function ($scope, $http, $window) {
$scope.CompaignBasket = [];
$scope.CompaignBasketTotal = 0;
$scope.CompaignBasketCauseTotal = [];
//Sample Data Section, Real data plz go to $scope.UpdateCompaign function
$scope.InitialCompaignBasket = function () {
$scope.CompaignBasket = [];
var causeAmount = [];
var causeitem = 0;
var donorDetail = {};
donorDetail.Date = '2/14/13';
donorDetail.Donor = 'George Smith';
donorDetail.City = 'Los Angelos';
donorDetail.State = 'CA';
donorDetail.Total = 400;
causeAmount = []; //forloop Dynamic Insert!
causeAmount.push(10);
causeAmount.push(0);
causeAmount.push(500);
causeAmount.push(0);
causeAmount.push(1000);
causeAmount.push(0);
//causeitem = 0;causeAmount.push(causeitem); or I should use this format?
donorDetail.DonorCauseAmount = causeAmount;
$scope.CompaignBasketTotal += donorDetail.Total;
$scope.CompaignBasket.push(donorDetail);
console.log( $scope.CompaignBasket);
...
}
All the result display correctly but the item.donorCauseAmount has throw errors
<tr ng-repeat="item in CompaignBasket">
<td>{{item.Date}}</td>
<td>{{item.Donor}}</td>
<td>{{item.City}}</td>
<td>{{item.State}}</td>
<td ng-repeat="cause_item in item.DonorCauseAmount">{{cause_item}} </td>
<td>${{item.Total|number :0}}</td>
</tr>
Also for when I try to calculate the total for different CauseItem in the same col in script there is also a repeater error
for(var i=0; i< $scope.CompaignBasket.length; i++)
{
if (i == 0)
{
$scope.CauseTotalAmount = $scope.CompaignBasket[i].DonorCauseAmount;
console.log($scope.CauseTotalAmount);
}
else {
for (var j = 0; j < $scope.CauseTotalAmount.length; j++) {
// $scope.CauseTotalAmount[j] += $scope.CompaignBasket[i].DonorCauseAmount[j];
console.log('total '+ $scope.CompaignBasket[i].DonorCauseAmount[j]); //Error show up by this!!!
}
}
console.log($scope.CauseTotalAmount);
}
How can I solve it?
You need to use a track by index when you have duplicate values within the array.
<td ng-repeat="cause_item in item.DonorCauseAmount track by $index">{{cause_item}} </td>

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