Computed properties are the cornerstone of data binding in Ember.js. If you’ve written any code for Ember you’ve probably written your fair share of them. They tend to look something like this:
1 2 3 4 5 | |
The preceding property is a “getter”. That is, it can only retrieve a value. If you try to set the fullName property it will be ignored. Observe:
1 2 3 4 5 6 7 8 | |
This is where “setters” come in. Setters let us set a computed property and provide logic to handle, manipulate, or deal with the input.
Let’s make a getter+setter (AKA “accessor”) for our fullName property. This property handles both getting and setting.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
Let’s look this piece by piece.
Get or set?
The first part to notice is this:
1 2 3 4 5 | |
Getting: When we call user.get('fullName'), the property is called with key as 'fullName' and no fullNameString argument.
Setting: When we call user.set('fullName', 'John Stamos'), the property is called with key as 'fullName' and fullNameString as 'John Stamos'.
In other words, when arguments.length === 1, it should act as a getter, and when arguments.length === 2, it should act as a setter.
The Getter
When this function is being called as a getter, it behaves just like normal computed properties. This line from the getter section is the same as in my first example of a normal computed property:
1
| |
Whatever we return here is what we get when we call user.get('fullName').
Just like normal computed properties, we need to keep this line, too:
1
| |
The Setter
Here are the relevant sections of the setter portion of our property:
1 2 3 4 5 6 7 8 9 10 | |
First, as you might expect, we split a given full name into first and last name and then set the firstName and lastName properties:
1 2 3 | |
Finally, we return the value that we want to be cached as the fullName property’s value:
1
| |
This is the most surprising part so I’ll repeat it: whatever is returned from the setter is cached as the property’s value. One must be careful to return the right value.
And, that’s all there is to know about getters and setters in Ember.js. Or, at least on a surface level.
ur doin it wrong
The above structure for a getter+setter property is standard and is what the Ember.js documentation prescribes. But, it has a fatal flaw as the following example will demonstrate.
What if the firstName property is also a getter+setter? Let’s say, as a contrived example, that because of some new regulatory legislation we are not legally allowed to collect first names, only first initials. We might make our firstName property look like this:
1 2 3 4 5 6 7 8 9 | |
This property works like this:
1 2 3 | |
Let’s look at how the fullName property behaves when we introduce this change.
This use case works fine:
1 2 3 4 5 6 7 | |
This one… not so much:
1 2 3 4 5 6 7 | |
Uh oh. Did you catch it? The fullName property should be returning 'J Stamos', not 'John Stamos'. The problem is that the fullName property assumed how the firstName property would handle its input.
Instead, I advocate that each property mind its own business. fullName should allow firstName to handle its own input (and caching).
A better way
Here’s a version of the fullName property that keeps to itself and thus works correctly:
1 2 3 4 5 6 7 8 9 10 | |
When the property is called as a getter (i.e., with one argument), it works the same way it always had.
When the property is called setter, it sets the properties it needs to but doesn’t just return fullNameString like the previous version did. Instead, it allows the getter logic to run, which honors firstName’s and lastName’s decisions about how they should handle being set.
Not only is this version more correct, it is shorter and more DRY.
Conclusion
Getters and setters are an important part of data binding in Ember.js. Unfortunately, they can be confusing if you haven’t experimented a lot and read the source, which you shouldn’t have to do.
Ember.js is a young technology and presents a paradigm that’s new for most of us. If you have other insights, techniques, best practices, or critiques, I’d love to hear them.