Search code examples
ecmascript-6aureliaes6-map

Using a value convertor on an ES 6 Map in Aurelia


I'm trying to sort a Map using a value convertor on a repeat-binding in Aurelia:

Original code:

 <div repeat.for="[id, obj] of myMap">
     ${obj.SomeProperty}
 </div>

This works as expected, the values of the map are displayed and when I add something to the map, it gets added as well.

I then created the following value convertor:

export class SortValueConverter {
    toView(map, propertyName, direction) {
        const factor = direction === 'desc' ? -1 : 1;
        return new Map([...map.entries()].sort((a, b) => (a[propertyName] - b[propertyName]) * factor));
    }
}

<div repeat.for="[id, obj] of myMap | sort:'SomeProperty':'desc'">
     ${obj.SomeProperty}
 </div>

The values are getting passed in correctly to the value convertor and the return value is indeed a new map. The repeat binding is broken however, nothing is displayed in the UI.

I tried different approaches as well:

  1. Converting the map into an array before passing it into the value convertor and then using a regular array value convertor:

    export class SortValueConverter {
        toView(array, propertyName, direction) {
            const factor = direction === 'desc' ? -1 : 1;
            return array.sort((a, b) => (a[propertyName] - b[propertyName]) * factor));
        }
    }
    
    <div repeat.for="obj of Array.from(myMap.values()) | sort:'SomeProperty':'desc'">
         ${obj.SomeProperty}
    </div>
    
    // or alternatively:
    
    <div repeat.for="obj of [...myMap.values()] | sort:'SomeProperty':'desc'">
         ${obj.SomeProperty}
    </div>
    

This gives me an undefined as the first parameter in the value convertor.

  1. Accept a Map as the first parameter of the value convertor, but returning an Array:

    export class SortValueConverter {
        toView(map, propertyName, direction) {
            const factor = direction === 'desc' ? -1 : 1;
            return [...map.values()].sort((a, b) => (a[propertyName] - b[propertyName]) * factor));
        }
    }
    
    <div repeat.for="obj of myMap | sort:'SomeProperty':'desc'">
         ${obj.SomeProperty}
    </div>
    

The result is the same as in my first example: the array is returned correctly, but nothing is displayed.

How do you create a value convertor to sort a Map on a repeat-binding?


Solution

  • Your issue was the way you are handling the Map object.

    When you use 'map.entries()' to create an array, you get tuples, where position 0 is the key and position 1 is the object. You were using the tuple AS the object.

    You did:

    [...map.entries()].sort((a, b) => (a[propertyName] - b[propertyName]) * factor)

    Should have done:

    [...map.entries()].sort((a, b) => (a[1][propertyName] - b[1][propertyName]) * factor)

    There are other ways to do this, but this is the closest one to your code (and the easiest fix to apply).

    See this in action: https://gist.run/?id=caa652ef4fbc54e16df2e853784ffa4b

    Even though this still doesn't really explain why you weren't getting anything displayed (mine displayed, but didn't sort properly). Maybe you'll find the issue using the example provided (it is the bare minimum required for everything to work, so do some quick comparisons, good luck!).