AngularJS - orderBy value when ng-repeat by key, value pair - angularjs

I have counted the ocurrences of each item in a set of data with countBy function in lodash, and the result is this:
$scope.colors= {
"Orange": 3,
"Blue": 2,
"Pink": 1,
"Red": 1,
"Black": 2,
};
Now I would like to display the data and order it by its value. I've tried with
<div ng-controller="myCtrl" ng-app="myApp">
<ul>
<li ng-repeat="(key,value) in colors | orderBy:value">
{{key}} ({{value}})
</li>
</ul>
</div>
But the orderBy filter doesn't seem to do the trick (see the plunkr here)
Is there any way to do it with the current data form, or is it a better way to structure the data in order to achieve the desired result? Thanks in advance!

As the documentation says, orderBy expects an array as input, not an object. In general, although it's supported, I've never met a use-case where using an object with ng-repeat was a good idea.
Just transform your object into an array:
$scope.colors = [];
angular.forEach(occurrences, function(value, key) {
$scope.colors.push({
color: key,
count: value
});
});
and then use
<li ng-repeat="element in colors | orderBy:'count'">
{{ element.color }} ({{ element.count }})
</li>
See your updated plunkr.

After multiple tries, I found a way to iterate an object and have it ordered by the key.
<div ng-repeat="key1 in keys(obj1) | orderBy: key1">

I would instead use an array like so:
colors = [{color: 'red', value: 1}, {color: 'blue', value: 2}]
Then you can use
ng-repeat="color in colors | orderBy:'value'"
Plunkr

different approach - for
string from JsonConvert.SerializeObject(DataTable), so basically List<Dictionary<string, string>>
Example is combination of few solutions (sorry for no references)
In AngularJs controller:
vm.propertyName = '\u0022Spaced Column Name\u0022';
vm.reverse = true;
vm.sortBy = function (propertyName) {
vm.reverse = (vm.propertyName === '\u0022' + propertyName +'\u0022') ? !vm.reverse : false;
vm.propertyName = '\u0022' + propertyName + '\u0022';
};
vm.Data = JSON.parse(retValue.Data);
for (var i = 0; i < vm.Data.length; i++) {
var obj = {};
angular.forEach(vm.Data[i], function (value, key) {
obj[key] = value;
});
vm.DataArr.push(obj);
}
By ng-if="key != 'Id'" I'm hiding Id column with Guid
And then in html:
<table border="1">
<tr>
<td ng-repeat="(key, value) in vm.Data[0]" ng-if="key != 'Id'">
<button ng-click="vm.sortBy(key)">{{key}}</button>
<span ng-show="vm.propertyName == '\u0022'+key+'\u0022' && !vm.reverse" class="fa fa-arrow-up"></span>
<span ng-show="vm.propertyName == '\u0022'+key+'\u0022' && vm.reverse" class="fa fa-arrow-down"></span>
</td>
</tr>
<tr ng-repeat="row in vm.DataArr |orderBy:vm.propertyName:vm.reverse track by $index" ng-init="rowInd0 = $index">
<td ng-repeat="(key, value) in vm.Data[0]" ng-if="key != 'Id'">
<span>{{row[key]}}</span>
</td>
</tr>
</table>

Related

angular-ui-scroll with (key, value) ng-repeat syntax

I am looking into performance improvements for a large table I am rendering and have come across angular-ui-scroll which I would like to try out.
In my table I am using the key\value accessor on my ng-repeat, e.g:
<tr ng-repeat="(key, value) in vm.stopTimes track by key">
<td class="timetable-detail-stop" layout="row" flex layout-align="start center">
{{ vm.expandedTimetable.stops[key].name }}
</td>
<td ng-repeat="departure in value.times track by $index">
{{departure.time}}
</td>
</tr>
Can I use the supported key\value syntax from ng-repeat with ui-scroll? I'm not so sure I can having read through the docs.
Has anyone done this using keyed objects\dictionaries?
Thanks
If we are talking about table rows virtualizing, then it may look like
<div style="height: 500px; overflow: auto;" ui-scroll-viewport>
<table>
<tr ui-scroll="item in vm.datasource">
<td class="timetable-detail-stop">
{{ vm.expandedTimetable.stops[item.key].name }}
</td>
<td ng-repeat="departure in item.value.times track by $index">
{{departure.time}}
</td>
</tr>
</table>
</div>
Where the datasource is an object with get method which returns (via success callback) an array of items based on index and count params:
this.datasource = {
get: (index, count, success) => {
let result = [];
for (let i = index; i <= index + count - 1; i++) {
const stopTimeKey = this.getStopTimeKeyByIndex(i);
result.push({
key: stopTimeKey,
value: this.stopTimes[stopTimeKey]
});
}
success(result);
}
}
Here I assume that we can get stopTimes key by index... The simplest implementation of getStopTimeKeyByIndex method may be
this.getStopTimeKeyByIndex = (index) => Object.keys(this.stopTimes)[index];

