Ember Tips: Computed Properties and Arrow Functions? Not a Good Idea

Dec 11th, 2016

Arrow function expressions were definitely a great addition in ES6 and thanks to tools like babel the new syntax has been quite widely adopted. Besides more concise syntax, an interesting thing about arrow function expressions is that they preserve the context, i.e. they don’t define their own this, which was sometimes annoying and resulted in assigning that or self variables to keep the outer context that could be referred inside functions. As great as it sounds, arrow function expressions cannot be used in all cases. One example would be Ember computed properties.

Arrow Function Expressions - A Quick Introduction

Let’s start with a quick introduction to arrow functions. Before ES6, anytime we were using function expressions and wanted to refer this from outer context, we had to do some workarounds which are (arguably) a bit unnatural, especially comparing to other major programming languages.

Let’s do some pseudo-object-oriented programming with JavaScript (ES5) to illustrate a possible issue with function expressions:

123456789101112131415161718

functionOrder(){this.id=Math.floor((Math.random()*10000000)+1);// don't do it in a production code ;)this.items=[];}Order.prototype.addItem=function(item){this.items.push(item);}Order.prototype.logItems=function(){this.items.forEach(function(item){console.log("item description: "+item.description+" for order with id: "+this.id);});}varorder=newOrder();order.addItem({description:'Glimmer 2 rockzzz'});order.logItems();// whooops

We have a simple class-like functionality using constructor function and prototype to implement Order with some questionable ( ;) ) way of assigning id and some items. We can add more items with Order.prototype.addItem function and we can log them with Order.prototype.logItems function.

Function expressions create their own context and define own this, so it no longer refers to the outer context, which is the order instance. There are several ways to solve this problem.

The most obvious is to assign outer this to some other variable, like that or self:

123456

Order.prototype.logItems=function(){varself=this;this.items.forEach(function(item){console.log("item description: "+item.description+" for order with id: "+self.id);});}

You can also pass outer this as a second argument to forEach function:

12345

Order.prototype.logItems=function(){this.items.forEach(function(item){console.log("item description: "+item.description+" for order with id: "+this.id);},this);}

You can even explicitly bind outer this to callback argument inside forEach function:

12345

Order.prototype.logItems=function(){this.items.forEach(function(item){console.log("item description: "+item.description+" for order with id: "+this.id);}.bind(this));}

All these solutions work, but aren’t really that clean. Fortunately, since ES6, we can use arrow function expressions which preserve outer context and don’t define own this. After little refactoring Order.prototype.logItems could look like this:

12345

Order.prototype.logItems=function(){this.items.forEach((item)=>{console.log("item description: "+item.description+" for order with id: "+this.id);});}

Much Better!

As great as it looks like, it may not be a good idea to apply arrow function expressions everywhere, especially for Ember computed properties.

Ember Computed Properties And Arrow Functions? - Not A Good Idea

Recently I was doing some refactoring in one Ember app. The syntax in one of the models was a bit mixed and there were some function expressions and arrow function expressions which looked a bit like this:

app/models/user.js

1234567891011121314151617181920

importEmberfrom"ember";importModelfrom'ember-data/model';exportdefaultModel.extend({fullname:Ember.computed('firstname','lastname',function(){return`${this.get('firstName')}${this.get('lastName')}`;}),doThis:function(){// some logic goes here},doThat:function(){// even more logic},doYetAnotherThing(args){// more logic}});

So I decided ES6-ify entire syntax here and ended up with the following code:

app/models/user.js

1234567891011121314151617181920

importEmberfrom"ember";importModelfrom'ember-data/model';exportdefaultModel.extend({fullname:Ember.computed('firstname','lastname',()=>{return`${this.get('firstName')}${this.get('lastName')}`;}),doThis(){// some logic goes here},doThat(){// even more logic},doYetAnotherThing(args){// more logic}});

And how did this refactoring end up? Well, instead of a proper fullName I was getting undefined undefined! That was surprising, but then I looked at the changes and saw that I’m using arrow function expressions in computed properties and referring there to this, which won’t obviously work for the reasons mentioned before. So what are the options for computed properties?

Wrapping Up

Even though arrow function expressions are very convenient to use, they can’t be used interchangeably with function expressions. Sometimes you may not want this inside a function to preserve outer context, which is exactly the case with Ember computed properties.

Comments

About Me

Hi, my name is Karol Galanciak, I'm a technical polyglot and domain expert in vacation rental and fitness industries.

I specialize in building APIs, microservices architecture, working with legacy applications, building ambitious Single Page Applications and building payment processors for credit cards processing. Currently I'm working as CTO at BookingSync, so at this moment I'm not available for Ruby on Rails or Ember.js consulting.

Overwhelmed by too much information?

Following all the newsletters, RSS feeds, Twitter and other sources might be overwhelming. Stop wasting your valuable time - subscribe now and by the end of every month I will send you an email with the list of the most interesting Ruby / JavaScript (Ember.js) / PostgreSQL articles with a quick overview.