Angular Materials md-select and trackBy allowing options to be selected - angularjs

I'm trying to customise this Angular Material example code (https://material.angularjs.org/latest/api/directive/mdSelect) to my needs.
I have three groups of select options. If an option is selected in a group, it should unselect all options in other groups (but leave other options in own group as they are).
In my code I have managed to get the logic working right (as you will see from the console.log outputs at the bottom), but the actual select options do not interact with user input.
My JSFiddle: https://jsfiddle.net/e2LLLxnb/8/
My JS code:
var myModule = angular.module('BlankApp', ['ngMaterial']);
myModule.controller("FilterCtrl", function($scope, $element) {
$scope.categories = ["Any", "Target Category", "Option 1", "Option 2", "Option 3", "Option 4"];
$scope.mustCatSelected;
$scope.categoryObj = {};
// build the list of options with values and groups - create equivalent of $scope.data for <md-option ng-repeat="item in categoryObj.data.items">
var finGroup = [];
$scope.categories.forEach(function(value,key){
if(key>1){
finGroup.push(key);
};
});
$scope.categoryObj.data = {items: [], groups: [{
group: [0]
}, {
group: [1]
}, {
group: finGroup
}]};
$scope.categories.forEach(function(value,key){
$scope.categoryObj.data.items.push({name: value,
value: false,
id: (key + 1)});
});
$scope.clickOn = function(item, index) {
if(item.value == false){item.value = item.name;}
else {item.value = false;}
if (item.value === false) {
} else {
var thisGroup = [];
angular.forEach($scope.categoryObj.data.groups, function(value, key) {
if (value.group.indexOf(index) !== -1) {
thisGroup = value.group;
}
});
angular.forEach($scope.categoryObj.data.items, function(value, key) {
if (thisGroup.indexOf(key) !== -1) {
return;
} else {
value.value = false;
}
});
$scope.mustCatSelected = $scope.categoryObj.data.items.filter(function(e){
return e.value != false;
});
console.log($scope.mustCatSelected);
console.log($scope.categoryObj.data.items);
}
}
//search-term header
$scope.searchTerm;
$scope.clearSearchTerm = function() {
$scope.searchTerm = '';
};
// The md-select directive eats keydown events for some quick select
// logic. Since we have a search input here, we don't need that logic.
$element.find('input').on('keydown', function(ev) {
ev.stopPropagation();
});
});

Solved (finally!): https://jsfiddle.net/hqck87t1/4/
var myModule = angular.module('BlankApp', ['ngMaterial']);
myModule.controller("FilterCtrl", function($scope, $element) {
$scope.categories = ["Any", "None", "Option 1", "Option 2", "Option 3", "Option 4"];
$scope.mustCatSelected = [];
$scope.categoryObj = {};
$scope.categoryObj.items = [];
$scope.categories.forEach(function(value,key){
var grp;
if (key < 2){grp = key;}
if (key >= 2){grp = 2;}
$scope.categoryObj.items.push({
name: value,
id: (key + 1),
group: grp});
});
//set default
$scope.mustCatSelected.push($scope.categoryObj.items[0]);
$scope.clickOn = clickOn;
function clickOn(newValue, oldValue, type) {
//console.log($scope.categoryObj.items);
//console.log(oldValue);
if(oldValue.length == 0) {
return false;
}
//create arrays of new and old option ids
oldValue = JSON.parse(oldValue);
var newIds = [];
var oldIds = [];
newValue.forEach(function(value,key){
newIds.push(value.id);
});
oldValue.forEach(function(value,key){
oldIds.push(value.id);
});
//define and set the clicked value
var clickedValue;
newIds.forEach(function(value, key){
if(oldIds.indexOf(value) == -1) {
clickedValue = value;
}
});
var clickedGroup;
newValue.forEach(function(value,key){
if(value.id == clickedValue){
clickedGroup = value.group;
}
});
//console.log([clickedValue, clickedGroup]);
//console.log([newIds, oldIds, clickedValue]);
if(type == 'mustCat'){
$scope.mustCatSelected = $scope.mustCatSelected.filter(function(e){
return e.group == clickedGroup;
});
}
}
//search term above select
$scope.searchTerm;
$scope.clearSearchTerm = function() {
$scope.searchTerm = '';
};
// The md-select directive eats keydown events for some quick select
// logic. Since we have a search input here, we don't need that logic.
$element.find('input').on('keydown', function(ev) {
ev.stopPropagation();
});
});
There key to the solution lies in two things:
Using ng-change instead of ng-click. The former is used to distinguish the state of ng-model inline vs the specified state of ng-model after the change event. Whereas ng-click is not reliable for this.
Write the ng-change function in the html like this:
ng-change="clickOn(mustCatSelected, '{{mustCatSelected}}')"
where mustCatSelected is the ng-model and '{{mustCatSelected}}' the inline state of ng-model before the change event.
Now we have an multiple md-select with logic handling the selection of options / groups of options.

