3

i'm having trouble sorting and array that has a trackBy function. The use case is as follows:

I have an item array. All these items have a z-index property. I also have a layer manager that can edit the z-index of each item. When I want to save my items, I want to sort my array according to the z-index property of each item. I also have a trackBy on my ngFor to limit the amount of change detection on the *ngFor.

With a trackBy function on my *ngFor, the items switch places when I sort the array (which is bad). If I remove the trackBy function this doesn't happen, and the items are sorted according to z-index.

The *ngFor:

<div class="item" *ngFor="let item of items; let i = index;trackBy:getUniqueIdentifier">

The trackBy function:

getUniqueIdentifier(index, item) {
    return this.items.length + ', ' + this.languages._previewLanguage;
}

As you can see, the *ngFor is changed when the length of the items changes or if I switch my language settings. I've tried adding + ', ' + item.zIndex but this doesn't work.

The sorting function:

sortItemsArrayByZIndex() {
    this.items.sort((i1, i2) => {
        return (i1.zIndex - i2.zIndex);
    });
}

So it seems that trackBy and this sort function are in conflict for some reason. The result now is that when i use the sort function the the items array gets sorted according to z-index but the items also switch places, which means that their dimensions properties are switched for some reason.

What i want is that the items array is sorted according to z-index but that the items keep their dimensions.

How do i need to change my trackBy function or my sort function so that i get the result that i want?

Martijn van den Bergh
  • 1,434
  • 1
  • 20
  • 40
  • Why would you use a trackBy function there ? You're iterating over non-primitive values, and you're returning a value that can not be unique. What is your purpose with this track by ? –  Jan 24 '18 at 08:38
  • As i understand it trackBy can be used to determine when ngFor needs to do a change detection i.e. being rebuilt. This piece of code is in a prety big project and for performace reasons i dont want the ngFor to do a change detection every time something changes, so thats why i have the trackBy. But maybe my understanding of trackBy is wrong? – Martijn van den Bergh Jan 24 '18 at 08:41
  • No it's right, but in your case, if two of the items have the same language, then they have the same track by value. This can be very problematic, and it even may be where your problem comes from. –  Jan 24 '18 at 08:43
  • Well if the language changes, every item needs a change detection, so I don't really see why that would be a problem? Or is trackBy just for individual item change detection in a collection? – Martijn van den Bergh Jan 24 '18 at 08:49
  • 1
    If the language changes, every item will be concerned, but if any of the other fields (or their index) change, no item will be concerned. That's why your sorting may not be working : since the item indexes are changing and you're not tracking it, then the change detection isn't happening. This is an assumption, but you should probably test it though :) –  Jan 24 '18 at 08:52
  • Thanks, will look into it. – Martijn van den Bergh Jan 24 '18 at 08:55
  • 1
    SImply add the index into the return statement of the track by, this should do the trick ! –  Jan 24 '18 at 08:56
  • Added this as an answer, this is working. Thanks! – Martijn van den Bergh Jan 24 '18 at 09:18
  • 1
    Well glad I could help ! –  Jan 24 '18 at 09:19

2 Answers2

8

If you don't mind, I'll add my own answer to explain why your code wasn't working, since you only provided a working code with no explanation.

The trackBy function

The trackBy function is made to improve performances, and to avoid unecessary change detection. You can provide a custom function, as you did, and it will create a value that will be checked to see if the change detection need to be triggered.

The issue

Your trackBy function was this :

getUniqueIdentifier(index, item) {
  return this.items.length + ', ' + this.languages._previewLanguage;
}

This will return a value of this type :

90, en-US

Where 90 won't change unless you push/withdraw items from your array, and where en-US can be applied to several items of the array. This means that if you change either the index or another field, the change detetction won't happen.

The solution

Since you want to sort your array, you have to at least add the index to the return of your custom function. This means that if the index of the item changes (and since you sort your array, it will), the change detection will occur.

The minial code would be :

getUniqueIdentifier(index, item) {
  return index + ', ' + this.languages._previewLanguage + ', ' + this.items.length;
}
Community
  • 1
  • 1
  • 1
    Well thank you, but it was more of an explanation concern ! People coming to SOF usually look at the answers, not the comments ... –  Jan 24 '18 at 10:49
1

As user @trichetriche mentioned i needed to add the index, and also the zIndex property to the trackBy to let the TrackBy work as intended.

The trackBy function looks something like this:

getUniqueIdentifier(index, item) {
    return index + ', ' + item.zIndex + ', ' + this.languages._previewLanguage + ', ' + this.items.length;
}
Martijn van den Bergh
  • 1,434
  • 1
  • 20
  • 40