Ember.js getters and setters
19 March 2014
To make all the magic fairy-dust sprinkling efficient, like auto-updating templates and computed properties, Ember uses getters and setters instead of accessing properties directly the javascript (and dare I say, the Angular) way. At its most simplest form, it is object.get(property)
and object.set(property)
.
Howevers, it would not be Ember if we were not provided methods on top of these primitives to make our hard lives as web developers simpler. In the following post, I am going to show (some of) these methods through an example, so let's go.
Badges, badges, I want more badges
I am certainly in favor of having badges in every application. Discourse is actively discoursing their badge system so I quickly sketched out something to get ahead of them.
Let me just paste the code here and then use it to explain what the getter and setter methods are good for.
You can quickly scroll through the below templates. They are mostly there so that you get the whole picture. The stuff relevant to this discussion is found in the javascript code.
First, the badge class is defined and some badges created so that we have something to work with:
1App = Ember.Application.create(); 2 3App.Badge = Ember.Object.extend({ 4 name: '', 5 score: 0, 6 unlocked: false 7}); 8 9var rook = App.Badge.create({ 10 name: "R00k", 11 score: 1, 12 unlocked: true 13}); 14 15var talkative = App.Badge.create({ 16 name: "Talkative", 17 score: 10 18}); 19 20var hemingway = App.Badge.create({ 21 name: "Hemingway", 22 score: 1000 23}); 24 25App.Router.map(function() { 26}); 27 28App.IndexRoute = Ember.Route.extend({ 29 model: function() { 30 return [rook, talkative, hemingway]; 31 } 32});
getProperties and setProperties
The first couple of methods come handy when working with a single object but multiple properties.
1App.IndexController = Ember.ArrayController.extend({ 2 sortProperties: ['score'], 3 sortAscending: true, 4 5 (...) 6 7 actions: { 8 addBadge: function() { 9 var newBadgeProperties = this.getProperties(['name', 'score']); 10 newBadgeProperties.score = parseInt(newBadgeProperties.score, 10); 11 12 var newBadge = App.Badge.create(newBadgeProperties); 13 this.get('model').pushObject(newBadge); 14 this.setProperties({ name: '', score: '' }); 15 }, 16 17 (...) 18 } 19});
On line 9, we want to create a new object with the values provided in the input boxes (addBadge is the action that gets triggered when the Add button is clicked, check the template). getProperties
will create a javascript object creating key-value pairs for the passed properties. So the above might e.g yield { name: "Silent Bob", score: "2" }
. That gets directly passed in to create a new badge object.
On line 14, we use the mutating pair of getProperties
to reset the input fields. Pretty straightforward.
getEach and setEach
Ember has us covered when we are working with an array and want to get (or set) the same property of each item.
1App.IndexController = Ember.ArrayController.extend({ 2 sortProperties: ['score'], 3 sortAscending: true, 4 5 totalScore: function() { 6 var sum = function(s1, s2) { return s1 + s2; }; 7 return this.get('model').getEach('score').reduce(sum); 8 }.property('model.@each.score'), 9 10 actions: { 11 (...) 12 13 unlockAll: function() { 14 this.get('model').setEach('unlocked', true); 15 } 16 } 17});
When the "Unlock all" button launches the unlockAll
action, it calls setEach
on the badges (line 14), making all of them unlocked (you can verify this in the demo by seeing the color of the badge names turn darker - their css class has been updated). Another advange of setEach
is that it guards against calling set
on null or undefined values.
You might know the reader counterpart, getEach
as mapBy
. It goes through the array and makes another array by getting the passed property on each item. So in the above example (line 7), we first collect the score for each badge and then sum them up by way of reduction. (A shiny example of a non-distributed map-reduce :) ).
A macro can make nice code nicer
I have used a reduce computed macro before to set up sorting. I have gotten the hang of it and realized I could use another couple of them to make the above code simpler (and more performant):
The problem is that the subject under discussion, getEach
is gone now, so pretend you did not see this.
Finally, here is the jsbin, should you decide to play around with it:
I hope some of that sticks and you'll write less "bare" gets and sets.
Share on Twitter