Related

How to set all the rows of ng-repeat to be selected in Angular

I have a smimple ng-repeat that displays user details. I am trying to set all the rows to be selected.
Currently I can manually click and select all the rows individually using following code:
<tr ng-repeat="room in classrooms" ng-class="{'selected': room.selected}" ng-click="select(room)">
in controller
$scope.select = function(item) {
item.selected ? item.selected = false : item.selected = true;
}
and to get data from the selected rows I use following logic
$scope.getAllSelectedRows = function()
{
var x = $filter("filter")($scope.classrooms,
{
selected: true
}, true);
console.log(x);
}
UPDATED FROM #KADIMA RESPONSE
$scope.toggleSelectAll = function()
{
angular.forEach($scope.classrooms, function(room) {
room.selected ? room.selected = false : room.selected = true;
})
}
Set up a new function in your controller:
$scope.selectAll = function() {
angular.forEach(classrooms, function(room) {
room.selected = true
}
}
And then you can create a button in your html to call this function.
If you want to select all data you got you can set selected property using angular.forEach() method.
https://docs.angularjs.org/api/ng/function/angular.forEach
In your case:
angular.forEach(x, fuction(value) {
value.selected = true;
}

Angular - Trouble with ng-change

I am still a little new to Angular, and I am having trouble setting the default select option, if that option is the only one in the array. What happens specifically is that the select does default, however, the shipping cost is not being calculated. It only calculates if the user chooses it from the drop down. I think the issue may be because with the ng-change on the select element, but I am not sure.
In my HTML:
<select class="form-control" ng-change="updateShipper()" name="shipMethod"
ng-model="currentOrder.LineItems[0].ShipperName"
ng-show="user.ShipMethod.ShipperSelectionType == 'UserDropDown'"
ng-options="shipper.Name as (shipper.Name + ' ' + (shipper.ShippingRate.Price | currency | xlat)) for shipper in shippers"
ng-required="!currentOrder.IsMultipleShip() && user.ShipMethod != null" >
<option value=""></option>
In my controller:
scope.$watch('shippers', function(val) {
if(angular.isDefined(val)){
$timeout(function() {
if(val.length === 1){
scope.currentOrder.LineItems[0].ShipperName = val[0].Name;
}
}, 0);
}
});
$scope.updateShipper = function(li) {
$scope.shippingUpdatingIndicator = true;
$scope.shippingFetchIndicator = true;
if (!li) { // at the order level
angular.forEach($scope.shippers, function(s) {
if (s.Name == $scope.currentOrder.LineItems[0].ShipperName)
$scope.currentOrder.Shipper = s;
});
angular.forEach($scope.currentOrder.LineItems, function(item) {
item.ShipperName = $scope.currentOrder.Shipper ? $scope.currentOrder.Shipper.Name : null;
item.ShipperID = $scope.currentOrder.Shipper ? $scope.currentOrder.Shipper.ID : null;
});
saveChanges(function() {
$scope.shippingUpdatingIndicator = false;
$scope.shippingFetchIndicator = false;
});
}
else { // at the lineitem level for multiple shipping
angular.forEach($scope.shippers, function(s) {
if (s.Name == li.ShipperName)
li.Shipper = s;
});
li.ShipperName = li.Shipper.Name;
li.ShipperID = li.Shipper.ID;
saveChanges(function() {
$scope.shippingUpdatingIndicator = false;
$scope.shippingFetchIndicator = false;
});
}
};
Try ng-init="updateShipper()". Then set the initial value of your model in the controller like $scope.currentOrder.LineItems[0].ShipperName = 0(or whatever).

Angular.js count item iterations when filtering objects with ng-options

working on a list filtering with Angular, where listing the items based on region and creating an option select to list these regionsm where I have to remove duplications, and will need a count number next to the region name, which represents how many items from the same region.
I found this thread : How to count unique results based on a particular properties within ng-options but its not working when removing the duplications.
Heres is my select
<select ng-options="item.region as item.region for item in items | unique: 'region'" ng-model="catFilter">
Here is my app.js
var regionSort = angular.module('regionSort', [
'regionSort.controllers'
]);
regionSort.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;
var count = 0;
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;
};
});
Does anybody have any idea how should I solve this? Thanks in advance!!
Item structure is pretty simple like :
[
{
"name": "Name",
"desc": "Description",
"price": 700,
"priceold": 1080,
"persons": 10,
"discount": 35,
"vote": "9,3",
"image": "https://someige.jpg",
"url": "http://someurl.com",
"region": "XXX"
}, ...
]

