-
Notifications
You must be signed in to change notification settings - Fork 27.4k
orderBy should work for ngRepeat over objects too #1286
Comments
I'm not convinced that you should be able to sort objects with a filter; if the order of some collection of models is important, it seems like you would want to use an array, or augment your model with an array with references to the items in the collection. Can you provide some real-world example of why this would be desirable? |
Here's the real world example. My model is a bunch of jobs to process. A job looks like this: Job model
{
state: // one of ['queued', 'processing', 'complete']
type: // one of ['audio', 'image']
id: // some unique string
queue_date: // date that the job was queued
}
jobs_by_id model
{
<job id>: <job>
}
jobs_by_category model
{
queued: {
audio: {
<job id>: <job>
}
image: {
<job id>: <job>
}
}
processing: {
audio: {
<job id>: <job>
}
image: {
<job id>: <job>
}
}
complete: {
audio: {
<job id>: <job>
}
image: {
<job id>: <job>
}
} The view looks something like this: <h2>Jobs</h2>
<h3>Queued</h3>
<div job-table="queued"></div>
<h3>Processing</h3>
<div job-table="processing"></div>
<h3>Complete</h3>
<div job-table="complete"></div> The main.directive('jobTable', function(){
return {
templateUrl: '/views/job-table.html',
scope: true,
link: function(scope, element, attrs){
scope.state = attrs.jobTable;
scope.state_jobs = scope.jobs_by_category[scope.state];
}
};
}); The job-table.html view looks something like this: <h4>audio</h4>
<table>
<tr>
<th>date job was queued</th>
(a bunch more <th>'s related to audio)
</tr>
<tr ng-repeat="job in state_jobs.audio">
<td>{{job.queue_date}}</td>
(a bunch more <td>'s related to audio)
</tr>
</table>
<h4>image</h4>
<table>
<tr>
<th>date job was queued</th>
(a bunch more <th>'s related to image)
</tr>
<tr ng-repeat="job in state_jobs.image">
<td>{{job.queue_date}}</td>
(a bunch more <td>'s related to image)
</tr>
</table> I need the job function insertJobIntoCat(job){
var ref$, key$;
((ref$ = $scope.jobs_by_cat[job.state])[key$ = job.type] || (ref$[key$] = {}))[job.id] = job;
}
function newJob(job){
jobs_by_id[job.id] = job;
insertJobIntoCat(job);
}
function updateJob(job){
var old_job, ref$, key$;
old_job = jobs_by_id[job.id];
if (old_job.state === job.state && old_job.type === job.type) {
import$(old_job, job);
} else {
delete ((ref$ = $scope.jobs_by_cat[old_job.state])[key$ = old_job.type] || (ref$[key$] = {}))[old_job.id];
insertJobIntoCat(import$(old_job, job));
}
}
function import$(obj, src){
var own = {}.hasOwnProperty;
for (var key in src) if (own.call(src, key)) obj[key] = src[key];
return obj;
} So as you can see, if I wanted to switch to using an array in the The |
I would also appreciate this feature. |
@superjoe30, thanks for the extensive example! I've been busy the past week, but I'm still looking over this and thinking about it. I like the semantics, but implementation would be complicated, and I think it's more semantic to use an array when order matters. Complexity analysis for this isn't as straightforward as what you've suggested; although you can look at the insertions/deletions in isolation, the reality of the situation is that you also have to do dirty checking and updating of the DOM based on the changing data structure, and those operations are likely to be much more expensive, perhaps to the extent that O(N) insertion/deletion is negligible. We'd need to benchmark this to get a real answer. |
I third the request. Its really inefficient and risks denormalization to shift objects into arrays just to sort them in some view |
Made a few small changes to the orderByFilter code to allow objects, instead of just arrays: |
Me too...I simply render JSON after a database query from MongoDB. That returns objects. Even if it didn't, the classes within the framework I'm using returns objects when converting to JSON because the document or record class is managing the data model. In many, many cases this will be true across other web applications and APIs. I really think this is a deficiency with JSON "objects" that can be resolved by Angular. Ensuring these objects are processed in the "proper" order has always been an issue. So people make methods just for this purpose or output two hacky arrays sometimes or just always output arrays in general. It's very convenient to use JSON objects. I don't know, blame the JSON spec for how we got here...But we're here. |
Very interested in this, already added this in my version. Could somebody add this? |
+1 |
3 similar comments
+1 |
+1 |
+1 |
The real world example I gave above is now open source. |
+1 |
7 similar comments
+1 |
👍 |
+1 |
+1 |
+1 |
+1 |
+1 |
The solution by @brev works great if you don't mind modifying angular and don't care about your keys, since they get replaced by indexes. However, I wanted to have access to my keys while also sorting and searching. I also did not want to create an array version of my object on my scope that I had to keep in sync during changes. As @btford mentioned it is not trivial to do, the orderBy filter must return an array since there is no way to "order" an object's properties. This means that for this to "just work", ngRepeat would have to recognize a special kind of array (that saves key information) as derived from an object and then bind saved keys to the (key,value) paring instead of indexes to (index,value) as would normally occur with arrays. In order to avoid making changes to angular, a simple solution is to use another filter. This filter converts a general object to an array and saves the key away for later use as a non-enumerable property. The filter (not perf tuned and using underscore): app.filter('toArray', function() { return function(obj) {
if (!(obj instanceof Object)) return obj;
return _.map(obj, function(val, key) {
return Object.defineProperty(val, '$key', {__proto__: null, value: key});
});
}}); Example markup: <div ng-repeat="val in object | toArray | orderBy:'priority' | filter:fieldSearch">
{{val.$key}} : {{val}}
</div> Potential downsides:
Integrating directly into ngRepeat and orderBy/filter could address these issues but they didn't impact my project. |
I didn't quite understand how to make your filter "toArray" work in IE8, can you explain? |
Object.defineProperty creates a property that is not enumerable. See http://kangax.github.com/es5-compat-table/ for a note on IE8 compatibility where it states that Object.defineProperty only works on DOM objects in IE8. A simple change is to replace |
+1, and @slundberg it's a great idea just temporarily converting object to an array, thanks. |
+1 |
3 similar comments
+1 |
+1 |
+1 |
+1 |
3 similar comments
+1 |
+1 |
+1 |
Because at the moment sorting(orderBy property) object is not well supported. angular/angular.js#1286
+1 |
12 similar comments
+1 |
+1 |
+1 |
+1 |
+1 |
+1 |
+1 |
+1 |
+1 |
+1 |
+1 |
+1 |
How to sort date using this? |
I'm currently developing a real-time browser game with AngularJS, in which I have a room list. To update individual entries in this list, the server sends a notification with the new version of this single entry when it changes. Thus, a single entry can be by ID, provided by the server, updated, I need to use an object instead of an array. I would like to sort these entries with AngularJS, which, however, currently not working. I would be happy if this feature will be implemented soon. Otherwise, I had to write such a function myself, which I however come back in the boilerplate code hell. |
Here is a hard-coded solution to the OP: http://jsfiddle.net/q48Vt/ Here is a version that uses toArray but actually modifies the original object (and will work on IE8): |
I have created a module that provides the |
+1 |
2 similar comments
+1 |
+1 |
The 'toArray' filter provided above is a workable solution to this problem. Without completely rewriting how ngRepeat works, which is outside of the scope for 1.x, it is not feasible to solve this in any better way. |
http://jsfiddle.net/m7ynD/
The dates are not sorted, but they should be.
The text was updated successfully, but these errors were encountered: