AngularJS orderBy a column containing a list - arrays

is it possible to use orderBy with something other than strings and numbers?
I have a list of customers and one of the columns contains an array of values (list of urls). I want to order by the url list, i.e. the first url in the list.
I thought the array would maybe be automatically converted to a string. I also tried using providerUrls[0] as orderBy criteria.
I know I could write my own sorting function but if there is a simple solution for this I would like to learn how to use it.
This is a reduced example of my code:
<table>
<thead>
<tr>
<td>
<p data-ng-click="sortBy = providerUrls; sortReverse = !sortReverse;">
URLs
<span data-ng-show="sortBy === providerUrls && !sortReverse">
<img src="arrow-down.svg"/>
</span>
<span data-ng-show="sortBy === providerUrls && sortReverse">
<img src="arrow-up.svg"/>
</span>
</p>
</td>
</tr>
</thead>
<tbody>
<tr data-ng-repeat="provider in providerFilter = (providers | orderBy:sortBy:sortReverse) track by $index">
<td>
<p data-ng-repeat="url in provider.urls track by $index">
{{url}}
</p>
</td>
</tr>
</tbody>
</table>
My data structure:
var a = {
providerId: "a",
providerUrls: ["abc", "def", "ghi"]
// more properties
};
// b and c similar
$scope.providers = [a, b, c];

Related

ng-repeat or something similar on two arrays