Angular JS radio lists and dropdowns. Issue with dropdowns changing value of radio list value when triggered

I think I have a logic issue with my Angular that I need some help on.
We have a service that connects to a DB that gets saved values so our Angular form can get the values and use them. That is all set up.
My issue is, in our form we have 1 radio button list using an ng-repeat to generate the list and 3 dropdown lists. Our dropdown lists are triggering and update function to update the values of themselves. We are showing and hiding the dropdown menus based on the radio button lists selection (I will post the code). This should be noted we are using Umbraco CMS that this control is being used for, but this is not the actual issue.
The issue is, we console our response the value of the radio button list outputs fine to the selection we chose, BUT when I select one of the dropdown list items, the value of the dropdown list returns to the previously selected value. Any help figuring this out would be greatly appreciated.
Code is below:
my.controller.js
angular.module("umbraco")
.controller("Our.GalaxyEventSelectorController", function ($scope, $routeParams, notificationsService, GalaxyEventSelectorResource) {
$scope.emptyList = [{}];
$scope.ETypeRadio = { 0: "NA", 1: "RepeatingTimedEvent", 2: "RepeatSingleDayEvent", 3: "SingleTimedEvent" };
GalaxyEventSelectorResource.getEventById($routeParams.id, $scope.model.alias).then(function (response) {
// console.log("REsponse: " + response);
console.log("intial load on DOM load")
var resp = (response.data.indexOf("{") > -1 ? angular.fromJson(JSON.parse(response.data)) : "");
$scope.previousSelectedTypeOfEvent = (resp == "" ? "" : resp.TypeOfEvent);
$scope.previousSelectedEventTypeId = (resp == "" ? 0 : resp.EventTypeId);
$scope.previousSelectedEventName = (resp == "" ? "" : resp.EventName);
$scope.previousSelectedEventId = (resp == "" ? 0 : resp.EventId);
$scope.previousSelectedEventDate = (resp == "" ? "" : resp.EventDate);
//This loads the selection the initial time.
$scope.selectedTypeOfEvent = $scope.previousSelectedTypeOfEvent; //THIS Gets the previous radio button selection
//init EventTypeId dropdown
var initIdx = 0;
$scope.getEventTypeIds(true, initIdx);
}).then(function() {
//init Name dropdown
var initIdx = -1;
$scope.getEventNames(true, initIdx, $scope.previousSelectedEventTypeId);
}).then(function () {
//init EventIds and Dates dropdowns
var initIdx = -1;
$scope.getEventIdsAndDates(true, initIdx, $scope.previousSelectedEventName);
$scope.getEventDatesOnly(true, initIdx, $scope.previousSelectedEventName);
}).then(function () {
// init model values
$scope.typeOfEventRadioSelected($scope.previousSelectedTypeOfEvent);
$scope.updateModelValue(
$scope.previousSelectedTypeOfEvent,
$scope.previousSelectedEventTypeId,
$scope.previousSelectedEventName,
$scope.previousSelectedEventId,
$scope.previousSelectedEventDate);
});
$scope.updateModelValue = function (typeOfEvent, eventTypeId, eventName, eventId, eventDate) {
$scope.model.value = {
TypeOfEvent: typeOfEvent,
EventTypeId: eventTypeId,
EventName: eventName,
EventId: eventId,
EventDate: eventDate
}
console.log("Scope load and scope change");
console.log($scope.model.value);
};
//not used...attempting to make the visual nice onscreen but causes unpredictable loss of data. could be useful
$scope.addSpacesToCamelCase = function(txt) {
return txt.replace(/([a-z])([A-Z])/g, "$1 $2");
}
$scope.typeOfEventRadioSelected = function(selectedTypeOfEvent) {
//triggered when radio button selected.
var typeOfEvent = selectedTypeOfEvent;
var eventTypeId = $scope.selectedGalaxyEventTypeId != null
? $scope.selectedGalaxyEventTypeId.EventTypeId
: "";
var eventName = "";
var eventId = "";
var eventDate = "";
var initIdx = -1;
$("#GalaxyEventNameDdl").show();
$("#GalaxyEventIdDdl").show();
$("#GalaxyEventDatesDdl").show();
switch (selectedTypeOfEvent) {
case "NA":
$scope.getEventNames(false, initIdx, eventTypeId);
$scope.GalaxyEventIdsAndDates = $scope.initial;
$scope.GalaxyEventDates = $scope.initial;
$("#GalaxyEventNameDdl").hide();
$("#GalaxyEventIdDdl").hide();
$("#GalaxyEventDatesDdl").hide();
break;
case "RepeatingTimedEvent":
//$scope.getEventNames(false, initIdx, eventTypeId);
eventName = $scope.selectedGalaxyEventName != null
? $scope.selectedGalaxyEventName.EventName
: "";
$scope.GalaxyEventIdsAndDates = $scope.initial;
$scope.GalaxyEventDates = $scope.initial;
$("#GalaxyEventIdDdl").hide();
$("#GalaxyEventDatesDdl").hide();
break;
case "RepeatSingleDayEvent":
//$scope.getEventNames(false, initIdx, eventTypeId);
eventName = $scope.selectedGalaxyEventName != null
? $scope.selectedGalaxyEventName.EventName
: "";
$scope.getEventDatesOnly(false, initIdx, eventName);
eventDate = $scope.selectedGalaxyEventDates != null
? $scope.selectedGalaxyEventDates.EventDate
: "";
$scope.GalaxyEventIdsAndDates = $scope.initial;
$("#GalaxyEventIdDdl").hide();
break;
case "SingleTimedEvent":
//$scope.getEventNames(false, initIdx, eventTypeId);
eventName = $scope.selectedGalaxyEventName != null
? $scope.selectedGalaxyEventName.EventName
: "";
$scope.getEventIdsAndDates(false, initIdx, eventName);
eventId = $scope.selectedGalaxyEventId != null
? $scope.selectedGalaxyEventId.EventId
: "";
eventDate = $scope.selectedGalaxyEventDates != null
? $scope.selectedGalaxyEventDates.EventDate
: "";
$scope.GalaxyEventDates = $scope.initial;
$("#GalaxyEventDatesDdl").hide();
break;
}
$scope.updateModelValue(
typeOfEvent,
eventTypeId,
eventName,
eventId,
eventDate
);
};
$scope.eventTypeIDSelected = function (selectedEventTypeId) {
console.log("Event Type ID selected");
//triggered when EventTypeId dropdown is changed. update the datatype value & provide names in name dropdown
var initIdx = -1;
$scope.getEventNames(false, initIdx, selectedEventTypeId.EventTypeId);
$scope.GalaxyEventIdsAndDates = $scope.initial;// NOT WORKING TO WIPE THE LIST...WHY?
$scope.GalaxyEventDates = $scope.initial; // NOT WORKING TO WIPE THE LIST...WHY?
$scope.updateModelValue(
$scope.selectedTypeOfEvent,
selectedEventTypeId.EventTypeId,
"",
"",
"");
};
$scope.eventNameSelected = function (selectedName) {
//triggered when eventName dropdown is changed. update the datatype value & provide dates and ids in dropdowns
var initIdx = -1;
$scope.getEventIdsAndDates(false, initIdx, selectedName.EventName);
$scope.getEventDatesOnly(false, initIdx, selectedName.EventName);
$scope.updateModelValue(
$scope.selectedTypeOfEvent,
$scope.selectedGalaxyEventTypeId.EventTypeId,
selectedName.EventName,
"",
"");
};
$scope.eventIdSelected = function (selectedEventId) {
//triggered when eventId dropdown is changed. update the datatype value & init date dropdown
var initIdx = -1;
$scope.getEventDatesOnly(false, initIdx, $scope.selectedGalaxyEventName.EventName);
$scope.updateModelValue(
$scope.selectedTypeOfEvent,
$scope.selectedGalaxyEventTypeId.EventTypeId,
$scope.selectedGalaxyEventName.EventName,
selectedEventId.EventId,
selectedEventId.EventDate);
};
$scope.eventDatesSelected = function (selectedEventDate) {
//triggered when eventDate dropdown is changed. update the datatype value & init Id dropdown
var initIdx = -1;
$scope.getEventIdsAndDates(false, initIdx, $scope.selectedGalaxyEventName.EventName);
$scope.updateModelValue(
$scope.selectedTypeOfEvent,
$scope.selectedGalaxyEventTypeId.EventTypeId,
$scope.selectedGalaxyEventName.EventName,
"",
selectedEventDate.EventDate);
};
$scope.getEventTypeIds = function (initVal, idx) {
GalaxyEventSelectorResource.getEventIds().then(function (eventTypeIds) {
$scope.GalaxyEventTypes = eventTypeIds.data;
console.log("successfully retrieved galaxyeventids");
//console.log("Event Type IDs:", eventTypeIds.data[0]);
if (initVal) {
$scope.GalaxyEventTypes.some(function (x, i) {
if (x.EventTypeId == $scope.previousSelectedEventTypeId) {
idx = i;
return true;
}
});
}
$scope.selectedGalaxyEventTypeId = $scope.GalaxyEventTypes[idx];
},
function (data) {
console.log("failed to retrieve galaxyeventids");
});
};
$scope.getEventNames = function (initVal, idx, eventTypeId) {
GalaxyEventSelectorResource.getEventNamesByEventId(eventTypeId).then(function (eventNames) {
$scope.GalaxyEventNames = eventNames.data;
console.log("successfully retrieved galaxyeventnames");
if (initVal) {
$scope.GalaxyEventNames.some(function (x, i) {
if (x.EventName == $scope.previousSelectedEventName) {
idx = i;
return true;
}
});
}
$scope.selectedGalaxyEventName = $scope.GalaxyEventNames[idx];
},
function (data) {
console.log("failed to retrieve galaxyeventnames");
});
};
$scope.getEventIdsAndDates = function(initVal, idx, eventName) {
GalaxyEventSelectorResource.getEventIdsAndDatesByEventName(eventName).then(function (eventIds) {
$scope.GalaxyEventIdsAndDates = eventIds.data;
console.log("successfully retrieved galaxyIdsanddates");
if (initVal) {
$scope.GalaxyEventIdsAndDates.some(function(x, i) {
if (x.EventId == $scope.previousSelectedEventId) {
idx = i;
return true;
}
});
}
$scope.selectedGalaxyEventId = $scope.GalaxyEventIdsAndDates[idx];
},
function(data) {
console.log("failed to retrieve galaxyIdsanddates");
});
};
$scope.getEventDatesOnly = function (initVal, idx, eventName) {
GalaxyEventSelectorResource.getEventDatesByEventName(eventName).then(function (eventDates) {
$scope.GalaxyEventDates = eventDates.data;
console.log("successfully retrieved galaxydates");
if (initVal) {
$scope.GalaxyEventDates.some(function (x, i) {
if (x.EventDate == $scope.previousSelectedEventDate) {
idx = i;
return true;
}
});
}
$scope.selectedGalaxyEventDates = $scope.GalaxyEventDates[idx];
},
function (data) {
console.log("failed to retrieve galaxydates");
});
};
EventSelector.html
<div ng-controller="Our.GalaxyEventSelectorController">
<h5>Select Type of Event</h5>
<!--<div>-->
<div ng-repeat="n in ETypeRadio">
<!-- need to use ng-click as an ng-change on an ng-repeat element does not work -->
<!-- <input type="radio" ng-model="selectedTypeOfEvent" name="tOfE" ng-click="typeOfEventRadioSelected(selectedTypeOfEvent)" ng-value="{{n}}" value="{{n}}" />{{n}}-->
<input type="radio" ng-model="selectedTypeOfEvent" name="tOfE" ng-click="typeOfEventRadioSelected(selectedTypeOfEvent)" ng-value="{{n}}" value="{{n}}" />{{n}}
</div>
<!--</div>-->
<h5>Galaxy Event Type</h5>
<select ng-model="selectedGalaxyEventTypeId" ng-change="eventTypeIDSelected(selectedGalaxyEventTypeId)" ng-options="eventType.EventTypeId + ' - ' + eventType.EventTypeIdDescription for eventType in GalaxyEventTypes track by eventType.EventTypeId"></select>
<br/>
<div id="GalaxyEventNameDdl">
<h5>Galaxy Event Name</h5>
<select ng-model="selectedGalaxyEventName" ng-change="eventNameSelected(selectedGalaxyEventName)" ng-options="name.EventName for name in GalaxyEventNames">
<option value=""> --- Select Event Name ---</option>
</select>
<br />
</div>
<div id="GalaxyEventIdDdl">
<h5>Galaxy Event Id</h5>
<select data-ng-model="selectedGalaxyEventId" ng-change="eventIdSelected(selectedGalaxyEventId)" ng-options="id.EventId + ' - ' + id.EventDate for id in GalaxyEventIdsAndDates track by id.EventId">
<option value=""></option>
</select>
<br />
</div>
<div id="GalaxyEventDatesDdl">
<h5>Galaxy Event Date</h5>
<select data-ng-model="selectedGalaxyEventDates" ng-change="eventDatesSelected(selectedGalaxyEventDates)" ng-options="date.EventDate for date in GalaxyEventDates">
<option value=""></option>
</select>
</div>
We have a .resource.js but this is just performing gets to load our original data from our service.
angular.module("umbraco.resources").factory("GalaxyEventSelectorResource", function ($http) {
var galaxyEventService = {};
galaxyEventService.getEventIds = function () {
return $http.get("/umbraco/backoffice/api/GalaxyEventSelector/GetAllEventTypeIDs");
};
galaxyEventService.getEventById = function (id, propertyType) {
return $http.get("/umbraco/backoffice/api/GalaxyEventSelector/GetEventById?id=" + id + "&propertyType=" + propertyType);
};
galaxyEventService.getEventNamesByEventId = function(eventId) {
return $http.get("/umbraco/backoffice/api/GalaxyEventSelector/GetEventNamesByEventId?eventId=" + eventId);
};
galaxyEventService.getEventIdsAndDatesByEventName = function(eventName) {
return $http.get("/umbraco/backoffice/api/GalaxyEventSelector/GetEventIdsAndDatesByEventName?eventName=" + eventName);
};
galaxyEventService.getEventDatesByEventName = function(eventName) {
return $http.get("/umbraco/backoffice/api/GalaxyEventSelector/GetEventDatesByEventName?eventName=" + eventName);
};

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