anchorComputed Properties and Computed Property Macros
Computed Properties are among the first things developers new to Ember learn. They are a great way of defining dependencies between data points in the application and ensuring the UI stays consistent as these data points change.
Ember comes with a set of macros that implement property logic that most applications need and allow for short and expressive definitions like
isActive: Ember.computed.equal('state', 'isActive');
There are addons that provide even more macros for common use cases like ember-cpm or ember-awesome-macros.
anchorWhere Computed Property Macros fall short today
Computed Properties are very similar to template helpers in the way that both are pure functions that can only depend on their inputs. While a template helpers receives its inputs as arguments, Computed Properties define their inputs as dependent keys.
In some cases pure functions are not sufficient though as the computation in the template helper or computed property also depends on global state or the inputs cannot statically be listed in the helper or property definition. This is the case for example for computations on collections when it is unknown upfront on which property of each element in the collection the computation depends, e.g.
filteredUsers: filterByProperty('users' 'filter')
Here what we would like to do is filter the users
array by the value of the filter
property of the context. E.g. when filter
is 'isActive'
we'd expect filteredUsers
to contain all active users, when filter
is 'isBlocked'
we'd expect it to contain all blocked users and so on.
With template helpers and the ember-composable-helpers addon, we're be able to write something like this in the template:
{{#each (filter-by filter users) as |user|}}
…
{{/each}}
and because the filter-by
helper is a Class based helper this actually works and the DOM updates correctly whenever the value of the filter
property or e.g. the isActive
property of any user changes.
With Computed Properties it is not currently possible to implement something like this (at least not as a reusable macro).
anchorEnter Class based Computed Properties
With the Class based Computed Properties that ember-classy-computed introduces it is actually possible now to implement something like the above mentioned filterByProperty
macro. The computed property returned by that macro can now correctly be invalidated when any of the user's isActive
, isBlocked
etc. properties change although it is not actually possible to know what these properties might be upfront. This allows keeping the filtering logic in JavaScript as opposed to in the template when using a Class based template helper:
import filterByProperty from 'app/computeds/filter-by';
…
filteredUsers: filterByProperty('users' 'filter')
{{#each filteredUsers as |user|}}
…
{{/each}}
The implementation for the Computed Property macro looks like this:
// app/computeds/filter-by.js
import Ember from 'ember';
import ClassBasedComputedProperty from 'ember-classy-computed';
const { observer, computed: { filter }, defineProperty } = Ember;
const DynamicFilterByComputed = ClassBasedComputedProperty.extend({
contentDidChange: observer('content', function() {
// This method is provided by the ClassBasedComputedProperty
// base class and invalidates the computed property so that
// it will get recomputed on the next access.
this.invalidate();
}),
filterPropertyDidChange: observer('filterProperty', function() {
let filterProperty = this.get('filterProperty');
let property = filter(`collection.@each.${filterProperty}`, item => item.get(filterProperty));
defineProperty(this, 'content', property);
}),
// This method is called whenever the computed property on the context object
// is recomputed. The same lazy recomputation behavior as for regular computed
// properties applies here of course. The method receives the current values
// of its dependent properties as its arguments.
compute(collection, filterProperty) {
this.set('collection', collection);
this.set('filterProperty', filterProperty);
return this.get('content');
},
});
export default ClassBasedComputedProperty.property(DynamicFilterByComputed);
Comparing this code to the implementation of the filter-by
helper mentioned above you will recognize that both are almost identical. This illustrates very well what Class based Computed Properties are: a way to use the same mechanisms that are already established for Class based template helpers for Computed Properties as well.
anchorNotice
ember-classy-computed is currently at a very early stage and we haven't thoroughly tested the implementation just yet. We have also not done any benchmarking to get a better understanding of what the performance implications are. That is to say, while we encourage everyone to try this out, be aware you're currently doing so at your own risk as this is most likely not production ready (yet). We have the feeling though that this will be a valuable addition to Computed Properties in the future and can close the gap that currently exists between Computed Properties and template helpers.