So my situation is as follows: I got a component with a couple of input fields that represent a contact and are filled with data from a service:
@service('contact-data') contact;
Each field stands for one property that is accessed via
{{contact.properties.foo}}
I have the properties saved as a JS object to easily filter out empty fields when using them and I tracked it with @tracked
like so:
export default class MyService extends Service {
@tracked properties = {id: 0, foo: "", bar : "abc", };
@action updateProperty(name, value) {
this.properties[name] = value;
}
}
However, the properties do not re-render properly in the component and the textfields do not get updated.
I'd appreciate any help with this! Thanks!
Any time you have a bunch of nested state like that which needs to be tracked, just tracking the top-level object won't cause updates to the internals of that object to propagate out. You need to track the internal properties, or you need to reset the whole object you're tracking.
You have basically two rough options for dealing with updates to those internal properties:
@tracked
on the fields, and instantiate the utility class when you create the service. Then updates to those fields will update.this.properties = { ...this.properties, foo: newValue };
Of these, (1) is pretty much always going to be the cheapest and have the best performance. Doing (2.1) will be a little more expensive, because it requires the use of a Proxy
, but not enough that you would normally notice. Doing (2.2) will end up triggering a re-render for every property in the properties
used anywhere in the app, even if it didn't change.
In the case you've described, it appears the fields are well known, which means you should reach for that class. The solution might look something like this:
import Service from '@ember/service';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';
class TheProperties {
@tracked id;
@tracked foo;
@tracked bar;
}
export default class MyService extends Service {
properties = new TheProperties();
@action updateProperty(name, value) {
this.properties[name] = value;
}
}
Note that @tracked
installs getters and setters in place of plain class properties, so if you need to use this for a JSON payload somewhere or similar, you'll also want to implement toJSON
on the utility class:
class TheProperties {
@tracked id;
@tracked foo;
@tracked bar;
toJSON() {
let { id, foo, bar } = this;
return { id, foo, bar };
}
}