I recently started learning angular js. I have following object ResultRow:
ResultRow = function ( cars, prices) { //cars and prices are arrays
this.prices = prices;
this.cars = cars;
};
for example,
ResultsRow=new ResultsRow(["Ford","Honda","Nissan"],[20,22,18]);
I want to display the ResultsRow in table as:
Car Price
Ford 20
Honda 22
Nissan 18
So basically print car[0],price[0] in one table row, car[1], price[1] in next table row and so on.
I tried following using ng-repeat:
<tr>
<td ng-repeat="car in ctrl.ResultsRow.cars">
{{car}}
</td>
<td ng-repeat="car in ctrl.ResultsRow.prices">
{{price}}
</td>
</tr>
I am having a hard time figuring out how to display the exact format that I want. Any help will be appreciated.
P.S. It is quite complicated for me to change the format of ResultRow as I am generating it after quite a few data operations.
You should make an array of objects with properties to iterate in stead of trying to simultaneously iterate two separate arrays.
Example:
var row1 = {
car: "Ford",
price: 20
};
var row2 = {
car: "Honda",
price: 22
};
$scope.rows = [row1, row2];
Then in the template:
<tr ng-repeat="row in rows">
<td>{{row.car}}</td>
<td>{{row.price}}</td>
</tr>
if you want to use a multi-dimension array then I would suggest use new Array and if your car array and price array content is in order then you could do something like this;
vm.ResultsRow=new Array(["Ford","Honda","Nissan"],[20,22,18]);
<tr ng-repeat="car in vm.ResultsRow[0]">
<td>
{{car}}
</td>
<td>
{{vm.ResultsRow[1][$index]}}
</td>
</tr>
angular.module("app",[])
.controller("ctrl",function($scope){
var vm = this;
vm.ResultsRow=new Array(["Ford","Honda","Nissan"],[20,22,18]);
console.log(vm.ResultsRow)
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl as vm">
<table>
<tr ng-repeat="car in vm.ResultsRow[0]">
<td>
{{car}}
</td>
<td>
{{vm.ResultsRow[1][$index]}}
</td>
</tr>
</table>
</div>
change the function for mapping data, like this :
var repeatObject = new Array();
ResultRow = function ( cars, prices) { //cars and prices are arrays
if(cars.length == prices.length) {
for(let i in cars){
this.repeatObject.push({'cars' : cars[i], 'prices' : prices[i]});
}
}
};
So you can use 'ng-repeat' :
<tr ng-repeat="row in repeatObject">
<td>{{row.car}}</td>
<td>{{row.price}}</td>
</tr>

Push extenal data with data from server to array

I am currently working on a project in which, I am fetching the data from server and adding(pushing) in the array for further process. And its working great.
I face problem when I add an external data to the array with the data coming from server.
var quantity= ItemsValue[1];
$scope.product = [];
$http.get(___).success(function(data) {
$scope.greeting = data ;
$scope.product.push($scope.greeting);
}
I want to push "quantity" with the "$scope.greeting". I already tried different thing such as concatenation but failed.
I want data of array to be like this. For Example
$scope.product=[{ "greeting.name": "abc", "greeting.price": "50",
"quantity":"1" }]
Name and Price came from server and quantity in added as an extra data to Array.
<tbody >
<tr ng-repeat="greeting in product" ><!-- -->
<td class="cart_product">
<img src={{greeting.Image}} alt="book image" ></td>
<td class="cart_description">
<h4>{{greeting.Name}}</h4></td>
<td class="cart_price">
<p>Rs. {{greeting.Cost}}</p>
</td>
<td class="cart_quantity">
<div class="cart_quantity_button">
<a class="cart_quantity_up" href=""> + </a>
<input class="cart_quantity_input" type="text" name="quantity" value="Itemsquantity" autocomplete="off" size="2">
<a class="cart_quantity_down" href=""> - </a>
</div>
</td>
<td class="cart_total">
<p class="cart_total_price">Rs. {{greeting.Cost}}</p>
</td>
<td class="cart_delete">
<a class="cart_quantity_delete" ng-click="removeItem($index)"><i class="fa fa-times"></i></a>
</td>
</tr>
</tbody>
This is the code of client end.
Any way to push these things together...
You can just add it into the object by creating a new property
var quantity = ItemsValue[1];
$scope.product = [];
$http.get(___).success(function(data) {
$scope.greeting = data;
$scope.greeting.quantity = quantity;
$scope.product.push($scope.greeting);
}

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>

Ng-repeat doesn't works with localStorage

Something weird is happening with my localStorage. Data is saved in my localStorage but is never displayed in ng-repeat. However, when the data is less than 2 items, in that case, it is displayed on the screen. When it is greater than 2 it is not shown. This is my controller code
$scope.saved = localStorage.getItem('todos');
if($scope.saved != null ) {
$scope.invoice = { items: JSON.parse($scope.saved) };
} else {
$scope.invoice = { `enter code here`
items : [{
qty: 10,
description: 'item',
cost: 9.95}]
};
}
And this is my addition code:
$scope.addItem = function(x) {
$scope.invoice.push({
qty: 1,
description: x.cakename,
cost: x.id
});
localStorage.clear();
localStorage.setItem('todos', JSON.stringify($scope.invoice));
};
This is table code:
<div>
<table class="table" style="margin-top:50px">
<tr>
<th>Name</th>
<th>Qty</th>
<th>Cost</th>
<th>Total</th>
<th></th>
</tr>
<tr ng-repeat="item in invoice">
<th>#{{ item.description }}</th>
<th>#{{ item.qty }}</th>
<th>#{{ item.cost }}</th>
<th>#{{ item.qty * item.cost }}</th>
<td>[<a href ng:click="removeItem($index)">X</a>]</td>
</tr>
<tr>
<td></td>
<td></td>
<td>Total:</td>
<td> #{{ total() | currency }} </td>
</tr>
</table>
<a href="#/checkout" class="btn btn-danger">
Checkout <span class="glyphicon glyphicon-shopping-cart" aria-hidden="true"></span>
</a>
</div>
When there are only 2 items and I refresh, the screen shows,
Name Qty Cost Total
Black Forest 1 1 1 [X]
Berry 1 2 2 [X]
Total: $3.00
Checkout
otherwise, it shows
Name Qty Cost Total
Total: $7.00
As the total is 7.. The data is still there but not getting displayed.
You stringify the entire invoice object, but when you read it back from local storage, you parse it into an object with a different structure.
Try $scope.invoice = JSON.parse($scope.saved) instead.
I solved my question. It was because of the duplicate keys in the array. Duplicate keys are banned because AngularJS uses keys to associate DOM nodes with items. So adding this did the trick.
ng-repeat="item in invoice track by $index"
So stupid of me!

AngularJs sort object in ngRepeat

I'm using AngularJs and found a problem in ordering properties of a hash object in a template.
My object is like:
function TestCtrl($scope){
$scope.week = {'MONDAY': ['manuel'], 'TUESDAY': [], 'WEDNESDAY': ['valerio'], 'THURSDAY': ['manuel', 'valerio'], 'FRIDAY': []}
}
Now, when I try to print these values in my template:
<div ng-repeat="(day, names) in week">
<span>{{day}}</span>
<ul> <li ng-repeat="name in names">{{name}}</li> </ul>
</div>
The order of the days printed is different: FRIDAY MONDAY THURSDAY TUESDAY WEDNESDAY
I tried to apply the filter orderBy but I think it doesn't work with objects, but just with arrays...
How can I order it?
As per AngularJS docs (version 1.3.20):
You need to be aware that the JavaScript specification does not define
what order it will return the keys for an object. In order to have a
guaranteed deterministic order for the keys, Angular versions up to
and including 1.3 sort the keys alphabetically.
A workaround is to use an array of keys:
function TestCtrl($scope){
$scope.week = {
'MONDAY': ['manuel'], 'TUESDAY': [],
'WEDNESDAY': ['valerio'], 'THURSDAY': ['manuel', 'valerio'],
'FRIDAY': []}
$scope.weekDays = ["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY"];
}
Use the array in view for iteration:
<div ng-repeat="day in weekDays">
<span>{{day}}</span>
<ul> <li ng-repeat="name in week[day]">{{name}}</li> </ul>
</div>
Update from AngularJS version 1.4.6 docs:
Version 1.4 removed the alphabetic sorting. We now rely on the order
returned by the browser when running for key in myObj.
There is no way to order hash objects like that. Not just in angular but in javascript in general.
I would convert the hash object to an array of objects, something like that:
$scope.week = [{day: 'MONDAY', names: ['manuel']}, {day: 'TUESDAY', names: []} ...];
And then change the view to something like that:
<div ng-repeat="day in week|orderBy:'day'">
<span>{{day.day}}</span>
<ul> <li ng-repeat="name in day.names">{{name}}</li> </ul>
</div>
This was fixed in Angular 1.4. As stated in the official Angular documentation below:
Version 1.4 removed the alphabetic sorting. We now rely on the order
returned by the browser when running for key in myObj
https://docs.angularjs.org/api/ng/directive/ngRepeat
There is actually a simple solution ...
The object keys are not ordered by default BUT if you create the object in browser from scratch your browser WILL know the order ;)
Example:
// test-1
var data = {};
data['a'] = 10;
data['b'] = 5;
data['c'] = 2;
Object.keys(data); // ["a", "b", "c"]
// test-2
var data = {};
data['b'] = 5;
data['a'] = 10;
data['c'] = 2;
Object.keys(data); // ["b", "a", "c"]
So simply ... recreate the object ... or use this simple filter:
.filter('orderObject', function () {
return function (object, reverse) {
var keys = Object.keys(object || {}).sort();
if (reverse) keys.reverse();
for (var ordered = {}, i = 0; keys[i]; i++) {
ordered[keys[i]] = object[keys[i]];
}
return ordered;
}
})
Example with regular objects:
<!-- MARKUP : DEFAULT -->
<table>
<tr ng-repeat="(key, value) in data">
<td>{{key}}</td>
<td>{{value}}</td>
</tr>
</table>
<!-- RESULT : test-1 -->
<table>
<tr>
<td>a</td>
<td>10</td>
</tr>
<tr>
<td>b</td>
<td>5</td>
</tr>
<tr>
<td>c</td>
<td>2</td>
</tr>
</table>
<!-- RESULT : test-2 -->
<table>
<tr>
<td>b</td>
<td>5</td>
</tr>
<tr>
<td>a</td>
<td>10</td>
</tr>
<tr>
<td>c</td>
<td>2</td>
</tr>
</table>
Example with sorted objects:
<!-- MARKUP : with FILTER orderObject:<reverse?> -->
<table>
<tr ng-repeat="(key, value) in data | orderObject">
<td>{{key}}</td>
<td>{{value}}</td>
</tr>
</table>
<!-- RESULT : test-1 without reverse -->
<table>
<tr>
<td>a</td>
<td>10</td>
</tr>
<tr>
<td>b</td>
<td>5</td>
</tr>
<tr>
<td>c</td>
<td>2</td>
</tr>
</table>
<!-- RESULT : test-2 with reverse -->
<table>
<tr>
<td>c</td>
<td>2</td>
</tr>
<tr>
<td>b</td>
<td>5</td>
</tr>
<tr>
<td>a</td>
<td>10</td>
</tr>
</table>
This question is old, but I ended up coming up with an answer to this that I thought might be an improvement on some of the previous answers.
Rather than simply convert the object into an array, it's much more DRY to create an angular filter that does that for you, and then ngRepeat or ngOptions over that.
As an example:
angular.module('myproject')
.filter('objOrder', function () {
return function(object) {
var array = [];
angular.forEach(object, function (value, key) {
array.push({key: key, value: value});
});
return array;
};
});
Then, with an object like:
$scope.degrees: {
ASC: "Associate's",
BAS: "Bachelor's",
MAS: "Master's",
MD: "M.D.",
JD: "J.D.",
PHD: "Ph.D",
OTH: "Other"
}
We could use it like so:
<select
ng-model="myDegree"
required
ng-options="item.key as item.value for item in degrees | objOrder"
>
</select>
This way, you neither have to create a new array and pollute $scope, nor do you have to go back and change your actual degrees object, which could have unwanted side-effects.

Resources