Computed Properties with Computed Dependent Keys in Ember.js

Ember’s support for computed properties and bindings is excellent: powerful and not too complicated. Every so often, however, I find myself wanting a particular feature that is not built into Ember’s object system. I want the ability to declare a computed property whose dependent key(s) are actually the value of another computed property.

A pattern that I like to use is where I model important parts of my application in data, and use this data to programmatically accomplish things such as generating UI or controlling the behavior of the system. It’s at the level of writing that machinery that it becomes natural to leverage the computed properties system in more interesting ways.

Exploring the Problem

The intersection of the sets of people here at Atomic that also use Ember and that like to use data to drive their applications is steadily growing larger. Today, when faced with this problem yet again, I decided to ask around the office and see how other teams had handled it.

While I was chatting with Drew, we discussed a few ways that he had achieved this before on previous projects. The first time he’d solved this, he had used a combination of property observers and manually creating Ember.Binding objects. This was clever, but required a fair amount of code and had a drawback: the computed properties would not have a value immediately. Instead, you must wait until the second tick of the runloop after instantiation.

After this, he started to explain an alternative method that he’d used in another project, which led to my inspiration for how I ended up solving it for my project.

My Solution

You can’t naturally create properties with dynamic dependent keys in Ember because you have to know the key path at the time that you declare your computed property. However, there’s nothing stopping you from defining your own class and returning an instance of it in the body of a computed property. That’s the key here.

Let’s look at an example, where we want to depend on a subpath of our model that’s dynamically determined by the value of our “subpath” property:


Ember.Object.extend({
     trampoline: function() {
          return Ember.Object.extend({
               value: Ember.computed.alias(`model.${this.get("subpath")}`)
          }).create({model: this.get("model")});
     }.property("model", "subpath"),
     dynamicDependencyProp: Ember.computed.alias("trampoline.value")
});

As far as I can tell, this approach works quite well. Our property should behave just like any other computed property, without any caveats for the code outside this definition. The biggest drawback that I can see is that we need another property to be defined, which prevents us from wrapping this definition up into a convenient function for us to call.

Ways to Improve

Unfortunately, I don’t see any obvious ways to improve this. Ideally, you would not need the second computed property, which would not only make the implementation cleaner, but also allow for the definition to be abstracted into a function call that would return a computed property after doing the work for you.

I leave this as an exercise for the reader. Can you simplify or improve this technique?

Conversation
  • Nicholas says:

    That’s not a bad solution! I as well have needed to compute dependent keys on many an occasion, although normally my needs are for computing at init time. I’ll remember this for future reference for sure.

  • Whew. It was really tough, but I managed to write a dynamicAlias function. It uses your method of creating a “trampoline” property at runtime, but hides the complexity from the user.

    JSBin


    function dynamicAlias(dependentKeyKey) {
    var dynamicAliasKey = `${dependentKeyKey}_alias`;
    return Ember.computed(function() {
    var dependentKey = this.get(dependentKeyKey);
    Ember.defineProperty(this, dynamicAliasKey, Ember.computed.alias(dependentKey));
    return this.get(dependentKey);
    }).property(dependentKeyKey, dynamicAliasKey);
    }

  • Just stumbled upon a simpler alternative. You could just use `Ember.defineProperty`. Here is a link to an example: https://github.com/offirgolan/ember-cp-validations/blob/4ee9fa7476e92116b7e6a2ae32c1ffeda0b359f8/tests/dummy/app/components/validated-input.js#L26-L28

    I hope that helps! Thanks for the post though.

    • Matt Schick says:

      I don’t think the code is ember-cp-validations is actually updating the depending key dynamically. The dependent key comes from ‘valuePath’, and if valuePath was to change during the lifetime of the component, those computed properties created via ‘defineProperty’ would have no idea. I think….I haven’t actually run the code.

  • Matt Schick says:

    I had to update my version because I was getting errors from the value of ‘subpath’ being ‘undefined’ initially. I’m now checking to see if subpath has a value, and returning a dummy object if it doesn’t. Not sure why you didn’t encounter this issue, perhaps something changed in more recent ember versions (I’m on 2.4.1)

    https://gist.github.com/schickm/08ec48667a4d43e59d91

  • You don’t know the path at the time of declaring the property, but for certain you can observe the property where the path is stored. So you don’t need to create classes or objects on the fly, just replace the computed alias with the correct one each time the path changes:

    https://gist.github.com/schickm/08ec48667a4d43e59d91#gistcomment-1909465

    “`
    export default Ember.Component.extend({
    init() {
    this._super(…arguments);
    this.value = Ember.computed.alias(`model.${this.get(‘subpath’)}`);
    },

    fieldObserver: Ember.observer(‘subpath’, function(){
    this.value = Ember.computed.alias(`model.${this.get(‘subpath’)}`)
    this.notifyPropertyChange(‘value’);
    }),

    value: null
    });
    “`

  • Comments are closed.