Sort an object in angularjs ng-repeat directive - javascript

I have an object
Object {1: "CMAR0002", 2: "CMAR0003", 4: "CMAR0001"}
I am using this object in ng-repeat directive but i want to sort it by values. But the problem is that angular orderBy filter requires array of objects. But i have only pretty simple object here ;)
Thanks in advance

By Angular ng-repeat documentation:
It is possible to get ngRepeat to iterate over the properties...
However, there are a limitations compared to array iteration, like "The built-in filters orderBy and filter do not work with objects, and will throw if used with one"
If you are hitting any of these limitations, the recommended workaround is to convert your object into an array that is sorted into the order that you prefer before providing it to ngRepeat. You could do this with a filter such as toArrayFilter or implement a $watch on the object yourself.
Anyway, you can try to implement you own filter for this, but it will be pretty ugly and it will require much effort (probably for nothing).
Hope, it will help you!

Do this:
<li ng-repeat="(key, value) in Object | orderBy: key">
{{ key }} - {{ value }}
</li>
Fiddle: http://jsfiddle.net/oo4cb9xf/1/

Related

How to loop over object properties with ngFor in Angular app

I can write data by keyValuePipe or {{data | JSON}} but I think that is not good solution for me. How I can write data differently? How I use for this example Object.entries, Object.keys or Object.value?
Code with JSON in Stackblitz: https://stackblitz.com/edit/angular-ivy-jn8snh?file=src/app/app.component.ts
Your object is considered as a dictionary in javascript.
You can loop over properties of your object as follow:
<div *ngFor="let property of data">
<p>{{ property }} - {{ data[property] }}</p>
</div>
You can't to do that out of the box. NgForOf allows iterating over a collection, and by default object does not implement required methods and properties. In theory you could get the keys of the object by writing something like this:
<p *ngFor="let property of Object.keys(object)">
{{property}} {{object[property]}}
</p>
But the way the contents of ngFor are evaluated, the global variable Object is not present in the scope. So you're best off writing a custom pipe that would do the work for you by transforming your object into iterable of your choice that is suitable for your requirements (not idea what that would be since the only information is that "keyValuePipe or {{data | JSON}} is not good solution for me" without any reasoning).

Keeping insertion order in Angular

I have a HTML fragment that iterates over key, value collection. When I create an object and put some value in, then iterate trough that object via HTML fragment, all works perfectly.
However since I need keys in specific order, I'm using a Map instead of plain object. This time when I debug I can see that my insertion order was preserved, but for some reason the HTML fragment which iterates over collection doesn't seem to know how to do so. I see nothing on my screen when I use the map object, opposed to the regular object when I see unordered content
tr ng-repeat="(key, value) in rowTitlesValues"
Is how my HTML fragment looks like, when I switch rowTitlesValues back to object works again, what am I doing wrong, and how does one keep insertion order or how do I sort object so it's keys are in custom order?
From Angular reference on ng-repeat (link):
Iterating over object properties
It is possible to get ngRepeat to iterate over the properties of an object using the following syntax:
<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.)
Version 1.4 removed the alphabetic sorting. We now rely on the order returned by the browser when running for key in myObj. It seems that browsers generally follow the strategy of providing keys in the order in which they were defined, [...]
If this is not desired, the recommended workaround is to convert your object into an array that is sorted into the order that you prefer before providing it to ngRepeat. You could do this with a filter such as toArrayFilter or implement a $watch on the object yourself.
Additionally, I do not think Angular 1.x knows how to iterate over a Map. I believe this line in the code proves it:
collectionKeys = [];
for (var itemKey in collection) { // iterates your object using `in`, not `of` or `Map.forEach()`
...
}
// ng-repeat then iterates the collectionKeys to create the DOM
So you will probably need to act as Angular docs suggest:
[...] convert your object into an array that is sorted into the order that you prefer before providing it to ngRepeat. You could do this with a filter such as toArrayFilter or implement a $watch on the object yourself.

ng-repeat track by is not working: slow and still generating $$hashKey

