Skip to content

Instantly share code, notes, and snippets.

@zkwentz
Last active June 5, 2016 22:20
Show Gist options
  • Save zkwentz/dc002c0a5ad9affab6734b1984c9c487 to your computer and use it in GitHub Desktop.
Save zkwentz/dc002c0a5ad9affab6734b1984c9c487 to your computer and use it in GitHub Desktop.
Ember Meetup Talk Outline 6/15

A Pattern for Promised Properties

Computed properties and promises, historically, just don't mix. In fact, a lot of developers have devoted a lot of time to avoiding the problem altogether. I work with Ember data a lot, and therefore promises and computed properties a lot, and I'd like to share my pattern for dealing with promised in properties. And how that helped me develop a good pattern for dynamically-keyed, computed properties.

Both of these examples that follow are contrived and both could more easily done in a template, but they come together in a good example where we need to filter a model's hasMany children by a belongsTo relationship on those children, and pass those to a select as options. It shows both the need for these techniques, and yet how much of a corner case they are.

Ember.Component.extend({
model: null,
childrenOnModel: Ember.computed('model.manyChildren.[]',{
get(key) {
let model = this.get('model');
if (!!model) {
// manyChildren returns a promise, returning simply this returns a promise proxy, and not the actual resolved data.
// due to the fact that computed properties cache, this would never resolve if we used it in the template.
model.get('manyChildren').then((manyChildren) ->
this.set('childrenOnModel',manyChildren);
)
// we return null directly below the promise, so that we do not return the promise proxy `[object Object]`.
// this also makes it easy for us in computed properties where we depend on this computed property. We can
// assume if it's `null` that our promise hasn't resolved yet, and know that when it does resolve, our
// computed property's type will change, ensuring that we always trigger a recompute of that computed
// property.
return null;
}
},
set(key,value) {
// if we didn't override set, our getter would completely override this computed property, so we simply define
// `set` and do nothing to ensure that doesn't happen.
return value;
}
})
});
<div class="there-are-better-ways">
{{#each (get model childrenOnModel) as |child|}}
<span class="child-name">{{child.name}}</span>
{{/each}}
</div>
Ember.Component.extend({
model: null,
dynamicKey: null,
_dynamicComputedProperty: null,
dynamicComputedProperty: Ember.computed('_dynamicComputedProperty','model','dynamicKey',{
get(key) {
// we don't get but actually check that this doesn't have a key of `_dynamicComputedProperty`, if
// it doesn't, we know it hasn't been defined yet, and we should define that property.
if (!this._dynamicComputedProperty) {
// let's get our dynamic key, something we don't know, and so we don't have the luxury of explicitly
// specifying it here in the computed property.
let dynamicKey = this.get('dynamicKey');
// we get the model, this is the object off of which we have many properties, and what we expect our
// dynamicKey to be a member of, it's probably a good idea to do some checking here that the property actually
// exists on the model with an `Ember.assert()`.
let model = this.get('model');
// Do we have both a `model` and an allowed `dependentKey`?
if (!!dynamicKey and !!model) {
// we do, let's pass our `dynamicKey` to the computed property builder `_createDynamicComputedPropertyWithKey`.
Ember.defineProperty(this,'_dynamicComputedProperty',this._createDynamicComputedPropertyWithKey(dynamicKey));
}
// we always return the result of `_dynamicComputedProperty`.
return this.get('_dynamicComputedProperty');
}
},
set(key, value) {
value
}
}
}),
_createDynamicComputedPropertyWithKey(dynamicKey) {
// merge our `dynamicKey`, with the dependentKeys that we do know.
let dependentKeys = ['model',dynamicKey];
// this function returns a computed property, it's what we are defining in the previous computed property.
return Ember.computed(dependentKeys,()->
let model = this.get('model');
if (!!model) {
return model.get(dynamicKey);
}
);
}
});
<div class="and-yet-still-better-ways">
{{#with (get model dynamicKey) as |dynamicProperty|}}
<span>{{dynamicProperty.name}}</span>
{{/with}}
</div>
<div class="well-not-always">
{{#power-select options=(get model childrenOnModel) selected=(filteredChildren?) multiple=true onchange=(action 'multiChanged') as |option|}}
{{#if (eq option.belongsTo.id filterId)}}
{{option.name}}
{{/if}}
{{/power-select}}
</div>

In the previous example, even if you did find a way around the problem of having null options in your select input. You still have the issue of showing what's properly selected. Somehow, you'll be making a computed property there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment