Angular 3 filters (inputs and checkboxes) - angularjs

I need some help.
screenshot: https://monosnap.com/file/VxAdq975FVT6QHkECfxlFyHgGd3sAn
I have 3 filters on top: rooms, size and price. How to filter table results, when something typing in the filter fields?
UPDATE
$scope.$watch( '[min_size, max_size]', function(val) {
$scope.filterBySizeRange();
});
$scope.filterBySizeRange = function() {
$scope.filteredSizes = [];
angular.forEach($scope.apps, function(items) {
if (items.size >= $scope.min_size
&& items.size <= $scope.max_size) {
$scope.filteredSizes.push(items);
}
if (!$scope.min_size
&& !$scope.max_size) {
$scope.filteredSizes.push(items);
};
});
};
UPDATE 3
Here is my solution, that works with single or multiple range input fields
fiddle

I think you want to use $watchGroup.
$scope.$watchGroup(['min_size', 'max_size'], function(val) {
$scope.filterBySizeRange();
});
$scope.filterBySizeRange = function() {
$scope.filteredSizes = [];
angular.forEach($scope.apps, function(items) {
if (items.size >= $scope.min_size
&& items.size <= $scope.max_size) {
$scope.filteredSizes.push(items);
}
if (!$scope.min_size
&& !$scope.max_size) {
$scope.filteredSizes.push(items);
};
});
};
Anyway I believe that it would be better to create your own filter function
// template
<div ng-repeat="item in apps|sizefilter:min_size:max_size">
// filter
app.filter('sizefilter', function() {
return function(collection, minSize, maxSize) {
var items = collection.slice(0, collection.length -1);
var i =0, len = items.length
for (; i < len;) {
if (items.size < minSize && items.size > maxSize) {
items.splice(i, 1);
} else {
i++;
}
}
return items;
});
};
});

// Min/max size filter
$scope.sizeFilter = function(app){
if (!$scope.min_size && !$scope.max_size) {
return app;
} else if(!$scope.max_size){
return (app.size >= $scope.min_size);
} else if(!$scope.min_size){
return (app.size <= $scope.max_size);
} else {
return (app.size >= $scope.min_size && app.size <= $scope.max_size);
}
}

Related

ng-repeat how to use filetr with 2 parametrer

Here im using ng-repeat as
<tr ng-repeat="d in TranHistory">
<td>{{d.Quantity}}</td>
<td>{{d.Qty_Lock}}</td>
<td>{{d.Balancedcommodity |filter:GetBalance(d.Quantity,d.Qty_Lock)}}</td>
</tr>
im trying to do Sum Operation on filter:GetBalance
$scope.GetBalance = function (Quantity,lock) {
if (Quantity > 0 && lock > 0) {
var available = Quantity - lock;
return d.Balancedcommodity = available;
}
}
Here why im not able to update my value
Thank you
why don't you do this:
<td>{{GetBalance(d)}}</td>
and in your js:
$scope.GetBalance = function (d) {
if (d.Quantity > 0 && d.Qty_Lock > 0) {
var available = d.Quantity - d.Qty_Lock;
d.Balancedcommodity = available;
return d.Balancedcommodity;
}
return "";
}
And if you really wanna use filter: try custom filter like below
angular.filter('getBalance', function(){
return function(d){
if (d.Quantity > 0 && d.Qty_Lock > 0) {
var available = d.Quantity - d.Qty_Lock;
return available;
}
return "";
}
});
And in your html you can use it like this:
<td>{{d|getBalance}}</td>

Custom angular directive controller not updating

I created my own angular directive which can simply be used as:
<basket-summary></basket-summary>
This element is used on my master template page (the index.html).
The directive is as follows:
/* Directive */
angular.module('ecommerceDirectives').directive('basketSummary', [function() {
return {
restrict : 'E',
scope: {},
replace: true,
controller : 'BasketController',
controllerAs: 'basketController',
bindToController: {
basketController : '='
},
templateUrl: function(element, attrs) {
if (typeof attrs.templateUrl == 'undefined') {
return 'app/views/basket-summary.html';
} else {
return attrs.templateUrl;
}
},
link: function (scope, element, attrs) {
console.log("test");
}
};
}]);
The templateUrl of this directive is as follows:
<div class="btn-group pull-right">
<a class="btn btn-warning" ng-click="basketController.viewBasket()"><span class="badge">{{basketController.getTotalQuantities()}}</span> <span class="hidden-xs">Shopping</span> Cart</a>
<button type="button" class="btn btn-warning dropdown-toggle" data-toggle="dropdown">
<span class="fa fa-caret-down"></span>
<span class="sr-only">Toggle Dropdown</span>
</button>
<div class="dropdown-menu">
<div class="col-xs-12 cartItemHeader">
<h4>My Shopping Cart</h4>
</div>
<div class="quickCart" ng-repeat="cartItem in basketController.customerBasket.items">
<div class="col-xs-12 cartItemWrap">
<div class="col-xs-12 desc">{{cartItem.product.title}}</div>
<div class="col-xs-5 col-sm-8 price">{{cartItem.product.rrp_currency}} {{cartItem.product.rrp_amount}}</div>
<div class="col-xs-6 col-sm-3 units">Items: {{cartItem.quantity}}</div>
<div class="col-xs-1 trash"><a ng-click="basketController.deleteCartItem(cartItem.product.id)"><i class="fa fa-trash"></i></a></div>
</div>
</div>
</div>
And the BasketController is as follows:
/* Controller */
angular.module('ecommerceControllers').controller('BasketController', ['$rootScope', '$scope', '$route', '$location', 'CustomerBasketService', 'AppSettingService', 'StorageService', 'DEFAULT_CURRENCY_CODE', 'MERCHANT_ID_KEY', function($rootScope, $scope, $route, $location, CustomerBasketService, AppSettingService, StorageService, DEFAULT_CURRENCY_CODE, MERCHANT_ID_KEY) {
function basketProduct(product) {
this.id = product.id;
this.title = product.title;
this.categories = product.categories;
this.images = product.imaes;
this.created_on = product.created_on;
this.sku_code = product.sku_code;
this.lang = product.lang;
this.short_description = product.short_description;
this.attributes = product.attributes;
this.rrp_currency = product.rrp_currency;
this.rrp_amount = product.rrp_amount;
this.barcode_number = product.barcode_number;
this.last_modified_on = product.last_modified_on;
}
function basketItem(quantity, product) {
this.quantity = quantity;
this.product = new basketProduct(product);
}
function load() {
var basket = StorageService.get("customer_basket");
if (basket) {
for (var i = 0; i < basket.items.length; i++) {
var cartItem = basket.items[i];
cartItem = new basketItem(cartItem.quantity, cartItem.product);
basket.items[i] = cartItem;
}
}
return basket;
}
var basketController = this;
$scope.basketController = basketController;
basketController.customerBasket = load();
if (!basketController.customerBasket) {
basketController.customerBasket = {
shipping : null,
taxRate : null,
tax : null,
items : []
};
}
basketController.addCartItem = function(quantity, product) {
if (product == undefined || product == null) {
throw "No Product was specified.";
}
if (quantity == undefined || quantity == null) {
quantity = null;
}
var found = false;
if (basketController.customerBasket && basketController.customerBasket.items && basketController.customerBasket.items.length > 0) {
for (var i = 0; i < basketController.customerBasket.items.length; i++) {
var cartItem = basketController.customerBasket.items[i];
if (product.id === cartItem.product.id) {
found = true;
cartItem.quantity = cartItem.quantity + quantity;
if (cartItem.quantity < 1) {
basketController.customerBasket.items.splice(i, 1);
} else {
$rootScope.$broadcast('customerBasketItemUpdated', {item: cartItem});
}
}
}
}
if (!found) {
var cartItem = new basketItem(quantity, product);
basketController.customerBasket.items.push(cartItem);
$rootScope.$broadcast('customerBasketItemAdded', {item: cartItem});
}
basketController.saveBasket();
}
basketController.updateBasket = function() {
if (basketController.customerBasket && basketController.customerBasket.items && basketController.customerBasket.items.length > 0) {
for (var i = 0; i < basketController.customerBasket.items.length; i++) {
var cartItem = basketController.customerBasket.items[i];
if (cartItem.quantity < 1) {
basketController.customerBasket.items.splice(i, 1);
}
}
}
basketController.saveBasket();
}
basketController.deleteCartItem = function(productId) {
if (productId == undefined) {
throw "Product ID is required.";
}
if (basketController.customerBasket && basketController.customerBasket.items && basketController.customerBasket.items.length > 0) {
for (var i = 0; i < basketController.customerBasket.items.length; i++) {
var cartItem = basketController.customerBasket.items[i];
if (productId == cartItem.product.id) {
basketController.customerBasket.items.splice(i, 1);
}
}
}
//Save
basketController.saveBasket();
}
basketController.getCurrencyCode = function() {
var code = DEFAULT_CURRENCY_CODE;
if (basketController.customerBasket && basketController.customerBasket.items && basketController.customerBasket.items.length > 0) {
code = basketController.customerBasket.items[0].product.rrp_currency;
}
return code;
};
basketController.getTotalQuantities = function(id) {
var total = 0;
if (basketController.customerBasket && basketController.customerBasket.items && basketController.customerBasket.items.length > 0) {
for (var i = 0; i < basketController.customerBasket.items.length; i++) {
var cartItem = basketController.customerBasket.items[i];
if (id == undefined || id == cartItem.product.id) {
total += cartItem.quantity;
}
}
}
return total;
};
basketController.getTotalAmount = function(cartItem) {
var total = 0;
if (cartItem) {
total = (cartItem.quantity * cartItem.product.rrp_amount);
}
return total.toFixed(2);
};
basketController.getFinalTotalAmount = function() {
var total = 0;
if (basketController.customerBasket && basketController.customerBasket.items && basketController.customerBasket.items.length > 0) {
for (var i = 0; i < basketController.customerBasket.items.length; i++) {
var cartItem = basketController.customerBasket.items[i];
total += (cartItem.quantity * cartItem.product.rrp_amount);
}
}
return total.toFixed(2);
};
basketController.clearBasket = function() {
StorageService.set("customer_basket", null);
basketController.customerBasket = {
shipping : null,
taxRate : null,
tax : null,
items : []
};
$rootScope.$broadcast('customerBasketCleared', {});
}
basketController.saveBasket = function() {
if (basketController.customerBasket) {
StorageService.set("customer_basket", basketController.customerBasket);
$rootScope.$broadcast('customerBasketSaved', {});
}
}
basketController.checkout = function(serviceName, clearCart) {
if (serviceName == undefined || serviceName == null) {
serviceName = "PayPal";
}
switch (serviceName) {
case "PayPal":
basketController.checkoutPayPal(clearCart);
break;
default:
throw "Unknown checkout service '" + serviceName + "'.";
}
};
basketController.checkoutPayPal = function(clearCart) {
if (basketController.customerBasket && basketController.customerBasket.items && basketController.customerBasket.items.length > 0) {
// global data
var data = {
cmd: "_cart",
business: '', //parms.merchantID,
upload: "1",
rm: "2",
charset: "utf-8"
};
AppSettingService.getAppSetting(MERCHANT_ID_KEY).get().$promise
.then(function(result) {
data.business = result.value;
for (var i = 0; i < basketController.customerBasket.items.length; i++) {
var cartItem = basketController.customerBasket.items[i];
var ctr = i + 1;
data["item_number_" + ctr] = cartItem.product.sku_code;
data["item_name_" + ctr] = cartItem.product.title;
data["quantity_" + ctr] = cartItem.quantity;
data["amount_" + ctr] = cartItem.product.rrp_amount.toFixed(2);
}
// build form
var form = $('<form/></form>');
form.attr("action", "https://www.paypal.com/cgi-bin/webscr");
form.attr("method", "POST");
form.attr("style", "display:none;");
addFormFields(form, data);
$("body").append(form);
// submit form
form.submit();
form.remove();
if (clearCart) {
try {
basketController.clearBasket();
} catch (exception) {
}
}
}).catch(function(reason) {
console.log(reason);
});
}
};
basketController.viewBasket = function() {
$location.path("/basket");
$route.reload();
};
function addFormFields(form, data) {
if (data != null) {
$.each(data, function (name, value) {
if (value != null) {
var input = $("<input></input>").attr("type", "hidden").attr("name", name).val(value);
form.append(input);
}
});
}
}
return basketController;
}]);
My dilemma is as follows:
On my product page, I have product.html page (which is an angular view) and the "add to cart" button adds item to the cart but it doesn't update my cart summary button.
How do I make it that when I click the "add to cart" button that it update my cart and the number of items added to the cart?
The example image:
As you can see the orange button at the top right says that I have no items but there is 3 items in my cart already.
I tried various scoping allowed in directives but I seem to be failing to make it work.
PS: I am a newbie in AngularJS so please make your answer as simple to understand. :-)
The view product.html is linked to BasketController and this is the button that add item to cart.
<a class="btn btn-success btn-lg pull-right" role="button" ng-click="basketController.addCartItem(1, productController.selectedProduct)"><i class="fa fa-plus"></i> Add To Cart</a></div>
You have an isolated scope issue. The controller of your directive is not the same as the controller you want to use in your product page. See: scopes
I would suggest you to create a factory, which is always a singleton where you store your basket products and inject them in both, the directive and the products page

