Porting MobX ideas to Ember.js - dependency-less properties?

Recently I’ve read a lot about a new observable library written initially for ReactJS - MobX. From what I see on the surface it doesn’t differ from what Ember.js offers a lot apart from one thing - properties don’t need dependencies. For me this is a huge deal, because dependencies on properties are one of the worst parts of Ember.js in my opinion. A few reasons on top of my head:

  • they may be hard to understand for beginners (I’ve seen confusion on this several times)
  • a typo in a dependency may cause bugs that are hard to find, it will usually break only when a value changes after the initial page load
  • dependencies are just boilerplate
  • depending on nested collections is not possible without workarounds at the moment

As far as I understand how MobX works, dependencies are calculated on runtime - when a property is calculated, each lookup is registered and added to dependencies. This is neat and I think it may be actually faster than specifying dependencies by hand. Let’s consider a following property:

Ember.computed('useFoo', 'foo', function() {
  if(this.get('useFoo')) {
    return this.get('foo');
  } else {
    return this.complicatedAndSlowComputation();
  }
});

Currently in Ember.js, when foo changes, the property will be recomputed even if useFoo is set to false. If Ember.js worked more like MobX and useFoo was set to false, only useFoo property would be registered as a dependency (because foo wouldn’t be reached).

I’m wondering if a similar model could be introduced to Ember.js? My limited knowledge of Ember’s observable code tells me that it could be possible, but I’m not really sure about that.

3 Likes

quite surprised to see no one responded to this great idea! I was trying to learn react and redux and later found mobx and it seems to bee great!! I like your ideas.

What does the core members say?

This implicit behavior would not always work. What if you have:

Ember.computed('foo', function() {
  if(this.get('useFoo')) {
    return this.get('foo');
  } else {
    return this.complicatedAndSlowComputation();
  }
});

In this scenario, for whatever reason, you don’t want the property to be recomputed when useFoo changes. How would you define such a thing?

Also, I think it’s valuable to see the dependencies of a property right there in the definition. Otherwise, if your property has more than a few lines of code, it will become quite difficult to figure out why something is being recomputed.

It would work, but it would work differently. Personally I’d take all the benefits of MobX approach over a drawback like that.

It has some value, yes, but then again, it depends on what you value more. An unneeded recomputation has some cost, but in my experience it would rarely result in a bigger problem. A misspelled or missing dependency often results in a broken UI.

Another thing is that it allows for things that you can’t do in Ember.js at the moment (for example dependency on nested properties on collections)

Not saying that there wouldn’t be any advantages, but it would be different and it would certainly be a breaking change. I’m sure it would be possible to write an addon that does something like that.

Sure, I am aware of that. Quite possibly the biggest blocker is that it would be probably hard to write a deprecation warning for such a change.

I’m not so sure about that. This would change the entire key-value observing stuff, so I doubt that it could be an addon. Or at least not at the moment, without changes in the core.

Re: the problem of complicatedAndSlowComputation() you would make that value itself a computed property. In MobX, computed properties are memoized. That means that a complicatedAndSlowComputation property would be computed once and cached for future use of the resulting value. It would only be recomputed if it (internally) depended on some other observable value that changes.

To some degree this maps with how @tracked works.

But, tracked actually has even nicer behavior (IMO).

So with @tracked any native getter/setter just works without even having to know that the value is computed or reactive, in a way getters are always computed properties but just native to JS instead of being anything special. And therefore @tracked does all the work to notify changes across values.