ng-repeat orderBy suddenly stopped sorting - angularjs

The data being shown in the ng-repeat is acquired from a firebase db and as such is loaded asynchronously
this is the HTML:
<tr ng-animate="{enter: 'animate-enter', leave: 'animate-leave'}" ng-repeat="player in players|orderBy:'-Level'" class="neutral">
<td>{{$index+1}}</td>
<td>{{player.PlayerName}}</td>
<td>{{player.Wins}}</td>
<td>{{player.Losses}}</td>
<td>{{player.Level}}</td>
</tr>
And this is my controller:
app.controller 'RankController', ($scope, angularFire) ->
$scope.players;
ref = new Firebase("https://steamduck.firebaseio.com/players")
angularFire(ref, $scope, 'players')
What am I doing wrong? why is the list not being ordered by Level?
edit: Turns out this works perfectly if I use the model made by lukpaw. As such the problem must be in the data I receive which looks like this :

I think that your sorting is OK.
I did simple example and it works in your way. Maybe something which did not you placed is wrong in your code (first check JavaScript console).
HTML:
<!doctype html>
<html ng-app="App">
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.min.js"></script>
<script type="text/javascript" src="script.js"></script>
</head>
<body>
<div ng-controller="Ctrl">
<table border="1">
<tr ng-animate="{enter: 'animate-enter', leave: 'animate-leave'}" ng-repeat="player in players | orderBy:'-Level'">
<td>{{$index+1}}</td>
<td>{{player.PlayerName}}</td>
<td>{{player.Wins}}</td>
<td>{{player.Losses}}</td>
<td>{{player.Level}}</td>
</tr>
</table>
</div>
</body>
</html>
JavaScript:
angular.module('App', []);
function Ctrl($scope) {
$scope.players =
[{PlayerName:'John', Wins: 12, Losses:10, Level: 2},
{PlayerName:'Mary', Wins:7, Losses:19, Level: 1},
{PlayerName:'Mike', Wins:5, Losses:21, Level: 1},
{PlayerName:'Adam', Wins:9, Losses:35, Level: 3},
{PlayerName:'Julie', Wins:10, Losses:29, Level: 2}]
}
Plunker example

It would appear that the orderBy filter only knows how to sort an array. As such this would never work with JSON objects being used as the model.
I ended up implementing my own filter :
app.filter "orderObjectBy", ->
(input, attribute) ->
return input unless angular.isObject(input)
array = []
for key of input
array.push input[key ]
array.sort (a, b) ->
a = parseInt(a[attribute])
b = parseInt(b[attribute])
b - a
ng-repeat="player in players | orderObjectBy:'Level'"

How about ng-repeat="player in players|orderBy:Level:reverse"?

Inspired by #RonnieTroj answer, this filter reuses built-in orderBy filter and can handle both arrays and objects of any comparable type, not just integers:
app.filter 'orderCollectionBy', ['orderByFilter', (orderByFilter) ->
(input, attribute, reverse) ->
return unless angular.isObject input
if not angular.isArray input
input = (item for key, item of input)
orderByFilter input, attribute, reverse
]

Related

ng-repeat orderBy with double digit number

I have names such as:
Name - Page 9
Name - Page 2
Name - Page 6
Name - Page 15
Name - Page 1
Name - Page 12
Name - Page 14
Name - Page 13
Name - Page 10
Name - Page 11
Currently I am doing:
<div ng-repeat="data in results | orderBy:'-name'">
{{ data.name }}
</div>
But it doesn't order it with the numbers taken into consideration.
Does anyone know how I can make sure it is ordered according to the numbers?
<!DOCTYPE html>
<html>
<head>
<script src="angular.js"></script>
</head>
<body data-ng-app="app">
<div data-ng-controller="testC">
<div ng-repeat="data in results | orderBy:['name']">-{{data.name}}</div>
</div>
</body>
</html>
<script type="text/javascript">
var app = angular.module('app', []);
app.controller('testC', ['$scope', function($scope) {
$scope.results = [{'name':'Page 21'},
{'name':'Page 12'},
{'name':'Page 10'},
{'name':'Page 01'},
{'name':'Page 30'},
{'name':'Page 15'},
{'name':'Page 05'}];
}]);
</script>
it is working for me ,please try once .
https://jsfiddle.net/shivtumca12/nqbLLr4x/
and also
https://jsfiddle.net/shivtumca12/LxLu8v70/
It should work as expected. Check this plunker that I created.
Plunker Code
Edit: To handle double digits you can write a custom sort function like this:
$scope.myValueFunction = function(value) {
var numpart = value.name.replace( /^\D+/g, '');
return -(numpart-1000);
}
What this does is extract the number part and make it a sortable number. You can remove the preceding -ve sign to get ascending order.
Then in your html, you can call this function like this:
<tr ng-repeat="friend in friends | orderBy:myValueFunction">
<td>{{friend.name}}</td>
<td>{{friend.phone}}</td>
<td>{{friend.age}}</td>
</tr>