Truncate ng-bind-html in AngularJS

I'm using truncate.js https://github.com/sparkalow/angular-truncate and it works great for codes like this:
{{announcement.content | characters:25}}
However, i can't seem to setup for the following and i can't get it to work:
<p ng-bind-html="parseTrustedHtml(announcement.content | characters : 25)"></p>
I encountered a similar issue, the issue is that angular-truncate is meant for strings, not HTML. Here is my solution:
Markup:
<div class="container" ng-controller="parentCtrl">
<div ng-bind-html="text | limitHtml : maxNumberOfChar:'...' | trustAsHtml"></div>
</div>
Code:
.filter('trustAsHtml', ['$sce', function($sce) {
return $sce.trustAsHtml;
}])
.filter('limitHtml', function() {
return function(text, limit, ellipsis) {
var _getClosedTagsString = function(_tagArray) {
var _returnArray = [],
_getTagType = function(_string) {
return _string.replace(/<[\/]?([^>]*)>/,"$1");
};
angular.forEach(_tagArray,function(_tag,_i) {
if(/<\//.test(_tag)) {
if(_i === 0) {
_returnArray.push(_tag);
} else if(_getTagType(_tag) !== _getTagType(_tagArray[_i - 1])) {
_returnArray.push(_tag);
}
}
});
return _returnArray.join('');
},
_countNonHtmlCharToLimit = function(_text,_limit) {
var _isMarkup = false,
_isSpecialChar = false,
_break = false,
_underLimit = false,
_totalText = 0,
_totalChar = 0,
_element,
_return = {
textCounter : 0,
offsetCounter : 0,
setEllipsis : false,
overElementArray : []
};
angular.forEach(_text,function(_c) {
_underLimit = _return.textCounter < _limit;
if(_c === '<' && !_isMarkup && !_isSpecialChar) {
(!_underLimit) && (_element = '<');
_isMarkup = true;
} else if(_c === '&' && !_isMarkup && !_isSpecialChar) {
_isSpecialChar = true;
} else if(_isMarkup) {
//tracking html elements that are beyond the text limit
(!_underLimit) && (_element = _element + _c);
if(_c === '>') {
//push element in array if it is complete, and we are
//beyond text limit, to close any html that is unclosed
(!_underLimit) && (_return.overElementArray.push(_element));
_break = true;
_isMarkup = false;
}
} else if(_c === ';' && _isSpecialChar) {
_isSpecialChar = false;
//count as one character
_return.textCounter++;
_break = true;
}
if(_underLimit) {
if(!_isMarkup && !_isSpecialChar && !_break) {
//counting number of characters in non html string
_return.textCounter++;
}
_return.offsetCounter++;
} else {
_return.setEllipsis = true
}
_break = false;
});
//returns offset within html of number of non html characters found
return _return;
},
_charToLimitOutput = _countNonHtmlCharToLimit(text.toString(),limit);
return text.toString().substr(0, _charToLimitOutput.offsetCounter) +
ellipsis + _getClosedTagsString(_charToLimitOutput.overElementArray);
}
})
.controller('parentCtrl', function($scope,$timeout) {
$scope.text = "<span><h1>Example </h1><p>Special Text</p><div>other stuff</div></span>";
$scope.maxNumberOfChar = 10;
});
No need to use truncate.js
You can solve this using custom directives and filters.
try this one: https://stackoverflow.com/a/45076560/6816707
Need more information, however can you check if parseTrustedHtml method/function is available at $scope or $rootscope.

angularjs : how to restrict input type number to allow only even number with min and max limit as well as steps to increase

I am working on one requirement where I want to allow only even numbers to text box or number box(input type number). with minimum and maximum limit like from 4 to 14 and it should only increase by step of 2 if we have number box.
I tried with HTML input type number with min max and step attributes it's working fine but we can edit the text box with any number so to restrict I tried using directive but it's not working out for me. I will be glad if anyone can help me out with this.
HTML :
<body ng-controller="ctrl">
new : <number-only-input step="2" min="4" max="14" input-value="wks.number" input-name="wks.name" >
</body>
Script :
var app = angular.module('app', []);
app.controller('ctrl', function($scope){
$scope.name = 'Samir Shah';
$scope.price = -10;
$scope.wks = {number: '', name: 'testing'};
});
app.directive('numberOnlyInput', function () {
return {
restrict: 'EA',
template: '<input type="text" name="{{inputName}}" ng-model="inputValue" />',
scope: {
inputValue: '=',
inputName: '=',
min: '#',
max: '#',
step: '#'
},
link: function (scope) {
scope.$watch('inputValue', function(newValue,oldValue) {
var arr = String(newValue).split("");
if (arr.length === 0) return;
if (arr.length === 1 && (arr[0] == '-' || arr[0] === '.' )) return;
if (arr.length === 2 && newValue === '-.') return;
if (isNaN(newValue)) {
scope.inputValue = oldValue;
return;
}
if(!isNaN(newValue)){
if(newValue < parseInt(scope.min) || newValue > parseInt(scope.max)){
scope.inputValue = oldValue;
return;
}
}
});
}
};
});
<form name="testForm">
<div ng-controller="MyCtrl">
<input type="text" name="testInput" ng-model="number" ng-min="2" ng-max="14" required="required" numbers-only="numbers-only" />
<div ng-show="testForm.testInput.$error.nonnumeric" style="color: red;">
Numeric input only.
</div>
<div ng-show="testForm.testInput.$error.belowminimum" style="color: red;">
Number is too small.
</div>
<div ng-show="testForm.testInput.$error.abovemaximum" style="color: red;">
Number is too big.
</div>
<div ng-show="testForm.testInput.$error.odd" style="color: red;">
Numeric is odd.
</div>
</div>
</form>
angular.module('myApp', []).directive('numbersOnly', function () {
return {
require: 'ngModel',
link: function (scope, element, attrs, modelCtrl) {
element.bind('blur', function () {
if (parseInt(element.val(), 10) < attrs.ngMin) {
modelCtrl.$setValidity('belowminimum', false);
scope.$apply(function () {
element.val('');
});
}
});
modelCtrl.$parsers.push(function (inputValue) {
// this next if is necessary for when using ng-required on your input.
// In such cases, when a letter is typed first, this parser will be called
// again, and the 2nd time, the value will be undefined
if (inputValue == undefined) return ''
var transformedInput = inputValue.replace(/[^0-9]/g, '');
if (transformedInput != inputValue || (parseInt(transformedInput, 10) < parseInt(attrs.ngMin, 10) && transformedInput !== '1') || parseInt(transformedInput, 10) > parseInt(attrs.ngMax, 10) || (transformedInput % 2 !== 0 && transformedInput !== '1')) {
if (transformedInput != inputValue) {
modelCtrl.$setValidity('nonnumeric', false);
} else {
modelCtrl.$setValidity('nonnumeric', true);
}
if (parseInt(transformedInput, 10) < parseInt(attrs.ngMin, 10) && transformedInput !== '1') {
modelCtrl.$setValidity('belowminimum', false);
} else {
modelCtrl.$setValidity('belowminimum', true);
}
if (parseInt(transformedInput, 10) > parseInt(attrs.ngMax, 10)) {
modelCtrl.$setValidity('abovemaximum', false);
} else {
modelCtrl.$setValidity('abovemaximum', true);
}
if (transformedInput % 2 !== 0 && transformedInput !== '1') {
modelCtrl.$setValidity('odd', false);
} else {
modelCtrl.$setValidity('odd', true);
}
transformedInput = '';
modelCtrl.$setViewValue(transformedInput);
modelCtrl.$render();
return transformedInput;
}
modelCtrl.$setValidity('nonnumeric', true);
modelCtrl.$setValidity('belowminimum', true);
modelCtrl.$setValidity('abovemaximum', true);
modelCtrl.$setValidity('odd', true);
return transformedInput;
});
}
};
});
Active fiddle http://jsfiddle.net/tuckerjt07/1Ldmkmog/
You could define a property with getter and setter to process the entered value. If the value does not match the requrements display messages but not accept new value.
Using this method you could apply any validation logic, the second field editValue is needed because otherwise you could not enter an invalid number. Therefore editValue alows to enter numbers with numerous digits which will be partially invalid during entering the value.
Property:
// Property used to bind input containing validation
Object.defineProperty($scope, "number", {
get: function() {
return $scope.editValue;
},
set: function(value) {
value = parseInt(value);
$scope.editValue = value;
var isValid = true;
// Min?
if (value < parseInt($scope.min)) {
$scope.toSmall = true;
isValid = false;
} else {
$scope.toSmall = false;
}
// Max?
if (value > parseInt($scope.max)) {
$scope.toBig = true;
isValid = false;
} else {
$scope.toBig = false;
}
// Step not valid
if (value % parseInt($scope.step) > 0) {
$scope.stepNotValid = true;
isValid = false;
} else {
$scope.stepNotValid = false;
}
$scope.isValid = isValid;
if (isValid) {
$scope.value = value;
}
}
});
Working example
Below you can find a complete working example directive containing the property described above including increase/decrease buttons:
var app = angular.module('myApp', []);
app.directive('numberOnlyInput', function() {
return {
restrict: 'E',
template: '<input type="text" ng-model="number" ng-class="{\'error\': !isValid}"/><button ng-click="increase()">+</button><button ng-click="decrease()">-</button> Value: {{value}} {{stepNotValid ? (" value must be in steps of " + step) : ""}} {{toSmall ? " value must be greater or equal to " + min : ""}} {{toBig ? " value must be smaler or equal to " + max : ""}}',
scope: {
value: '=value',
min: '#',
max: '#',
step: '#'
},
link: function($scope) {
// Increase value
$scope.increase = function() {
var newValue = parseInt($scope.value) + parseInt($scope.step);
if (newValue <= $scope.max) {
$scope.number = newValue;
$scope.editValue = $scope.number;
}
};
// Decrease value
$scope.decrease = function() {
var newValue = parseInt($scope.value) - parseInt($scope.step);
if (newValue >= $scope.min) {
$scope.number = newValue;
$scope.editValue = $scope.number;
}
};
// Property used to bind input containing validation
Object.defineProperty($scope, "number", {
get: function() {
return $scope.editValue;
},
set: function(value) {
value = parseInt(value);
$scope.editValue = value;
var isValid = true;
// Min?
if (value < parseInt($scope.min)) {
$scope.toSmall = true;
isValid = false;
} else {
$scope.toSmall = false;
}
// Max?
if (value > parseInt($scope.max)) {
$scope.toBig = true;
isValid = false;
} else {
$scope.toBig = false;
}
// Step not valid
if (value % parseInt($scope.step) > 0) {
$scope.stepNotValid = true;
isValid = false;
} else {
$scope.stepNotValid = false;
}
$scope.isValid = isValid;
if (isValid) {
$scope.value = value;
}
}
});
// Init actual Value of the input element
$scope.number = parseInt($scope.value);
$scope.editValue = parseInt($scope.value);
}
};
});
app.controller('controller', function($scope) {
$scope.value = 10;
});
.error {
color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="controller">
Number:
<number-only-input min="4" max="14" step="2" value="value"></number-only-input>
</div>
Why are you doing too much of work of a simple thing. Max length will not work with <input type="number" the best way I know is to use oninput event to limit the maxlength. Please see the below code, Its a generic solution work with all the Javascript framework.
<input name="somename"
oninput="javascript: if (this.value.length > this.maxLength) this.value = this.value.slice(0, this.maxLength);"
type = "number"
maxlength = "6"
/>

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