Updating a Map doesn't trigger binding updates

Hi!
I’m using a Map to store some metadata. The elements get updated on user interaction. My template displays some of this metadata. However updating the data won’t be reflected in the template.

Template

<template>
<div>
    ${myMap.get('foo')}
    <a click.trigger="update()">Update</a>
</div>
</template>

ViewModel

class SampleCustomElement {
    myMap: Map<string, string> = new Map();
    update() {
        this.myMap.set('foo', 'bar');
    }
}

I found out that ModifyMapObserver would patch the Map to inject observable behaviour but somehow it doesn’t work.

Did anyone have tried something like that already?

Thanks!

myMap.get('foo') is a function call and won’t be observed for changes.

If you don’t mind dirty checking, then you can use a get-property in your view-model

get foo() {
  return myMap.get('foo');
}

I’ve found it better to just change my pattern to immutable collections in my components. This is much cleaner and the binding just works. Try creating a new map object in the update() method.

If the map is bound (input) to your element, then the update method in SampleCustomElement should call a method (output) in the parent component to set the item. The parent component would own all updates to map and a new map is created whenever needed.

Over the past year, I’ve realized that this pattern of inputs and outputs makes for clean code and reliable binding.

1 Like

Thanks for your responses! I’m going with @swalters suggestion and using immutable collections. This works flawlessly.

I actually just opened up an issue about this here: https://github.com/aurelia/binding/issues/642

Turns out what I did to work around it was to iterate over the map and select the item that matches the right key.

<div repeat.for="key, value of map" if.bind="key === 'foo'></div>

I think we all agree this isn’t good. However, I did this way back in the alpha days of Aurelia and never had the problem again. I think it would be great to add this to the binding engine, but it’s just a super low priority for most developers at the moment.

@davismj to observe a Map, does aurelia just have to intercept calls to set?

It already does: https://github.com/aurelia/binding/blob/master/src/map-observation.js.

The problem is that it handles these changes the same way it handles array mutations. If you push to an array, the first n items don’t have their bindings refreshed. Only iterable bindings (repeat.for) do. This is why the above strategy works.

<div repeat.for="key, value of map" if.bind="key === 'foo'></div>

@davismj using 2 template controller on the same elements may not work well on IE

It’s time to make Map great again, then.