I have an ng-repeat set up like so:
ng-repeat="article in main[main.mode].primary | orderBy: main[main.mode].primary.filter.order
track by article.url"
main[main.mode].primary is an array and ….filter.order is a string.
According to this blog post
Behind the scenes ngRepeat adds a $$hashKey property to each task to keep track of it. If you replace the original tasks with new tasks objects from the server, even if those are in fact totally identical to your original tasks, they won’t have the $$hashKey property and so ngRepeat won’t know they represent the same elements.
Regenerating the list is a very common task and the app is hanging for over a second, hence my interest in track by. According to the many questions and docs I've looked at, I've used the correct syntax to both order and track an array. From the docs:
item in items | filter:searchText track by item.id is a pattern that might be used to apply a filter to items in conjunction with a tracking expression.
Why isn't track by being implemented? I'm running angular 1.3.11.
Edit
It doesn't even work if I remove the orderBy argument
ng-repeat="article in main[main.mode].primary track by article.url"
according to the Angular Documentation orderBy only works on arrays so if you are iterating over an object you are not going to be able to use it unless you convert your object into an array
https://docs.angularjs.org/api/ng/filter/orderBy
there are other ways you can handle this either implementing your own filter or just by transforming your object into an array of objects with key value properties. something like
var narr=[]
angular.forEach(object,function(k,v){
narr.push({key:v,value:v})
})
and now narr is an array ready to be sorted by either key or value using orderBy

Filter ng-repeat table with data coming from directives Angularjs

I have an array of object that I use in a ng-repeat loop with an attribute referring to an another object by it's id.
{Description: 'something', OtherObjectId: 1}...
I'm using a directive to display information about the OtherObject in that ng-repeat loop.
<li ng-repeat="object in objects">
<div>{{object.Description}}</div>
<div other-object-info="date" other-object-id="{{object.OtherObjectId}}"></div>
</li>
The otherObjectInfo directive use innerHtml to put the requested info in that html tag
Now is the part that doesn't obviously work, I need to be able to filter that list with all the displayed informations, including the one inserted by the directives.
How would you resolve it? I guess the best way would be to include the OtherObject inside the objects array before rendering the view, but I like the simple way of using that directive because I need it in multiple views.
Thank you very much for your output!
Consider a custom filter for the ng-repeat. You would keep the other objects separate from the array and pass whatever you need to the custom filter function:
<li ng-repeat="object in objects | filter:customFunct(otherObjectCollection)">
$scope.customFunct = function(otherCollection) {
return function(object) {
// locate the target object within otherCollection using object.OtherObjectId
// eg you can use this if you're using lodash:
var targetObj = _.find(otherCollection, {id : object.OtherObjectId});
// make the comparison you need
// use 'return' to return true based on the comparison
return targetObj.value > 40;
}
}
In this way, you can define and use a custom filter with a passed argument.
You can read more discussion on this solution here:
https://stackoverflow.com/a/17813797/1220172
https://stackoverflow.com/a/17811582/1220172

Pass parent scope value into ng-repeat loop in Angular

This should be an extremely simple question, but all of the workarounds I've found are complex. I'm looping through an array of objects in using ng-repeat in a template as follows:
<div class="row-fluid" ng-repeat="message in messages.current|filter:'draft'">
{{ message.subject }} ... {{ campaign.name }} ...
</div>
Since the ng-repeat creates a new scope, the 'campaign' object from the controller doesn't seem to be accessable. Is there any way (aside from adding the campaign object to every item in my array) of getting that value?
Thanks in advance.
You can access the parent scope by using $parent
<div class="row-fluid" ng-repeat="message in messages.current|filter:'draft'">
{{ message.subject }} ... {{ $parent.campaign.name }} ...
</div>
This is a way that works that doesn't use $parent. It searches upwards through the nested scopes to find the object you're using, however many scopes it has to go through.
In the scope that contains the list, you can define an object with the list as a property, like this:
$scope.obj = {};
$scope.obj.items = ['item1','item2','item3'];
Then have the ng-repeat look like this:
<div ng-repeat="item in obj.items | filter:'item3' track by $index">
{{obj.items[ obj.items.indexOf(item) ]}}
</div>
(you need to use obj.items[ obj.items.indexOf(item) ] rather than obj.items[ $index ] because $index is the index of the filtered array, not the original)
The reason this works is because while obj doesn't exist in the current scope, as you attempt to access its property, Angular will look above the current scope rather than give you an error (if you just tried {{obj}} it would be undefined, and Angular would be happy with giving you nothing instead of looking through higher scopes). This is a helpful link about nested scopes: http://www.angularjshub.com/examples/basics/nestedcontrollers/
In my case I needed the track by $index, because I had an input with ng-model bound to an item in the array, and whenever the model updated, the input would blur because I think the HTML was being re-rendered. A consequence of using track by $index is that items in the array with identical values will be repeated. If you modify one of those other than the 1st one, weird things will happen. Maybe you can filter for uniqueness to avoid that.
I'm relatively new to AngularJS, so please comment if there is anything big I'm missing. But this works, so I'm using it at least.
Another method might be to pass parent scope as a scope variable to the directive i.e.
<my-directive
md-parent-scope="this"
ng-repeat="item in items"></my-directive>
It's a bit messy, but you have more control over what the parent actually is and can pass anything in.

Categories