Question about cached records


#1

The guide says about cached records:

One downside to returning a cached record is you may find the state of the data has changed since it was first loaded into the store’s identity map. In order to prevent this stale data from being a problem for long, Ember Data will automatically make a request in the background each time a cached record is returned from the store. When the new data comes in, the record is updated, and if there have been changes to the record since the initial render, the template is re-rendered with the new information.

Okay, on the surface, that’s simple enough to understand. But I’m not completely certain of the implementation. Let’s say I have a simple model foo that has one property text. And in my route I have this:

model() {
  return this.store.findRecord('foo', 123).then((foo) => {
    console.debug('foo:', foo.get('text'));
    return foo;
  });
}

And in the route I have:

{{my-controller foo=model}}

So two things here:

  • Does this mean the console.debug() will actually run twice, once when the cached record is returned immediately, and then once again when the updated record is returned?
  • Will my-component first receive the cached record, and then receive the updated record (ie, didReceiveAttrs() runs twice)?

Blink from loading (isPending) to resolved. Why?
#2

To quickly answer the questions:

  • console.debug() will only ever one once for each time you enter this route — and if the record is cached, as you suggest, then this will happen immediately.
  • didReceiveAttrs in this case will only run once.

The reason for this has to do with the fact that ember-data uses mutable data structures. So, there is only ever one reference to this model — but attributes on the model will be updated.

Because of this, ember-data does expose a few events that you can attach to on the model to know when it has been updated…for example:

https://emberjs.com/api/data/classes/DS.Model.html#event_didUpdate

That being said, you’ve encountered one of the confusing things about Ember data because I think that it’s fairly intuitive to expect that didReceiveAttrs() might run twice.

There’s lots of discussions and ideas around this very problem — ember-redux or googling for something like ember-data immutable will give you some ideas for alternatives (I’m not necessarily suggesting that you use those alternatives; but it might help to understand what some of the implications of ember data are when you see alternative solutions to similar problems).


#3

@Spencer_Price Thank you, your explanation is quite helpful.

Given your information, I may have to rework some things to properly handle model updates. I really don’t want to go down the redux path (I might as well use React if so). I’ll see if I can possibly use DS.Model's didUpdate() event in my program flow. One problem I may have is, even if I can use didUpdate(), I have a lot of models I’m fetching simultaneously, and to individually listen to each one may get messy.

If worse comes to worse, I may end up ignoring whether data I’m fetching is from a cached record or not since the records I’m dealing with really shouldn’t be changing all that often, at least not in the few minutes that our average user should be on the app. Also, I may just give a UI option where a user can do a force reload (via reload: true passed into findRecord()) so they’re guaranteed to get the latest record.


#4

@accelerate the behaviour is all configurable.

Have a look at the options you can supply to findRecord: https://emberjs.com/api/data/classes/DS.Store.html#method_findRecord

This changes how the findRecord call behaves at point of use.

You can also configure background reloading at the adapter level: https://emberjs.com/api/data/classes/DS.JSONAPIAdapter.html#method_shouldBackgroundReloadRecord

So you can configure you whole application or individual models to behave as you need


#5

Thanks @alexspeller.

I have a followup question. I’m aware of using { reload: true } with findRecord(). My question, though, is how I can do a force reload under only certain conditions? Specifically, I have a refresh button on my page. That button basically does a this.refresh() on the route. So when the user clicked the button, I want to use { reload: true } with findRecord(). But under other conditions, such as transitioning to this route, I want it to use the cached record if available.

The hackish way that I can think of is to pass forceReload=true as a URL param, and so I can check for that in model() to determine what findRecord() behavior I want. But I want to know if there’s a cleaner approach.