You might have run across the situation where you have lots of properties in a Controller or Component, and they have associated Computed Properties that calculate some presence value. They end up feeling like repetitive boilerplate.

How might you go about cleaning them up? Perhaps auto-generating them?

Typical Ember Example

Specifically, what kind of code am I talking about? Something like this:

 1 import Ember from 'ember';
 2 
 3 let LoginComponent = Ember.Component.extend({
 4   userName: '', 
 5   password: '', 
 6 
 7   hasUsername: Ember.computed.notEmpty('userName'),
 8   hasPassword: Ember.computed.notEmpty('password'),
 9 
10   ... etc ...
11 });
12 
13 export default LoginComponent;

Typically these properties are the results of requirements from the UI to display different elements on particular states. The template will have some conditionals that lean on these properties to achieve this. Depending on how complex the UI is, you could end up having a few of these state properties.

Let’s look at how we could save ourselves from manually having to define lots of boilerplate properties.

Higher Order Functions

Any function that takes another function as a parameter, or returns a function as its result is a Higher Order Function. Given that functions are first class citizens in Javascript, it’s pretty easy, even casual to use Higher Order Functions to clean up functions that differ only by name and the property referenced. Can we use Higher Order Functions to create the notEmpty Computed Properties for us? Let’s see.

Take 1: Using Ember’s defineProperty Method

Ember provides a method which allows you to perform a basic level of meta-programming by defining properties at runtime. The parameters for defineProperty are (basically):

  • The object you want to define the new property on
  • The name of the new property
  • A ‘Descriptor’ which for our purposes will be a Computed Property to be attached to the new property

Which looks like this:

Ember.defineProperty(this, myCpPropName, Ember.computed('propName', function(){...});

We’re now going to use a Higher Order Function to pass back to defineProperty so that the Computed Property has a dynamic value.

Note that this is a trivial example for the purposes of showing how Higher Order Functions can be used, and the example is chosen to illustrate the technique.

And with that in mind lets look at the solution:

 1 import Ember from 'ember';
 2 
 3 const { computed, on } = Ember;
 4 
 5 // This is our Higher Order Function, that we are passing in the field name
 6 // to so that it can be captured by the Computed Property and returned as the
 7 // function to be called. 
 8 //
 9 // We define it here because if we defined it on the Component we would expose
10 // a method on the public API which is not usable outside the component, and 
11 // only returns a descriptor. 
12 const has = (field) => { return computed.notEmpty(field); };
13 
14 let LoginComponent = Ember.Component.extend({
15 
16   UI_FIELDS: ['userName', 'password'],
17 
18   // Builds a field property name from a field name.
19   _fieldPropName(field) {
20     // Utilize ES6 string interpolation. 
21     return `has${field.capitalize()}`;
22   },
23 
24   // Run once on component init, sets the default empty values for the field
25   // and generated the hasPropertyName properties for each field.
26   defineFieldProperties: on('init', function() {
27     this.UI_FIELDS.forEach((field) => {
28       this.set(field, '');
29       let propName = this._fieldPropName(field);
30       Ember.defineProperty(this, propName, has(field));
31     });
32   })
33 });
34 
35 export default LoginComponent;

Unfortunately there’s a bit of a hidden trap here. As it turns out defineProperty is a relatively slow function, and we’ve just set it up so that the Component is going to execute it every time its initialized. While the technique above would be fine in something long lived like a `Service, it’s no good in a component displayed 100 times. So what do we do?

Take 2: Pre-creating the attributes at Object creation

The best way to go about this is to pre-generate an Object with the properties and Computed Properties we need, and then re-extend our component with the pre-generated items before we export it. By taking this approach, Ember will only have to do its defineProperty magic once and only once.

 1 import Ember from 'ember';
 2 
 3 const { computed, on } = Ember;
 4 
 5 // Our Higher Order Function, as before
 6 const has = (field) => { return computed.notEmpty(field); }
 7 
 8 // The Component UI fields as before. 
 9 const UI_FIELDS = ['userName', 'password'];
10 
11 // Pre-generate an object with the properties and computed
12 // properties that we need. 
13 let processedAttrs = {};
14 UI_FIELDS.forEach( (field) => {
15   processedAttrs[field] = '';
16   processedAttrs[`has${field.capitalize()}`] = has(field);
17 });
18 
19 // Create a Component just as you normally would. 
20 let LoginComponent = Ember.Component.extend({
21   // Your usual Ember component stuff in here as needed
22   debugOutput: on('init', function(){
23     console.log('hasPassword=', this.get('hasPassword'));
24     console.log('hasUserName=', this.get('hasUserName'));
25   })
26 });
27 
28 // Export the component with an extra extension of our pre
29 // generated attributes and computed properties.
30 export default LoginComponent.extend(processedAttrs);

So obviously this got a little messy looking, as changes to optimize for performance usually make things. That said, the technique above could be abstracted out quite easily to wrap the Ember Component class and handle the necessary heavy lifting without all of the ceremony.

Summary

At the end of our exploration, we can see how it might be possbile to clean up and auto generate boilerplate Computed Properties. In the end things looked a litlle messy, and obviously you wouldn’t use this technique to clean up two properties.

That said, I feel like there’s the bones of a simple validation system here. You could build a simple form validation system on this technique with some pre-canned validators that are higher order functions along the lines of the has function, for example minLength, maxLength and so on. Then the UI_FIELDS value would be expanded to an object, and each field name would have associated keys describing the validation to use, defaults, etc.

It was an interesting diversion for me, and hopefuly you learned something new.

Eternal thanks to the ever patient and helpful Alex Matchneer, Todd Smith-Salter and Kelly Sutton for their input on this article!