Angular Filter based on Multiselect-Dropdown

I use nya-bootstrap-select directive (http://nya.io/nya-bootstrap-select/)
for a Multiselect-Dropdown ( Angular 1.5.8 ).
Now I would like to filter a collection based on the selected options.
Example:
https://jsfiddle.net/mrtzdev/2z6xfo5w/18/
<ol id="dynamic-options" class="nya-bs-select" ng-model="model2" multiple >
<li nya-bs-option="(key,value) in companyList" >
<a>{{ value.name}}</a>
</li>
</ol>
Filtered Collection:
<tbody>
<tr ng-repeat="client in clients | filter:{ name: model1.name, company: { name: model2.name } } ">
<td>{{$index + 1}}</td>
<td><em>{{client.name}}</em>
</td>
<td>{{client.designation}}</td>
<td>{{client.company.name}}</td>
</tr>
</tbody>
This obviously does not work for the multiselect. How can use a custom filter, to filter on multi-selected options ?
I modified your fiddle and make it work. This filter is not good in dynamic handling but it will allow you to filter your data by the exact filter attribute name.
View
<tr ng-repeat="client in clients | filter:{ name: model1.name} | inArrayExact : { myArray: model2, searchKey: 'name', filterKey: 'company' }">
<td>{{$index + 1}}</td>
<td><em>{{client.name}}</em>
</td>
<td>{{client.designation}}</td>
<td>{{client.company.name}}</td>
</tr>
AngularJS custom filter
App.filter('inArrayExact', function($filter){
return function(list, arrayFilter, element){
if((angular.isArray(arrayFilter.myArray) && arrayFilter.myArray.lenght > 0) && || angular.isObject(arrayFilter.myArray)){
var itemsFound = {};
angular.forEach(list, function (listItem, key) {
angular.forEach(arrayFilter.myArray, function (filterItem) {
if (angular.isDefined(filterItem[arrayFilter.searchKey])
&& angular.isDefined(listItem[arrayFilter.filterKey])
&& angular.isDefined(listItem[arrayFilter.filterKey][arrayFilter.searchKey])) {
if (angular.isUndefined(itemsFound[key])
&& listItem[arrayFilter.filterKey][arrayFilter.searchKey] == filterItem[arrayFilter.searchKey]) {
itemsFound[key] = listItem;
}
}
});
});
return itemsFound;
} else {
return list;
}
};
});
model2 is an array objects not a string, you need to create a custom filter where you pass model2 and loop over the objects and check the name.
you can check this link on how to build custom filters
https://toddmotto.com/everything-about-custom-filters-in-angular-js/

How to change $index for two ngRepeats with same input but different filters?

I've simulated my problem here.
Looking into this html, you can see that I am doing two ng-repeats with the same array as input, but different filters to each one:
<div ng-app='Lists'>
<div ng-controller='listsController'>
<table>
<tbody>
<tr ng-repeat='item in listValues | filter : xxx track by $index' ng-click="update($index)">
<td>{{item.ref}}</td>
<td>{{item.others}}</td>
</tr>
</tbody>
</table><hr/>
<table>
<tbody>
<tr ng-repeat='item in listValues | filter : yyy track by $index' ng-click="update($index)">
<td>{{item.ref}}</td>
<td>{{item.others}}</td>
</tr>
</tbody>
</table><hr/>
<div>{{updateIndex}}</div>
</div>
</div>
And my js code:
var appModule = angular.module('Lists', []);
appModule.controller('listsController', function($scope) {
$scope.listValues = [
{'ref' : '1', 'others' : 'abc..'},
{'ref' : '2', 'others' : 'def..'},
{'ref' : '1', 'others' : 'ghi..'},
{'ref' : '2', 'others' : 'jkl..'}
];
$scope.xxx = function(a){
return a.ref == 1;
};
$scope.yyy = function(a){
return a.ref == 2;
};
$scope.update = function(i) {
$scope.updateIndex = i;
};
$scope.updateIndex = "none";
});
The problem I'm stuck is that the update(index) function needs to change the object in the correct index of the listValues array. But as you can see clicking in the object of the second table gives me the $index of the first table.
How to work around this situation? Thanks in advance.
Using the $index is doomed to fail, even if you iterate once. $index is the index of the current item in the filtered array. And that index is different from the index of the same element in the original, non-filtered array.
If you want to modify an item on click, don't pass its index as argument. Pass the item itself:
ng-click="update(item)"
Instead of filters use ng-if which allows you to track items by index.Index will give exact click even list has duplicate items
<body>
<div ng-app='Lists'>
<div ng-controller='listsController'>
<table>
<tbody>
<tr ng-repeat="item in listValues track by $index" ng-click="update($index)" ng-if="xxx(item)=='1'">
<td>{{item.ref}}</td>
<td>{{item.others}}</td>
</tr>
</tbody>
</table><hr/>
<table>
<tbody>
<tr ng-repeat='item in listValues track by $index' ng-click="update($index)" ng-if="item.ref=='2'">
<td>{{item.ref}}</td>
<td>{{item.others}}</td>
</tr>
</tbody>
</table><hr/>
<div>{{updateIndex}}</div>
</div>
</div>
</body>

How to remove object from array within ng-repeat with AngularJS?

I am having an array with objects like [{...}, {...}] which I am outputting with ng-repeat. Then I have a delete button with a function to delete it.
Is there a simple way to delete it in AngularJS, perhaps with $index? Or I need to specify an ID on every object as an property?
If you don't apply a filter to reorder or filter your array, you can do this:
<div ng-repeat="item in items" ng-click="delete($index)">{{item}}</div>
And the delete function:
$scope.items = [...];
$scope.delete = function (index) {
$scope.items.splice(index, 1);
}
Another way to do it without filter problems: (ONLY IE9+)
<div ng-repeat="item in items | orderBy: 'id'" ng-click="delete(item)">{{item}}</div>
And the delete function:
$scope.items = [...];
$scope.delete = function (item) {
$scope.items.splice($scope.items.indexOf(item), 1);
}
http://jsfiddle.net/oymo9g2f/2/
Here is another example, using Jade too:
template.jade:
label All Items
ul.list-group
li.list-group-item(ng-repeat="item in items | orderBy: '_id'")
strong {{item.name}}
a.trash(ng-click='deleteItem(item)')
//a.trash is a bootstrap trash icon, but you don't need to use it.
controller.js:
$scope.deleteItem = function (item) {
$scope.items.splice($scope.items.indexOf(item),1);
}
removeWith
comparison for each element in a collection to the given properties object,
returning an array without all elements that have equivalent property values.
$scope.collection = [
{ id: 1, name: 'foo' },
{ id: 1, name: 'bar' },
{ id: 2, name: 'baz' }
]
<tr ng-repeat="obj in collection | removeWith:{ id: 1 }">
{{ obj.name }}
</tr>
<tr ng-repeat="obj in collection | removeWith:{ id: 1, name: 'foo' }">
{{ obj.name }}
</tr>
First try to do it this way, but the listing was not actualized at runtime.
$scope.delete = function (index) {
delete $scope.items[index];
}
Then with the answer given above by Facundo Pedrazzini did work properly for me.
$scope.delete = function (index) {
$scope.items.splice(index, 1);
}
Version: AngularJS v1.6.4
In blade.php
<table style="width:100%;">
<tr ng-repeat="name in planFormData.names track by $index">
<td>
<div class="form-group">
<label>Plan Name<span style="color:red;">*</span> </label>
<input type="text" class="form-control" ng-model="planFormData.names[$index].plan_name" name="plan_name" id="status-name" placeholder="Plan Name" autocomplete="off" required>
</div>
</td>
<td>
<i class="icon-plus" ng-click="addRow($index)" ng-show="$last"></i>
<i class="icon-trash" ng-click="deleteRow($event,name)" ng-show="$index != 0"></i>
</td>
</tr>
</table>
In controller.js
$scope.deleteRow = function($event, name) {
var index = $scope.planFormData.names.indexOf(name);
$scope.planFormData.names.splice(index, 1);
};
In Angular 6, I did similar for Multi Dimensional Array. It's working
RemoveThisTimeSlot(i: number, j: number) {
this.service.formData.ConsultationModelInfo.ConsultationWeekList[i].TimeBlockList.splice(j, 1);
}

Getting AngularJS orderBy to sort both directions

I'm attempting to setup a clickable table column header that sort Ascending when first clicked, and Descending when clicked again. My ascending sort is working fine, but I'm not sure how to setup an expression within my OrderBy to sort Descending
My setup thus far:
Table html has something like
<th ng-click="sort('LastName')">Last Name</th>
My sort method looks like
scope.sort = function (columnName) {
if (angular.isDefined(scope.filter)) {
if (scope.filter.SortColumn == columnName) {
scope.filter.SortColumn = columnName;
scope.filter.SortDirection = scope.filters.SortDirection == "Asc" ? "Desc" : "Asc";
} else {
scope.filter.SortColumn = columnName;
scope.filter.SortDirection = "Asc";
}
}
};
And my ng-repeat looks as follows
<tbody ng-repeat="name in resp.Names | orderBy : filter.SortColumn">
How can I get the SortDirection to factor into the orderBy?
To simply reverse you'd change it to this:
<tbody ng-repeat="name in resp.Names | orderBy : filter.SortColumn : true">
It'd be best if you used a boolean in your controller, but this should work too:
<tbody ng-repeat="name in resp.Names | orderBy : filter.SortColumn : filter.SortDirection === 'Desc'">
And just for fun, here's how I do sorting with filtering in my tables.
Controller:
$scope.search = { query: ''};
$scope.sort = { field: 'defaultField', descending: true};
$scope.order = function(newValue) {
if(newValue === $scope.sort.field) {
$scope.sort.descending = !$scope.sort.descending;
} else {
$scope.sort = {field: newValue, descending: false};
}
};
$scope.filteredDocuments = function() {
var a = $filter('filter')($scope.documents, {$:$scope.search.query});
var b = $filter('orderBy')(a, $scope.sort.field, $scope.sort.descending);
return b;
};
A search box for filtering:
<input type="text" ng-model="search.query">
A column header:
<th nowrap>
<a href ng-click="order('size')">Size </a>
<i ng-show="sort.field === 'size' && !sort.descending" class="fa fa-sort-amount-asc"></i>
<i ng-show="sort.field === 'size' && sort.descending" class="fa fa-sort-amount-desc"></i>
</th>
The row binding:
<tr ng-repeat="d in filteredDocuments()" >
A simplified version of the above answer:
<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<body>
<p>Click the table headers to change the sorting order:</p>
<div ng-app="myApp" ng-controller="namesCtrl">
<table border="1" width="100%">
<tr>
<th ng-click="orderByMe('name')" >Name</th>
<th ng-click="orderByMe('country')">Country</th>
</tr>
<tr ng-repeat="x in names | orderBy:myOrderBy:mySortOrder">
<td>{{x.name}}</td>
<td>{{x.country}}</td>
</tr>
</table>
</div>
<script>
angular.module('myApp', []).controller('namesCtrl', function($scope) {
$scope.names = [
{name:'Jani',country:'Norway'},
{name:'Carl',country:'Sweden'},
{name:'Margareth',country:'England'},
{name:'Hege',country:'Norway'},
{name:'Joe',country:'Denmark'},
{name:'Gustav',country:'Sweden'},
{name:'Birgit',country:'Denmark'},
{name:'Mary',country:'England'},
{name:'Kai',country:'Norway'}
];
$scope.sorts={};
$scope.orderByMe = function(x) {
$scope.myOrderBy = x;
if(x in $scope.sorts) {
$scope.sorts[x]=!$scope.sorts[x];
} else {
$scope.sorts[x]=false;
}
$scope.mySortOrder=$scope.sorts[x];
}
});
</script>
</body>
</html>

Resources