Leveraging Computed Properties in EmberJS

Modern-day applications often consist of complex data layers, backed with restful APIs and/or complex back-end systems to provide the services. Fortunately for us, it’s very simple today to create such applications using modern fronting framework and tooling. Frameworks like AngularJS, EmberJS, Aurelia, amongst many other frameworks, have demonstrated that the data logic can be extracted to a simpler layer, letting the user deal with transactions, and not deal with API configuration. Building an application with this approach can often yield to very high development velocities, at the risk of increasing technical debt; frameworks like these greatly simplify tasks, but require the entire development team to understand the given framework. In many cases, using a complex framework can result in a lot of confusion, given that resources are often disparate, or even nonexistent. Such is the case, I found, for ember data, a library to simplify data transactions using EmberJS.

In this write-up, I hope to show you how to leverage Ember Data’s computed properties. Understanding how computed properties work in ember is key to simplifying your application. By having data layer which performs most operations, you can easily avoid groundwork for initialization of their models.

A refresher on computed properties

A computed property is a property which is computed in real-time. Computed properties are not sent the server, when the model was saved, deleted, or modified. For this reason, it is important to understand that not everything should be a computed property. In most cases, it’s best to simplify the data layer as much as possible, without restricting performance. (Recall that computed properties are dependent on key values, and therefore can cause a lot of performance issues, and headaches when tracing performance).

The canonical example of a computed property is creating someone’s full name. As you are well aware, a person’s full name is the combination of their first, middle, and last names.

On the official EmberJS website, here is the example they provide:

Can you see what’s wrong? What if the user doesn’t have a first name? Suppose we only type in “Renner” in the input field bound to lastName. fullName would simply result in Renner.

Using notEmpty to complete the fullName

What we really need is a way to check if both the firstName and the lastName are present? Then, if they are, set the fullName? A naive approach would be to use observers (now depreciated):

By opting for an observer instead of a computed property, we allow fullName to be sent to the server and saved in the data model, which may or may not be a good thing. In this case, it makes very little sense, however, to have fullName as part of the data model, as fullName can always be found by looking at firstName and lastName of the given model. Besides, observers suck.

A better solution would be to use Javascript’s Array type to group all the names of a given candidate. This can be done with a combination of Array.splice() and Array.concat(), with a safeguard using Ember.makeArray():

Using Ember.computed.oneWay

To address some performance issues, users have recently taken refuge in ember’s new one-way data binding features. Starting in Ember 2.0, one-way data bindings are default, and the user must explicitly opt in to two-way bindings. Using one-way data binding reduces the complexity of a given computed property, and prevents changes from propagating in the other direction. For example, if we are tracking the length of a given has many relationship, we me opt to do so in the following manner:

models/applicant:

Using the computed method notEmpty works as expected. However, this means that Ember’s Run loop must execute the method computed.notEmpty every time a new job is added to the applicant.

A better solution would be to leverage Ember’s oneWay data binding. This way, we can bind to the length attribute, without having to worry about propagating changes to the length attribute of jobs. Just like in the previous case, using Ember.computed.alias, we can use an macro key to get the property we are interested:

Using this approach will be faster because the length property of jobs will always return an integer (because deep down, jobs is an instance of Ember’s ArrayProxy); in the case that the jobs array is empty, it will return 0, and 0 in Handlebars evaluates to false.

Common Ember.Computed Mistakes

Combining Arrays

In many cases, you might find yourself trying to combine arrays. For example, suppose you’re building an application to perform event planning and to manage events such as weddings, corporate events, and private events. In building such an application, you would have to maintain a list of people both going to the events and people not going to the events. For each event, you would typically have a list of all attendees to an event, and a list of all attendees not attending an event, and you might want to combine these attendees. A naiive approach would be to have a property on the given component or controller which represents each group of attendees:

Thus, the logic to add or remove attendees would look like this:

Can you see the problem here? Although this approach will work, maintaining two separate lists of attendees forces us to carry out two separate operations when the add or remove a given attendee. Wouldn’t it be easier if we could just add or remove a given attendee in one line?

A better approach would be to use the Ember.Computed.filterBy computed property helper. In fact, the solution is to not combine arrays at all; instead, we can keep all the attendees in one array, and filter out the attendees which are or aren’t going:

Thus, the logic to how to remove a given applicant would only be one line:

But wait! Can you still see that there are some problems? We can further optimize this code and remove both rsvp() and unRSVP() actions, replacing them with a single toggleRSVP(). This is easily done with Ember’s toggleProperty method:

And here, instead of having a separate button for RSVP and for unRSVP, we can simply find the current value of the attendee’s status to the template:

templates/components/event.hbs

Creating Records the Right Way

When I first started using Ember Data, I didn’t understand why saving records was so tedious. Using any model that leveraged composition, I would often find myself calling the save() method several times, within 4 promises deep. Here’s an example of one of my save() methods:

The reason this got complicated so quickly is because I had dependent models (using the relationships DS.hasMany, DS.belongsTo, and so on. In other words, I had to createRecord the dependent record before creating the parent record.

ES7 Async to the Rescue

One way to deal with the 4 levels deep of promises is to use ES6 generators and the ES2016 (ES7) extended promise await API. Generators are a new feature in ES6 which allow asynchronous operations with the keyword yield. At compile-time, Babel (the ES6 transpiler) searches for every declartion of the keyword yield, and those occurences thereafter act as return points of each succeeded promise that is returned from the function.

Like generators, the await keyword awaits the result of a given promise, and does not execute the next yielded value until it’s resolved.

Generators are actually functions in disguise, with the exception that insted of return, they use yield, to indicate the success of a promise.

Let’s re-write the complex saveVehicles() method with a generator:

In order to use ES2016 features, we need to tell Babel:

ember-cli-build.js

Then, on your command-line:

In his blog post, Damian explores usage of ES2016/ES7 in far greater detail, and I highly recommend you read his post for more information.

Addendum: Async Computed Properties

In some cases, you may need to compute values in a model based on other models, as described above in the saveVehicles example. In these cases, a typical, hacky solution might involve proxying out the promisy value using something like Ember.computed.alias, and then using a long chain of then, catch, and finallys. For these cases, luckily, I have discovered Ember CLI Dispatch.

Here’s a trivial example of ember-cli-dispatch‘s usage, for getting parameters for a given item:

In this example, the metadata for the object is only resolved after the component is loaded. The above assumes you’ve set up your config/environment.js file with your adapter endpoint.

But again. do you see a problem with this? Why not refactor this to occur within the Model?

app/models/module.js:

Then, as soon as module.metadata is referenced (because all relationships are async unless you opt out in Ember 2.0+), Ember will make the async request, only when needed.

But most of all, metadata should really be a part of the object payload, as specified in the ember data docs.

Altogether, I’m still not convinced there are many use cases for something like Ember CLI Dispatch, as most logic can be done in the model, and lazily computed on demand. In any case, you should now have the resources to cleanly compute, create, and update your Ember Data components.

Join The Conversation

Your email address will not be published. Required fields are marked *