I often define service calls in my view models like this:
define: {
person: {
get(lastVal, setVal) {
PersonModel.findOne({ id: this.attr('personId') }).then(setVal);
}
}
}
A person
binding will cause the initial service call but there is no way to update the person if a user action requires it to be refreshed. What do people recommend for this? I have two approaches I am currently using depending on the situation:
Using a method with the events object
Here I define the service call in the method getPerson()
and call it in events.inserted()
. I can then call the method anytime I want to refresh the person.
can.Component.extend({
//...
viewModel: {
getPerson() {
PersonModel.findOne({ id: this.attr('personId') }).then(person => {
this.attr('person', person);
});
}
},
events: {
inserted(el, ev) {
this.viewModel.getPerson();
}
}
});
It’s simple and straight forward but maybe we could avoid using the events object.
Making extra bindings
We can do it all in the view model so that simply a person
binding triggers the initial service call. By adding an extra binding within the person
virtual property, we can change the depended-upon property to trigger a refresh.
can.Component.extend({
//...
viewModel: {
define: {
person: {
get(lastVal, setVal) {
// bind to extra property
this.attr('extraProp');
PersonModel.findOne({ id: this.attr('personId') }).then(setVal);
}
}
},
extraProp: 1,
refreshPerson() {
// increment extra property to cause change
this.attr('extraProp', this.attr('extraProp') + 1);
}
}
});
Thoughts
I’m not sure I like either way. Does anyone have an alternative?
1 Like
If you are using models … the instance should be in the instance store
. Couldn’t you just have a method called refreshPerson
like:
define: {
person: {
get(lastVal, setVal) {
PersonModel.findOne({ id: this.attr('personId') }).then(setVal);
}
}
},
refreshPerson: function(){
PersonModel.findOne({ id: this.attr('personId') })
}
If something can call refreshPerson()
the model should do the ajax request and update the Person instance in the store.
1 Like
Hmm… good thinking. Both of my use cases are actually lists but that should work as long as I have ids, correct? One of them does not really have ids but does have a unique field which should suffice if I define that field as the id for the model.
I’m thinking of doing this to avoid repeating the service call code:
define: {
person: {
get(lastVal, setVal) {
this.getPerson().then(setVal);
}
}
},
getPerson() {
return PersonModel.findOne({ id: this.attr('personId') });
}
Looks good to me. Thanks @justinbmeyer!
That works fine when the list is the same and only properties of the instances change, but if an item should no longer be in the list after the refresh, the item remains on the screen because that’s not how the bindings are set up (using an #each).
This seems to work as long as you don’t have a default:
define: {
people: {
get(lastVal) {
if (lastVal) {
return lastVal;
}
this.getPeople();
}
}
},
getPeople() {
PersonModel.findAll({}).then(people => {
this.attr('people', people);
});
}
One of my data sets does have a default, though, so part of the problem is differentiating between the default value and the actual value from the service. I thought of comparing the value to the map’s cached defaults but lastVal
is already a map so I can’t test for equality.
So I basically have 3 requirements:
- default value for
people
- async fetch when
people
is bound
- method that will refresh
people
on demand
This should work with can.connect which is able to add/remove items from a list in the list store.