AngularJS ng-repeat dynamic columns

So I have a list of products; their columns change dynamically apart from two.
There will always be an id column and a name column.
How can I get ng-repeat to show the values of the other columns without knowing what they are until runtime?
I'm not sure what kind of layout you're looking for, but something like this basically takes all of the members of an object and throws them into an array that you can iterate with ng-repeat.
<html>
<head>
<link rel="stylesheet"
</head>
<body ng-app="app" ng-controller="myController as ctrl">
<table>
<tr ng-repeat="room in ctrl.rooms | filter: ctrl.expand">
<td ng-repeat="prop in room.props">
{{room[prop]}}
</td>
</tr>
</table>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.min.js "></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular-animate.min.js "></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular-aria.min.js "></script>
<script>
angular.module("app",[]).controller("myController", myController)
function myController() {
var vm = this
vm.rooms = [
{
name: 'Room 1',
floor: 1,
carpet: "shag"
},
{
name: 'Room 2',
doors: 2,
windows: 4
},
{
name: 'Room 3',
size: "12x12",
color: "green"
}
];
vm.expand = function(room) {
if (!room.props) {
room.props=[];
for (var prop in room) {
if (prop.indexOf("$") != 0 && prop !=="props") {
room.props.push(prop)
}
}
}
return true;
}
}
</script>
</body>
</html>
Without the function call, you can also use something like this:
<div ng-repeat="room in ctrl.rooms">
<div ng-repeat='(key, val) in room'>
<p>{{key}}: {{val}}</p>
</div>
</div>
Not entirely sure about what you're working with here but you could sift out data with a filter and ng-repeat.
Link to SO on that topic.

How to conditionally apply Angular filters on ng-repeat of object attributes?

I have an object with 100+ attributes, such as "name", "price", "expiry date"...etc
I am using ng-repeat to iterate through all the key-pair values of the object and displaying them on a table.
<table class="table">
<tr ng-repeat="x in attr_array">
<td><b>{{x.key}}</b></td>
<td>{{x.value}}</td>
</tr>
</table>
But I want to use the Angular date-filter on certain attributes, such as any date fields:
{{ x.value | date: 'MMM d, y'}}
And ideally other filters too. How can I go about doing this?
I tried to recreate your problem and solved it with ng-if.
There seems to be a function in the angular namespace to check every type like date, string, number, which I injected into the view through the scope.
Also notice I used the ng-repeat="(key, value) in ..." assuming that you are iterating over an object, source.
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body ng-app="app" ng-controller="RootController">
<table>
<tr ng-repeat="(key, value) in attr_array">
<td><b>{{key}}</b>
</td>
<td>
<span ng-if="isDate(value)">{{value | date: 'MMM d, y'}}</span>
<span ng-if="isNumber(value)">{{value | number: 4}}</span>
<span ng-if="isString(value)">{{value | uppercase}}</span>
</td>
</tr>
</table>
<script>
angular.module('app', [])
.controller('RootController', function($scope) {
$scope.isDate = angular.isDate;
$scope.isNumber = angular.isNumber;
$scope.isString = angular.isString;
$scope.attr_array = {
date: new Date(),
str: "hello",
nm: 50.2
};
});
</script>
</body>
</html>
This is no simple and elegant way to do it. You can't have dynamic filter so you choices are in prefered to less preferred order:
Preformat date fields dynamically in controller
Use ngIf/ngShow switches
Write custom filter that will apply another filter base on some configuration.
Here is an example of the custom filter approach:
angular.module('demo', [])
.controller('MainCtrl', function($scope) {
$scope.attr_array = [
{key: 'created', value: new Date, filter: ['date', 'MMM d, y']},
{key: 'name', value: 'Thomas Mann', filter: 'uppercase'},
{key: 'price', value: 1233.45, filter: 'number'},
{key: 'description', value: 'Some long string', filter: ['limitTo', 10] }
]
})
.filter('transform', function($filter) {
return function(value, filter) {
var filterData = [].concat(filter);
filterData.splice(1, 0, value);
return $filter(filterData.shift()).apply(this, filterData);
}
})
<script src="https://code.angularjs.org/1.4.9/angular.js"></script>
<table ng-app="demo" ng-controller="MainCtrl" class="table">
<tr ng-repeat="x in attr_array">
<td><b>{{ x.key }}</b>
</td>
<td>{{ x.value | transform:x.filter }}</td>
</tr>
</table>

