Last active
December 5, 2015 16:48
-
-
Save mfenniak/bb66e5cd7b83517217c5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Ember from 'ember'; | |
// computedDependent is a wrapper around Ember.computed that promotes a property access pattern that | |
// ensures that a computed property declares its dependencies accurately. | |
// | |
// You create a computed property with computedDependent, passing in the getter function and the names | |
// of the dependent properties as additional arguments. The getter function will be called with a | |
// single object argument which has ES5 properties defined upon it matching the names of the dependent | |
// properties; you can then use this 'props' argument to access the dependent properties. | |
// | |
// Example: | |
// fullName: computedDependent((props) => props.firstName + ' ' + props.lastName, 'firstName', 'lastName') | |
// | |
// This example is analogous to this normal Ember pattern: | |
// Ember.computed('firstName', 'lastName', () => this.get('firstName') + ' ' + this.get('lastName')) | |
// | |
// The advantage of computedDependent is that with the normal Ember pattern, it is possible for you to | |
// write 100% functioning code that passes common unit-tests by defining the getter function correctly, | |
// but missing or neglecting a dependent property in the Ember.computed() function call. The code will | |
// work correctly in a variety of testing scenarios, but then one day surprise you by failing to update | |
// properly because the property dependency isn't defined correctly. | |
// | |
// With computedDependent, if you missed a dependent property in the computedDependent() call, accessing | |
// it on the props object would always return undefined. This is far more likely cause an early testing | |
// failure as it would never work correctly. If it works when the dependent properties are static, it will | |
// work just as well when they're dynamic. | |
// | |
// computedDependent maintains all the advantages that a normal Ember pattern has, such as only accessing | |
// properties when they're actually used by the getter function. It also caches property accesses within | |
// each call to the getter (I'm not so sure how valuable that is...). It also works just fine for nested | |
// properties; eg. | |
// fullName: computedDependent((props) => props['name.first'] + ' ' + props['name.last'], 'name.first', 'name.last') | |
// | |
function computedDependent(getterFunction, ...dependentProperties) { | |
const PropertyAccessor = function(targetObject) { | |
this.__targetObject = targetObject; | |
this.__cache = new Map(); | |
}; | |
dependentProperties.forEach((dependantProperty) => { | |
Object.defineProperty( | |
PropertyAccessor.prototype, | |
dependantProperty, | |
{ | |
get() { | |
if (this.__cache.has(dependantProperty)) { | |
return this.__cache.get(dependantProperty); | |
} | |
const value = this.__targetObject.get(dependantProperty); | |
this.__cache.set(dependantProperty, value); | |
return value; | |
} | |
}); | |
}); | |
return Ember.computed(...dependentProperties, { | |
get() { | |
return getterFunction.apply(this, [new PropertyAccessor(this)]); | |
} | |
}); | |
} | |
// Example usage: | |
export default Ember.Component.extend({ | |
cardNumberValid: computedDependent(function(props) { | |
return !Ember.isBlank(props.cardNumber) && Stripe.card.validateCardNumber(props.cardNumber); | |
}, "cardNumber") | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment