I'm doing some testing with Angular to see if I can replicate what I already have in PHP more efficiently.
I have a set of data stored in JSON:
[
{
"name":"Blue Widget",
"description":"blue-widget",
"snippet":"The best blue widget around!",
"category":"Home Widgets",
"popular":true
},
{
"name":"Red Widget",
"description":"red-widget",
"snippet":"The best red widget around!",
"category":"Outdoor Widgets",
"popular":true
},
{
"name":"Green Widget",
"description":"green-widget",
"snippet":"The best green widget around!",
"category":"Work Widgets",
"popular":true
},
{
"name":"Yellow Widget",
"description":"yellow-widget",
"snippet":"The best yellow widget around!",
"category":"Home Widgets",
"popular":true
}
]
I'm grabbing this in my controller and adding it to my view in a fairly standard way (yes, I know not to use $http directly in a controller in production):
widgetApp.controller('widgetListCtrl', function($scope,$http){
$http.get('widgets/widgets.json').success(function(data){
$scope.widgets = data
})
})
If I use:
<li ng-repeat="widget in widgets">{{widget.category}}</li>
Then naturally it will just go through and list:
Home Widgets
Outdoor Widgets
Work Widgets
Home Widgets
What I'd like to do is generate a list of each widget.category but with each category only appearing once, so a user could then click on a category and be shown all the widgets in that category. How can I go about this? Sorry, I haven't got anything to go on because I pretty much have no idea where to start.
You can use the existing 'unique' filter from AngularUI.
<li ng-repeat="widget in widgets | unique: 'widget.category' ">{{widget.category}}</li>
Be sure to include a reference to the filters module in your app as well (e.g. angular.module('yourModule', ['ui', 'ui.filters']);).
You'd have to build a list of unique categories:
widgetApp.controller('widgetListCtrl', function($scope,$http){
$http.get('widgets/widgets.json').success(function(data){
$scope.uniqueCategories = [];
for (var i = 0; i < $scope.widgets.length; i++) {
if ($scope.uniqueCategories.indexOf($scope.widgets[i].category) === -1)
$scope.uniqueCategories.push($scope.widgets[i].category);
}
});
});
Make a dropdown with the model set to the category:
<select ng-model="categoryFilter" ng-options="category as category for category in uniqueCategories"></select>
And use a filter on your repeat:
<li ng-repeat="widget in widgets | filter: { category: categoryFilter }">{{widget.category}}</li>
Create a filter
app.filter('unique', function() {
return function (arr, field) {
return _.uniq(arr, function(a) { return a[field]; });
};
});
In Markup
<li ng-repeat="widget in widgets | unique:'category'">{{widget.category}}</li>
Create a distinct filiter and use it on your view:
angular.filter("distinct", function () {
return function (data, propertyName) {
if (angular.isArray(data) && angular.isString(propertyName)) {
var results = [];
var keys = {};
for (var i = 0; i < data.length; i++) {
var val = data[i][propertyName];
if (angular.isUndefined(keys[val]) && val != null) {
keys[val] = true;
results.push(val);
};
};
return results;
}
else {
return data;
}
}
})
<li ng-repeat="widget in widgets | distinct:'category'">{{widget.category}}</li>
I am new at angularjs. So, it might be fool question.Anyway, please let me explain my problem. I have a table which is listed by ng-repeat and I'd like to change a column datas with another datas in another table column.
<tr data-ng-repeat=" list in listTypes">
<td>{{list.Comments}}</td>
<td>{{list.Modul}}</td>
<td>{{list.UserId}}</td>
<td data-ng-repeat="user in userNames">{{user.UserName}}</td>
I want to get UserName instead of UserId, but the problem that UserName is recorded in another table. Here is my angular for getting listTypes :
$scope.GetList = function () {
var onSuccess = function (response, status) {
//1
$scope.listTypes = response.Data;
var str = response.Data;
$scope.listTypes = eval('(' + str + ')');
for (var key in $scope.listTypes) {
$scope.listTypes[key].selected = "";
}
$scope.GetUserNames();
};
var data = null;
var request = $rest.GetList(data);
NGTools.CallNgServiceWithRequest(request, onSuccess, "GetList");
};
And trying to get usernames with this code:
$scope.userdatas= [];
$scope.userNames = [];
$scope.GetUserNames = function () {
var onSuccess = function (response, status) {
//1
$scope.userNames = response.Data;
};
$scope.userdatas= $scope.listTypes.UserId;
var data = { userdatas: JSON.stringify( $scope.userdatas) };
var request = $rest.GetUserNames(data);
NGTools.CallNgServiceWithRequest(request, onSuccess, "GetUserNames");
};
but it doesn't work. I couldn't figure out what's wrong with this code block. Please let me know if any tip is available. Thank you!
Assuming that you have to collections in your scope - one of which holds the id of the user, and the other holding the name, like so:
$scope.users = [
{ id: 1, name: 'John Doe' },
{ id: 2, name: 'Jane Doe' },
{ id: 3, name: 'Janice Doe' } ];
$scope.userInfo = [
{ userId: 1, gender: 'male' },
{ userId: 2, gender: 'female' },
{ userId: 3, gender: 'female' }];
Then what you could do is ng-repeat over the one with the userInfo and in your binding expression - use the id to get the name from the other collection:
<li ng-repeat="item in userInfo">
{{ item.gender }} {{ getNameFor(item.userId) }}</li>
Where the getNameFor is defined as:
$scope.getNameFor = function(id) {
var user = $scope.users.filter(function(item) { return item.id === id })[0];
console.log(user);
return user.name;
Which I checked in a fiddle here: http://jsfiddle.net/01kmoxw9/
I have created a select tag with multiple to show it as a listbox and not dropdown.
I have a property which should hold the selected schoolclass which consists of multiple properties like: id, schoolclass , subject, schoolclassIdentifier and color.
When I select now an item in the listbox and press the delete button the $scope.activeStep.selectedSchoolclassCodes array contains one string like "Math10b" actually the selectedSchoolclassCodes array should contain an object created from the above properties.
Why is my selected object wrong?
HTML
<div class="col-md-6">
<select size="10" class="form-control col-md-6" multiple ng-model="activeStep.selectedSchoolclassCodes">
<option class="co-md-6" ng-repeat="item in activeStep.schoolclasses" style="background: rgb({{item.color}})" value="{{item.schoolclassCode}}">{{item.schoolclassCode}}</option>
</select>
</div>
CONTROLLER
'use strict';
angular.module('iplanmylessons').controller('EditSchoolclassCodeWizardStepController', function ($scope, wizardDataFactory, SchoolclassCodeViewModel) {
$scope.activeStep.schoolclassCodeColors = [
'255,165,0',
'255,255,0',
'145,240,140',
'0,128,0',
'170,210,230',
'255,190,200',
'240,130,240',
'100,100,255',
'210,210,210',
'255,0,0'
];
$scope.activeStep.selectedSchoolclassCodes = wizardDataFactory.schoolclassCodesAdded[0];
$scope.activeStep.newSchoolclass = "";
$scope.activeStep.newSubject = "";
$scope.activeStep.newSchoolclassIdentifier = "";
$scope.activeStep.schoolclasses = wizardDataFactory.schoolclassCodesAdded;
$scope.activeStep.schoolclassCodeColorsIsOpen = false;
$scope.activeStep.selectedSchoolclassCodeColor = null;
$scope.activeStep.deleteSchoolclassCode = function () {
for (var i = 0; i < $scope.activeStep.selectedSchoolclassCodes.length; i++) {
var index = Enumerable.from( wizardDataFactory.schoolclassCodesAdded).indexOf(function (s) {
return s.schoolclassCode === $scope.activeStep.selectedSchoolclassCodes[i].schoolclassCode;
});
wizardDataFactory.schoolclassCodesAdded.splice(index, 1);
}
$scope.activeStep.selectedSchoolclassCodes = null;
};
$scope.activeStep.schoolclassCode = function () {
return $scope.activeStep.newSubject + $scope.activeStep.newSchoolclass + $scope.activeStep.newSchoolclassIdentifier;
};
$scope.activeStep.setSchoolclassCodeColor = function (item) {
$scope.activeStep.selectedSchoolclassCodeColor = item;
$scope.activeStep.schoolclassCodeColorsIsOpen = false;
};
});
Have you try ng-options? This post has a good explanation of select/ ng-options with array of objects.
Hope it can help.
Consider a set of items and users. Each user can have one or more items. The set of items can be quite big, but every user will normally have small amount of items.
app:
items:
item1:
name: 'table'
color: 'white'
createdAt: '2014-08-09T12:54:58.803Z'
item2:
name: 'macbook air'
color: 'silver'
createdAt: '2014-06-09T12:54:58.803Z'
item3:
name: 'firebase t-shirt'
color: 'yellow'
createdAt: '2014-07-09T12:54:58.803Z'
users:
user1:
items:
item1: true
item3: true
user2:
items:
item2: true
Given a user id, e.g. user1, I would like to display a list of user's items, sorted by createdAt:
yellow firebase t-shirt
white table
Every update on the Firebase server should be reflected in the app view.
I guess the view should look like this:
<div ng-repeat="item in items | orderBy:'createdAt'">
{{ item.color }} {{ item.name }}
</div>
But, I can't figure out an easy way to set up $scope.items.
This is what I currently do:
var userItemsRef = new Firebase(FIREBASE_ROOT + '/users/user1/items');
$scope.userItems = $firebase(userItemsRef).$asArray();
$scope.userItems.$loaded(function() {
$scope.userItems.$watch(setItems);
setItems();
});
function setItems() {
var promises = $scope.userItems.map(function(userItem) {
var itemRef = new Firebase(FIREBASE_ROOT + '/items/' + userItem.$id);
return $firebase(itemRef).$asObject().$loaded();
});
$q.all(promises).then(function(items) {
$scope.items = items;
});
}
Is this the best way to utilize AngularFire?
It would probably be simplest to store the items by user. So a structure like this:
/app/items/$user_id/$item_id/...
This would allow for the items belonging to a particular user to be retrieved like so:
var userId = 'user1';
var ref = new Firebase(FIREBASE_ROOT + '/items/' + userId);
$scope.items = $firebase(ref).$asArray();
If this isn't possible, because items are shared between users, it's probably simplest to use a join lib.
var userId = 'user1';
var userIndexRef = new Firebase(FIREBASE_ROOT + '/users/' + userId + '/items');
var itemsRef = new Firebase(FIREBASE_ROOT + '/items/');
var ref = Firebase.util.intersection(userIndexRef, itemsRef);
$scope.items = $firebase(ref).$asArray();
But generally, it's safest and simplest to take the first route.
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);
}
});