Is there a way to escape dot notation in AngularJS groupBy expression? - angularjs

I am using this AngularJS filter: https://github.com/a8m/angular-filter#groupby
I have a piece of data (JSON) involving key names containing dots, which seems to be mistaken with nested properties.
Is there a way to prevent the filter from parsing the dot notation or an alternate way to specify the name of the field used for grouping ?
Note: I can't change the way data are build from the server.
My code looks like this :
<div ng-repeat="(key, values) in items | groupBy: 'category_id.name'" >
<div class="item-row">
{{key}}
</div>
<div class="badge-row" ng-repeat="item in values">
{{item.name}}
</div>
</div>
And here is the JSON data I get from the server :
[
{
"id": "1",
"name": "test",
"category_id.name": "Main"
},
{
"id": "2",
"name": "foo",
"category_id.name": "Other category"
},
{
"id": "3",
"name": "bar",
"category_id.name": "Test"
}
]
When running this code, at the moment, I am getting 'undefined' as key value.

Angular Filter uses $parse, so that you can order by fields in nested objects
I have a workaround for this problem. If it helps.
Template:
(key, values) in items | groupBy: rawProperty('category_id.name')
Controller:
$scope.rawProperty = function(key) {
return function(item) {
return item[key];
};
};
JS Bin Demo
Hope it helps.

Related

Filtering Angular templates by JSON property gets Digest infinite loop