AngularJS ng-click not firing controller method

I'm sure everyone has seen questions of a similar ilk, and trust me when I say I have read all of them in trying to find an answer. But alas without success. So here goes.
With the below code, why can I not get an alert?
I have an ASP.Net MVC4 Web API application with AngularJS thrown in. I have pared down the code as much as I can.
I know that my AngularJS setup is working correctly because on loading my view it correctly gets (via a Web API call) and displays data from the database into a table (the GetAllRisks function). Given that the Edit button is within the controller, I shouldn't have any scope issues.
NB: the dir-paginate directive and controls are taken from Michael Bromley's excellent post here.
I would appreciate any thoughts as my day has degenerated into banging my head against my desk.
Thanks,
Ash
module.js
var app = angular.module("OpenBoxExtraModule", ["angularUtils.directives.dirPagination"]);
service.js
app.service('OpenBoxExtraService', function ($http) {
//Get All Risks
this.getAllRisks = function () {
return $http.get("/api/RiskApi");
}});
controller.js
app.controller("RiskController", function ($scope, OpenBoxExtraService) {
//On load
GetAllRisks();
function GetAllRisks() {
var promiseGet = OpenBoxExtraService.getAllRisks();
promiseGet.then(function (pl) { $scope.Risks = pl.data },
function (errorPl) {
$log.error("Some error in getting risks.", errorPl);
});
}
$scope.ash = function () {
alert("Bananarama!");}
});
Index.cshtml
#{
Layout = null;
}
<!DOCTYPE html>
<html ng-app="OpenBoxExtraModule">
<head>
<title>Risks</title>
<link href="~/Content/bootstrap.min.css" rel="stylesheet">
<script type="text/javascript" src="~/Scripts/jquery-1.9.1.min.js"></script>
<script type="text/javascript" src="~/Scripts/bootstrap.min.js"></script>
<script type="text/javascript" src="~/Scripts/angular.js"></script>
<script type="text/javascript" src="~/Scripts/AngularJS/Pagination/dirPagination.js"></script>
<script type="text/javascript" src="~/Scripts/AngularJS/module.js"></script>
<script type="text/javascript" src="~/Scripts/AngularJS/service.js"></script>
<script type="text/javascript" src="~/Scripts/AngularJS/controller.js"></script>
</head>
<body>
<div ng-controller="RiskController">
<table>
<thead>
<tr>
<th>Risk ID</th>
<th>i3_n_omr</th>
<th>i3_n_2_uwdata_key</th>
<th>Risk Reference</th>
<th>Pure Facultative</th>
<th>Timestamp</th>
<th></th>
</tr>
</thead>
<tbody>
<tr dir-paginate="risk in Risks | itemsPerPage: 15">
<td><span>{{risk.RiskID}}</span></td>
<td><span>{{risk.i3_n_omr}}</span></td>
<td><span>{{risk.i3_n_2_uwdata_key}}</span></td>
<td><span>{{risk.RiskReference}}</span></td>
<td><span>{{risk.PureFacultative}}</span></td>
<td><span>{{risk.TimestampColumn}}</span></td>
<td><input type="button" id="Edit" value="Edit" ng-click="ash()"/></td>
</tr>
</tbody>
</table>
<div>
<div>
<dir-pagination-controls boundary-links="true" template-url="~/Scripts/AngularJS/Pagination/dirPagination.tpl.html"></dir-pagination-controls>
</div>
</div>
</div>
</body>
</html>
you cannot use ng-click attribute on input with angularjs : https://docs.angularjs.org/api/ng/directive/input.
use onFocus javascript event
<input type="text" onfocus="myFunction()">
or try to surround your input with div or span and add ng-click on it.
I've got the working demo of your app, code (one-pager) is enclosed below, but here is the outline:
removed everything concerning dirPagination directive, replaced by ngRepeat
removed $log and replaced by console.log
since I don't have a Web API endpoint, I just populated $scope.Risks with some items on a rejected promise
Try adjusting your solution to first two items (of course, you won't populate it with demo data on rejected promise)
<!doctype html>
<html lang="en" ng-app="OpenBoxExtraModule">
<head>
<meta charset="utf-8">
<title></title>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script>
var app = angular.module("OpenBoxExtraModule", []);
app.service('OpenBoxExtraService', function ($http) {
//Get All Risks
this.getAllRisks = function () {
return $http.get("/api/RiskApi");
}
});
app.controller("RiskController", function ($scope, OpenBoxExtraService) {
//On load
GetAllRisks();
function GetAllRisks() {
var promiseGet = OpenBoxExtraService.getAllRisks();
promiseGet.then(function (pl) { $scope.Risks = pl.data },
function (errorPl) {
console.log("Some error in getting risks.", errorPl);
$scope.Risks = [{RiskID: "1", i3_n_omr: "a", i3_n_2_uwdata_key: "b", RiskReference: "c", PureFacultative:"d", TimestampColumn: "e"}, {RiskID: "2", i3_n_omr: "a", i3_n_2_uwdata_key: "b", RiskReference: "c", PureFacultative:"d", TimestampColumn: "e"}, {RiskID: "3", i3_n_omr: "a", i3_n_2_uwdata_key: "b", RiskReference: "c", PureFacultative:"d", TimestampColumn: "e"} ];
});
}
$scope.ash = function () {
alert("Bananarama!");}
});
</script>
</head>
<body>
<div ng-controller="RiskController">
<table>
<thead>
<tr>
<th>Risk ID</th>
<th>i3_n_omr</th>
<th>i3_n_2_uwdata_key</th>
<th>Risk Reference</th>
<th>Pure Facultative</th>
<th>Timestamp</th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="risk in Risks">
<td><span>{{risk.RiskID}}</span></td>
<td><span>{{risk.i3_n_omr}}</span></td>
<td><span>{{risk.i3_n_2_uwdata_key}}</span></td>
<td><span>{{risk.RiskReference}}</span></td>
<td><span>{{risk.PureFacultative}}</span></td>
<td><span>{{risk.TimestampColumn}}</span></td>
<td><input type="button" id="Edit" value="Edit" ng-click="ash()"/></td>
</tr>
</tbody>
</table>
<div>
<div></div>
</div>
</div>
</body>
</html>
Thank you all for your help, particularly #FrailWords and #Dalibar. Unbelievably, this was an issue of caching old versions of the javascript files. Doh!
You can't directly use then on your service without resolving a promise inside it.
fiddle with fallback data
this.getAllRisks = function () {
var d = $q.defer();
$http.get('/api/RiskApi').then(function (data) {
d.resolve(data);
}, function (err) {
d.reject('no_data');
});
return d.promise;
}
This will also fix your problem with getting alert to work.

Error: [ngRepeat:dupes] on a seemingly valid object [duplicate]

I am defining a custom filter like so:
<div class="idea item" ng-repeat="item in items" isoatom>
<div class="section comment clearfix" ng-repeat="comment in item.comments | range:1:2">
....
</div>
</div>
As you can see the ng-repeat where the filter is being used is nested within another ng-repeat
The filter is defined like this:
myapp.filter('range', function() {
return function(input, min, max) {
min = parseInt(min); //Make string input int
max = parseInt(max);
for (var i=min; i<max; i++)
input.push(i);
return input;
};
});
I'm getting:
Error: Duplicates in a repeater are not allowed. Repeater: comment in item.comments | range:1:2 ngRepeatAction#https://ajax.googleapis.com/ajax/libs/angularjs/1.1.4/an
The solution is actually described here: http://www.anujgakhar.com/2013/06/15/duplicates-in-a-repeater-are-not-allowed-in-angularjs/
AngularJS does not allow duplicates in a ng-repeat directive. This means if you are trying to do the following, you will get an error.
// This code throws the error "Duplicates in a repeater are not allowed.
// Repeater: row in [1,1,1] key: number:1"
<div ng-repeat="row in [1,1,1]">
However, changing the above code slightly to define an index to determine uniqueness as below will get it working again.
// This will work
<div ng-repeat="row in [1,1,1] track by $index">
Official docs are here: https://docs.angularjs.org/error/ngRepeat/dupes
For those who expect JSON and still getting the same error, make sure that you parse your data:
$scope.customers = JSON.parse(data)
I was having an issue in my project where I was using ng-repeat track by $index but the products were not getting reflecting when data comes from database. My code is as below:
<div ng-repeat="product in productList.productList track by $index">
<product info="product"></product>
</div>
In the above code, product is a separate directive to display the product.But i came to know that $index causes issue when we pass data out from the scope. So the data losses and DOM can not be updated.
I found the solution by using product.id as a key in ng-repeat like below:
<div ng-repeat="product in productList.productList track by product.id">
<product info="product"></product>
</div>
But the above code again fails and throws the below error when more than one product comes with same id:
angular.js:11706 Error: [ngRepeat:dupes] Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater
So finally i solved the problem by making dynamic unique key of ng-repeat like below:
<div ng-repeat="product in productList.productList track by (product.id + $index)">
<product info="product"></product>
</div>
This solved my problem and hope this will help you in future.
What do you intend your "range" filter to do?
Here's a working sample of what I think you're trying to do: http://jsfiddle.net/evictor/hz4Ep/
HTML:
<div ng-app="manyminds" ng-controller="MainCtrl">
<div class="idea item" ng-repeat="item in items" isoatom>
Item {{$index}}
<div class="section comment clearfix" ng-repeat="comment in item.comments | range:1:2">
Comment {{$index}}
{{comment}}
</div>
</div>
</div>
JS:
angular.module('manyminds', [], function() {}).filter('range', function() {
return function(input, min, max) {
var range = [];
min = parseInt(min); //Make string input int
max = parseInt(max);
for (var i=min; i<=max; i++)
input[i] && range.push(input[i]);
return range;
};
});
function MainCtrl($scope)
{
$scope.items = [
{
comments: [
'comment 0 in item 0',
'comment 1 in item 0'
]
},
{
comments: [
'comment 0 in item 1',
'comment 1 in item 1',
'comment 2 in item 1',
'comment 3 in item 1'
]
}
];
}
If by chance this error happens when working with SharePoint 2010: Rename your .json file extensions and be sure to update your restService path. No additional "track by $index" was required.
Luckily I was forwarded this link to this rationale:
.json becomes an important file type in SP2010. SP2010 includes certains webservice endpoints. The location of these files is 14hive\isapi folder. The extension of these files are .json. That is the reason it gives such a error.
"cares only that the contents of a json file is json - not its file extension"
Once the file extensions are changed, should be all set.
Just in case this happens to someone else, I'm documenting this here, I was getting this error because I mistakenly set the ng-model the same as the ng-repeat array:
<select ng-model="list_views">
<option ng-selected="{{view == config.list_view}}"
ng-repeat="view in list_views"
value="{{view}}">
{{view}}
</option>
</select>
Instead of:
<select ng-model="config.list_view">
<option ng-selected="{{view == config.list_view}}"
ng-repeat="view in list_views"
value="{{view}}">
{{view}}
</option>
</select>
I checked the array and didn't have any duplicates, just double check your variables.
Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys.
Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}
Example
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title></title>
<script src="angular.js"></script>
</head>
<body>
<div ng-app="myApp" ng-controller="personController">
<table>
<tr> <th>First Name</th> <th>Last Name</th> </tr>
<tr ng-repeat="person in people track by $index">
<td>{{person.firstName}}</td>
<td>{{person.lastName}}</td>
<td><input type="button" value="Select" ng-click="showDetails($index)" /></td>
</tr>
</table> <hr />
<table>
<tr ng-repeat="person1 in items track by $index">
<td>{{person1.firstName}}</td>
<td>{{person1.lastName}}</td>
</tr>
</table>
<span> {{sayHello()}}</span>
</div>
<script> var myApp = angular.module("myApp", []);
myApp.controller("personController", ['$scope', function ($scope)
{
$scope.people = [{ firstName: "F1", lastName: "L1" },
{ firstName: "F2", lastName: "L2" },
{ firstName: "F3", lastName: "L3" },
{ firstName: "F4", lastName: "L4" },
{ firstName: "F5", lastName: "L5" }]
$scope.items = [];
$scope.selectedPerson = $scope.people[0];
$scope.showDetails = function (ind)
{
$scope.selectedPerson = $scope.people[ind];
$scope.items.push($scope.selectedPerson);
}
$scope.sayHello = function ()
{
return $scope.items.firstName;
}
}]) </script>
</body>
</html>
If you call a ng-repeat within a < ul> tag, you may be able to allow duplicates. See this link for reference.
See Todo2.html
Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: sdetail in mydt, Duplicate key: string: , Duplicate value:
I faced this error because i had written wrong database name in my php api part......
So this error may also occurs when you are fetching the data from database base, whose name is written incorrect by you.
My JSON response was like this:
{
"items": [
{
"index": 1, "name": "Samantha", "rarity": "Scarborough","email": "maureen#sykes.mk"
},{
"index": 2, "name": "Amanda", "rarity": "Vick", "email": "jessica#livingston.mv"
}
]
}
So, I used ng-repeat = "item in variables.items" to display it.

Resources