Octane selectively disable tracking / @computed status

I am in the process of migration to Octane and I face a problem I used to solve in the pre-octane versions with a simple hack, but now I am a bit confused. Here it is.

Say, I’ve got a model

// it's not migrated yet, nevermind
export default DS.Model.extend({
  someObject: DS.attr(),
  anotherobjects: DS.hasMany('anotherObject', {async: false}),
}

then in my component I allow editing of this someObject but I don’t want it to be bound to myModel but rather be detached. I do this using a so-called buffer, so the component works with this buffer and when it comes to save it simply rewrites the original model property and saves. However there is a single situation when I want my buffer to be recalculated, namely when the array of anotherobjects is changed. So, everything look somewhat like this

  buffer: computed('{myModel.anotherobjects.[]}', function () {
    return {...this.myModel.someObject};
  }),

  persist: task(function* () {
      // Copy the buffer back to the model and save it
      let bufferClone ={...this.buffer};
      this.myModel.set('someObject', bufferClone);
      yield this.myModel.save();
  }).restartable()

Phew, that was a long intro. Anyways, the hack I have here is that I don’t explicitly put the someObject as my buffer computed dependency, so it’s calculated only once (or in that rare case when anotherobjects array changes).

Now the questions:

  1. How can I do the same thing in octane? i.e. is it possible to tell exactly which properties I want to track and which I don’t wanna track.
// If I simply declared it as getter I'd have 2 issues: 
// I'll bound it to the `myModel.someObject` and vice versa 
//I won't have any mention of `myModel.anotherobjects`
get buffer() {
  return {...this.myModel.someObject};
}
  1. There is still the @computed decorator. From the documentation it’s not clear which status it has in the octane world: will it be deprecated? And what would happen if I put it on a getter: will it disable the tracked stuff at all, i.e. would this code be equivalent to my old one?
// will this work as expected?
@computed('{myModel.anotherobjects.[]}')
get buffer() {
  return {...this.myModel.someObject};
}
  1. (probably the most important one) Are there any other techniques to achieve the same goal I am trying to achieve?

I’m thinking on the answer to your broader question, but @computed decorating a getter is actually identical semantically to doing it with classic Ember Object, so that part works exactly as before. In fact, everything which becomes a JS decorator with classic classes is always has been a decorator; it’s just that we didn’t have the language-level syntax for decorators previously!


Your old solution is basically leaning on the fact that @computed both tracks the need for updates and caches/memoizes, while @tracked simply tracks the need for updates. You can just implement the caching yourself, at whatever level of granularity is appropriate: object (reference) equality, deep value equivalence, or anything in between. I’d have to spend some more time to write down a code sample which fleshes that out, but that’s probably the way I’d handle this.

You can also look at forcing re-compute using a modifier, but that definitely gets hairier. I’d think about further separating these, rather than trying to do it all just via getters. :thinking:

1 Like

Hi there! First of all thanks chriskrycho for clarifying things for me. I’ve chosen the “@computed on a getter” approach as it seemed to be the simplest one.

But I’d like to ask a follow up/related question here. Today I surprisingly found out that getters are evaluated each time they are called, so in that respect they are not equivalent to computeds. My bad here, probably I should’ve read the docs better or something.

Anyways, we’ve had some really heavy computed properties which took advantage of this “caching” mechanism which Ember computed provided. The question is how to transfer it properly to octane?

Again, a “@computed on a getter” seems to be the simplest approach. But as usual I’d like to know how legal it is in the octane world? Wouldn’t it be deprecated one day?

Maybe there is a vanilla solution for caching out there? I imagine this could be a decorator or something… What do you guys may recommend me?

For starters, you can just stick with @computed for those specific heavy CPs which need caching, and that’s my initial recommendation! They won’t be deprecated without a clear replacement story (whether via code, recommended third-party library, or detailed docs on writing your own), so you can safely take that path. That’s the whole reason Ember approaches change the way it does!

This difference is well known, of course, so there’s initial design work being done via RFC on a caching decorator. If you were feeling adventurous, you could help the Framework team flesh that out by helping build that decorator and then generating an experience report for it.

Finally, there’s always the old standby of just manually implementing caching for the CPs in question. If you only have a couple of them and you want to remove @computed from your code entirely, you can just decide what caching semantics you want and implement them yourself, e.g. using private properties on the component class.

1 Like