I am attempting to filter the results of an ng-repeat in a directive template. The below solution works as in it displays well on the screen, however I now get the error: Uncaught Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting! I referenced this page and the solution did not work: https://docs.angularjs.org/error/$rootScope/infdig
Any suggestions on how I can fix this? Or a better way to go about it?
HTML:
<filtered-set items="businesses | filter : {cat: 'jedi'} : true | filter:query |orderBy: orderList"></filtered-set>
Template:
<div class="bscroll mThumbnailScroller" data-mts-axis="x">
<ul>
<li class="business-card" data-ng-repeat="business in items" data-ng-click="select(business)">
<h2>{{business.name}}</h2>
<p>{{business.cat}}</p>
</li>
</ul>
</div>
Angular JS:
.controller('starWarsCtrl', function ($scope) {
$scope.businesses = [
{"name": "Obi-Wan Kenobi",
"index":88,
"cat": "jedi"},
{"name": "Yoda",
"index":69,
"cat":"jedi"},
{"name": "Lando",
"index":31,
"cat": "smuggler"},
{"name": "Han Solo",
"index":90,
"cat": "smuggler"},
{"name": "Darth Vader",
"index":98,
"cat": "sith"},
{"name": "Jar-Jar Binks",
"index":80,
"cat": "alien"},
{"name": "Mace Windu",
"index":45,
"cat": "jedi"},
{"name": "Chewy",
"index":76,
"cat": "smuggler"}
];
.directive('filteredSet', function() {
return {
restrict: 'E',
scope: {
items: '='
},
templateUrl: 'partials/filtered-set.html'
};
});
There may be a way to solve my above question as-is however I found a much better solution to avoid that problem. Here is what I did instead:
I created this function in my controller, it basically grabs all the JSON properties matching the "cat" property:
angular.forEach($scope.data, function(item) {
//item.cat is a string
if (categories.indexOf(item.cat) == -1) {
categories.push(item.cat);
}
});
return categories;
}
and my HTML, which is essentially a ng-repeat within an ng-repeat:
<div data-ng-app="app" data-ng-controller="starWarsCtrl">
<ul>
<li data-ng-repeat="cat in getCategories()">
<h2>{{cat}}</h2>
<div ng-click="select(i)" ng-repeat="i in data | filter:{cat: cat}">
<p>{{i.name}}</p>
</div>
</li>
</ul>
</div>
I also created a Codepen with my solution: http://codepen.io/Auzy/pen/adeqrP
I hope this helps!

Using ng-repeat in order to iterate over object properties and ordering by value instead of key with ng 1.3

I am in reference to the angular documentation about ngRepeat and iterating over object properties which states:
<div ng-repeat="(key, value) in myObj"> ... </div>
You need to be aware that the JavaScript specification does not define
the order of keys returned for an object. (To mitigate this in Angular
1.3 the ngRepeat directive used to sort the keys alphabetically.)
Say I have the following object:
var myObj = {FOO: 0, BAR: 1};
and want it to be ordered by value (i.e. 0 & 1) instead of keys. How can this be achieved with angular? (I use angular 1.3 by the way).
I have tried:
ng-repeat="(key, value) in myObj | orderBy:value"
But it does not work...
Can anyone please help?
Just as with filter, orderBy works with arrays only. One simple solution is to use a function that returns an array from an object:
<!DOCTYPE html>
<html>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<body>
<div ng-app="myApp" ng-controller="namesCtrl">
<input type="text" ng-model="searchText"/>
<ul ng-init="nameArray=objArray(names)">
<li ng-repeat="x in nameArray | filter:searchText | orderBy: 'value.name'">
{{x.value.name}}
</li>
</ul>
</div>
<script>
angular.module('myApp', []).controller('namesCtrl', function($scope) {
$scope.searchText='';
$scope.names = {
"1": {
"name": "some"
},
"2": {
"name": "values"
},
"3": {
"name": "are"
},
"4": {
"name": "there"
},
"5": {
"name": "here"
}
};
$scope.objArray=function (obj) {
var result=[];
for (var key in obj) {
result.push({
key: key,
value: obj[key]
});
}
return result;
}
});
</script>
</body>
</html>
I believe the best and elegant solution is to make a filter that changes your object into an array. Therefore you can reuse it thought all your project in your template without coding any js.
Here is what the filter would look like:
(function () {
'use strict';
angular.module('app').filter('toArray', toArray);
toArray.$inject = [];
function toArray() {
return toArrayFilter;
function toArrayFilter(input) {
if (angular.isArray(input)) return input;
var list = [];
angular.forEach(input, iterateProperty, list);
return list;
function iterateProperty(elt, key) {
this.push({ key: key, value: elt });
}
}
}
})();
and here is how you would use it in your html template:
<div ng-repeat="element in myObject | toArray | orderBy:value">
{{element.key}}:
<pre>{{element.value | json}}</pre>
</div>

Angularjs orderBy in ng-repeat not working as expected

I have the following div in my template:
<div ng-repeat="item in billing_history | orderBy:'-timestamp'">{{ item.date }}</div>
console.log(JSON.stringify($scope.billing_history)) gives me the following:
{
"May, 2015":{
"date":"May, 2015",
"timestamp":1432921230
},
"March, 2015":{
"date":"March, 2015",
"timestamp":1427846400
},
"February, 2015":{
"date":"February, 2015",
"timestamp":1425168000
}
}
No matter what, this is what is displayed:
February, 2015
March, 2015
May, 2015
I've tried orderBy:'-timestamp' and orderBy:'+timestamp'
I'm really not sure why this isn't working. Does anyone see anything that could be going wrong?
You cannot use order-by filter with an object literal (or it wont just work as expected). What you have is an object literal basically there is no specific guaranteed ordering for the keys (and so for its values). You would need to convert it to an array.
Example:
angular.module('app', []).run(function($rootScope) {
$rootScope.billing_history = [{
"date": "May, 2015",
"timestamp": 1432921230
}, {
"date": "March, 2015",
"timestamp": 1427846400
}, {
"date": "February, 2015",
"timestamp": 1425168000
}]
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<p>Reverse order:
<div ng-repeat="item in billing_history | orderBy:'-timestamp'">{{ item.date }}</div>
<p>Correct order:
<div ng-repeat="item in billing_history | orderBy:'timestamp'">{{ item.date }}</div>
</div>
Filter is one option, but be careful, filters are very performance intensive (they run as many times every digest cycle to stabilize and so what is in the filter matters much) and for operations like this on a large object it is very tricky. So better to set up view model appropriately or convert the format in the controller itself.
orderBy filter works on an array and you are using it on a object. Convert the object into array and try it.
Demo
http://plnkr.co/edit/4XtrJQaJ1itrVbb9bwE7?p=preview
You can create your own custom filter as Justin Klemm does here
app.filter('orderObjectBy', function() {
return function(items, field, reverse) {
var filtered = [];
angular.forEach(items, function(item) {
filtered.push(item);
});
filtered.sort(function (a, b) {
return (a[field] > b[field] ? 1 : -1);
});
if(reverse) filtered.reverse();
return filtered;
};
});
And then your markup turns into:
<div ng-repeat="item in billing_history | orderObjectBy:'timestamp'">
{{ item }}
</div>
Or
<div ng-repeat="item in billing_history | orderObjectBy:'-timestamp'">
{{ item }}
</div>
See this jsBin

JSON Angular Array pulling data from 3rd Loop

I am struggling to pull the data from the 3rd loop (Names) in the array below.
Any idea what i am doing wrong?
sample.json
{
"otc": [
{
"name": "Tums",
"language": [
{
"title": "English",
"names": [
{"name": "Tums"},
{"name": "Pepto"}
]
},
{
"title": "China"
},
{
"title": "Germany"
}
,
{
"title": "Mexico"
}
,
{
"title": "India"
}
,
{
"title": "United Kingdom"
}
]
},
{
"name": "Dramamine",
"language": [
{
"title": "title2album1"
},
{
"title": "title2album2"
},
{
"title": "title2album3"
}
]
}
]
}
And this is my index.html
<body ng-app="list">
<div ng-controller="ListCtrl">
<ul>
<li ng-repeat-start="meds in otc">
<strong> {{meds.name}}</strong> //This displays just fine
</li>
<li ng-repeat="lang in meds.language"><em>{{lang.title}}</em></li> //This displays just fine
<li ng-repeat-end ng-repeat="drugs in lang.names">{{drugs.name}}</li> //This doesnt display
</ul>
</div>
<script>
angular.module('list', []);
function ListCtrl($scope, $http) {
$http({method: 'GET', url: 'sample.json'}).success(function(data) {
$scope.otc = [];
angular.forEach(data.otc, function(value, key) {
$scope.otc.push(value);
});
$scope.isVisible = function(name){
return true;
};
});
}
</script>
It looks like your JSON data is a bit incomplete, since you're missing the names key in all of the language objects except for the English one.
Beyond that, your html/angular-view code is a bit off. The ng-repeat's should be nested inside of one-another. This is done by having the opening/closing html tags that contain the ng-repeat directive completely surround the inner <li> tags.
It's a bit hard to tell, but if you want nested lists, you need to add <ul> tags like in my example below.
I believe your index html should look something like:
<ul>
<li ng-repeat="meds in otc">
<strong> {{meds.name}}</strong> //This displays just fine
<ul>
<li ng-repeat="lang in meds.language">
<em>{{lang.title}}</em>
<ul>
<li ng-repeat="drugs in lang.names">{{drugs.name}}</li>
</ul>
</li>
</ul>
</li>
</ul>
The reason why the li ng-repeat tags should be nested is because ng-repeat creates its own isolate scope. Which means that inside of an ng-repeat tag, it only has access to the data made available by the ng-repeat="data in datar" part.
So having:
<li ng-repeat="lang in meds.language"><em>{{lang.title}}</em></li> //This displays just fine
<li ng-repeat="drugs in lang.names">{{drugs.name}}</li> //This doesnt display
as sibling elements, and not the second as a child of the other, the 2nd ng-repeat list does not know what the var lang is. So the drugs in lang.names fails.
Btw, this snippet assumes that the output you want is:
Tums
English
Tums
Pepto
China
Germany
etc
Dramamine
title2album1
title2album2
etc
If you wanted the output as a flat list, you can use the following CSS
ul ul {
padding: 0;
margin: 0;
list-style-type: disc;
}

Angular order dropdown select list

How I can order the list in select dropdown using Angular ?
This is the angular controller:
var app = angular.module("app", []);
app.controller('Ctrl', function($scope, $filter, $http) {
$scope.options= [
{
"id": 823,
"value": "81"
},
{
"id": 824,
"value": "77"
},
{
"id": 825,
"value": "152"
},
];
});
And html:
<h4>Angular-xeditable Editable row (Bootstrap 3)</h4>
<div ng-app="app" ng-controller="Ctrl">
<select ng-model="test" ng-options="v.id as v.value for v in options | orderBy: value"></select>
</div>
Now the order is: 81, 77, 152
And I want: 77,81,152
How I can do it ?
Jsfiddle to test
Thanks
Your values are strings so they won't be naturally ordered unless you convert them to integers.
You may convert them by creating your own filter or by defining a simple sorting function on your $scope:
Fiddle
<select
ng-model="test"
ng-options="v.id as v.value for v in options | orderBy: naturalOrder"
></select>
$scope.naturalOrder = function(item){
return parseInt(item.value, 10);
};
You need to place single quotes around the name of the field in your orderBy. The first argument to orderBy is a sort expression string.
in options | orderBy:'value'
You can extend this by adding a + or - before the field name to indicate initial sort direction. You can also provide a boolean value after the sort expression to enable toggling of the sort direction.
<div ng-init="desc=true">
...
in options | orderBy:'+value':desc

Resources