8 Essential Ember.js Interview Questions*

Sketch out the implementation of a master-detail view. A list of bands can be seen on the URL /bands and when one of them is selected, some detailed information is shown about the selected band, while the list of bands remains visible. The URL changes to band/24, where 24 is the ID of the selected band.

Make sure to show the relevant route definitions, route objects and templates.

You can assume you have access to a store object in the routes as this.store and that the store has the findAll(type) and findRecord(type, id) methods to retrieve all objects of a certain type and a specific object of a certain type.

// app/router.js

this.route('bands', function() {
  this.route('band', { path: ':id' });
});
// app/routes/bands.js

export Ember.Route.extend({
  model: function() {
    return this.store.findAll('band');
  }
});
// app/routes/bands/band.js

export Ember.Route.extend({
  model: function(params) {
    return this.store.find('band', params.id);
  }
});
<!-- app/templates/bands.hbs -->
<ul>
{{#each model as |band|}}
  <li>
    {{link-to band.name 'bands.band'}}
  </li>
{{/each}}
</ul>

{{outlet}}
<!-- app/templates/bands/band.hbs -->
<div>{{model.description}}</div>
Fields marked with an asterisk (*) are required
Comment submitted succesfully. Thank you.
We are going to review the comment and get back to you as soon as possible.

How does the above change if the URL structure is to be retained, but the list of bands must no longer be displayed when an individual band is selected? As before, sketch out the route definitions, route objects, and templates.

The routes are no longer nested, but instead are defined on the same level:

// app/router.js

this.route('bands');
this.route('band', { path: '/bands/:id' });
// app/routes/bands.js

export Ember.Route.extend({
  model: function() {
    return this.store.findAll('band');
  }
});
// app/routes/band.js

export Ember.Route.extend({
  model: function(params) {
    return this.store.find('band', params.id);
  }
});
<!-- app/templates/bands.hbs -->
<ul>
{{#each model as |band|}}
  <li>
    {{link-to band.name 'bands.band'}}
  </li>
{{/each}}
</ul>

{{outlet}}
<!-- app/templates/bands/band.hbs -->
<div>{{model.description}}</div>
Fields marked with an asterisk (*) are required
Comment submitted succesfully. Thank you.
We are going to review the comment and get back to you as soon as possible.

Use Ember computed macros to clear up the following class definition.

// app/models/monster.js

export default Ember.Object.extend({
  headCount: null,
  eyeCount: null,
  legCount: null,

  canBreathFire: false,
  canFreeze: false,
  canPetrify: false,

  victories: [],

  isDragon: Ember.computed('canBreathFire', function() {
    return this.get('canBreathFire') && this.get('headCount') > 1;
  }),

  isFake: Ember.computed('canBreathFire', 'canFreeze', 'canPetrify', function() {
    const hasDeadlyPower = this.get('canBreathFire') || this.get('canFreeze') || this.get('canPetrify');
    return !hasDeadlyPower;
  }),

  xp: Ember.computed('victories.@each.xp', function() {
    return this.get('victories').reduce(function(totalXP, victory) {
      return totalXP + victory.get('xp');
    }, 0);
  }),
});

The Ember.computed namespace contains several useful methods that make property definitions more explicit and robust.

In this example, a monster is a dragon if it is multi-headed and breathes fire, so you can define properties for each term and then use Ember.computed.and to determine “dragon-ness”.

Ember.computed.or is used to create a property that resolves to true if any properties in it are truthy. That’s what we use for hasDeadlyPower, which is true if the monster breathes fire, if it freezes its enemies, or if it petrifies enemies.

If a monster does not have a deadly power, it’s not really a monster. We can test for this by simply using Ember.computed.not on the previously computed hasDeadlyPower property.

In our example, the total number of XP is the sum of XPs the monster has earned from each victory. To find this total, we first create an array of these victory XPs, and then sum them together. Ember.computed has us covered for both of these, using Ember.computed.mapBy, and Ember.computed.sum.

// app/models/monster.js

export default Ember.Object.extend({
  headCount: null,
  eyeCount: null,
  legCount: null,

  canBreathFire: false,
  canFreeze: false,
  canPetrify: false,

  victories: [],

  multiHeaded: Ember.computed.gt('headCount', 1),
  isDragon: Ember.computed.and('canBreathFire', 'multiHeaded'),
  hasDeadlyPower: Ember.computed.or('canBreathFire', 'canFreeze', 'canPetrify'),
  isFake: Ember.computed.not('hasDeadlyPower'),
  victoryXPs: Ember.computed.mapBy('victories', 'xp'),
  xp: Ember.computed.sum('victoryXPs'),
});
Fields marked with an asterisk (*) are required
Comment submitted succesfully. Thank you.
We are going to review the comment and get back to you as soon as possible.

Find top Ember.js talent today. Toptal can match you with the best developers to finish your project.

Hire Toptal’s Ember.js Developers

Consider the following definition of a Monster class:

// app/models/monster.js

export default Ember.Object.extend({
  ...
  victories: [],
  ...
})

What’s wrong with initializing the victories property the way it’s done here? How would you fix it?

The problem is that properties defined at the class level (such as victories in this example) are shared across instances of the class. Since victories is a mutable array, any object pushed into it from any instance will be pushed to the property of the class and thus become “visible” in other instances, too:

import Monster from 'models/monster';

let dragon = Monster.create({ name: "Smaug" });
let troll = Monster.create({ name: "Trololo" });

>> dragon.get('victories').pushObject('dwarves')
>> dragon.get('victories') // => "dwarves"
>> troll.get('victories') // => "dwarves", WAT?

Possible solutions involve creating the victories array at the instance level.

For example, when the instance is created:

// app/models/monster.js

export default Ember.Object.extend({
  ...
  init: function() {
    this._super();
    if (!this.get('victories')) {
      this.set('victories', []);
    }
  },
})

Or, possibly, as a computed property:

// app/models/monster.js

export default Ember.Object.extend({
  ...
  victories: Ember.computed(function() {
    return [];
  })
})
Fields marked with an asterisk (*) are required
Comment submitted succesfully. Thank you.
We are going to review the comment and get back to you as soon as possible.

Assume you have a star rating component that displays stars for ratings of different items. When any star is clicked, the set action is fired on the component, with the new rating as the first parameter.

Here is the current component code and its invocation:

// app/components/star-rating.js

export default Ember.Component.extend({
  item: null,
  rating: 0,
  ...
  actions: {
    set: function(newRating) {
      var item = this.get('item');
      item.set('rating', newRating);
      return item.save();
    }
  }
});
{{#each songs as |song|}}
  {{star-rating item=song rating=song.rating}}
{{/each}}

You want your component to be reusable, and to be able to display the rating of anything from a song to the dribbling skill of a soccer player. In light of this, how could the following component definition be improved? How would you use the improved component to display the dribbling skill of a soccer player?

The component should receive a new action name that must be sent “upwards” from the set action:

// app/components/star-rating.js

export default Ember.Component.extend({
  item: null,
  rating: 0,
  setAction: '',
  ...
  actions: {
    set: function(newRating) {
      var item = this.get('item');
      this.sendAction('setAction', {
        item: this.get('item'),
        rating: newRating
      });
    }
  }
});

The setAction name needs to be passed into the component when invoking it from the template. In the first case, let that be updateRating:

{{#each songs as |song|}}
  {{star-rating item=song rating=song.rating setAction="updateRating"}}
{{/each}}

The corresponding updateRating action can be defined on the route (or controller) as follows:

// app/routes/band/songs.js

export default Ember.Route.extend({
  actions: {
    updateRating: function(params) {
      var song = params.item,
          rating = params.rating;

      song.set('rating', rating);
      return song.save();
    }
  }
});

To use the same component to set the dribbling skill of a soccer player, you invoke the component like this:

{{star-rating item=dribblingSkill rating=dribblingSkill.score setAction="updateSkill"}}

The action fired on the route would then be called updateSkill but nothing needs to change in the component’s code. Thus, the component is reusable.

Fields marked with an asterisk (*) are required
Comment submitted succesfully. Thank you.
We are going to review the comment and get back to you as soon as possible.

List the route hooks and actions you know, and give a common use case for each.

Hooks (called after entering):

  • beforeModel
  • model
  • afterModel
  • redirect
  • setupController
  • renderTemplate

Actions:

  • willTransition
  • didTransition
  • loading
  • error

Bonus points if the candidate mentions these hooks:

  • activate (after redirect)
  • deactivate (first hook when exiting a route)
  • resetController (called after deactivate when exiting a route)

Use Cases for Hooks


beforeModel

This is the place to put logic before the route loads any data.

Examples might include:

  • Transitioning to a login route if the user is not logged in
  • Transitioning to a particular route from the application route (e.g. redirect to /bands from /)

model

Fetches the main data backing up the route’s template.


afterModel

This is the place to put logic after the route has loaded its data.

Examples might include:

  • Banning a user from seeing a resource they have no access to
  • Redirecting to the detailed post view if there is only one post

redirect

This can be used for the same purpose as afterModel, the only difference being that transitioning in redirect does not invalidate the current transition, and thus does not rerun the previous hooks.


setupController

Sets up data other than the model on the controller.


activate

Called when the route has been fully entered.

One use case might be setting a CSS class on the body tag so that a modal becomes visible.


deactivate

Called when the route has been exited

A possible use case is resetting the CSS class that was set in the activate hook. In general, this is used to reset what was done in activate.


resetController

This hook is called when either the route is exited or its model changes (switching from one post to the next). These two cases are distinguished by the second argument of the method, which is true when the route is exited.

Since route-driven controllers are singletons in Ember, this is the best place to reset properties on the controller that we don’t want to persist when the route is exited. A valid use case might be resetting a counter to zero, or flipping back a switch that designates the status of a UX flow.

Use Cases for Actions


willTransition

This action is fired before the router starts the transition, which gives the developer the possibility to conditionally abort it.

Consequently, it might be used to warn the user about losing unsaved changes, for example.


didTransition

This action is fired on the destination route when the transition has fully completed. Setting the document’s title is a possible use case for this route action.


loading

If a model hook returns a promise, that promise must be resolved before the corresponding template can be rendered. When the model hook returns a promise, a loading action is fired on the parent route (so for bands.band, the loading action is fired on bands), which, by default, renders a loading template under the parent (in the given example, bands.loading). This default behavior can be overridden.


error

If one of the model hooks (beforeModel, model or afterModel) throws an error, an error action is fired on the parent route (so for bands.band, the error action is fired on bands), which, by default, renders an error template under the parent (in the given example, bands.error). This default behavior can be overridden.

Fields marked with an asterisk (*) are required
Comment submitted succesfully. Thank you.
We are going to review the comment and get back to you as soon as possible.

Explain what the “Data Down, Actions Up” (DDAU) concept is and what problem it solves.

DDAU is a best practice for data communication between components and the context in which they are used. The data is passed into components from their context (“data down”) by the component invocation in the template. When a user action makes it necessary to change that passed-in data, instead of mutating the passed-in data, the component should send an action (“actions up”) to its context. That action will trigger a change in the data passed into the component, and thus, the component’s state will change (potentially resulting in redrawing some parts of the component’s DOM representation).

DDAU was conceived to replace context-component communication via two-way bindings. Two-way bindings are used heavily in Ember, but they have a few issues, and it is now considered poor practice to rely on them. Mutating data at any nested level in the component hierarchy leads to scenarios that are hard to debug, because there are lots of places a piece of data given to a component can be changed. DDAU makes the flow of data very explicit and easier to comprehend.

Fields marked with an asterisk (*) are required
Comment submitted succesfully. Thank you.
We are going to review the comment and get back to you as soon as possible.

Explain what the each template helper does and show a few ways it can be extended/customized.

The #each helper iterates through an array and calls its block with each of its items. It is typically used to render a list of things.

{{#each bands as |band|}}
  {{band.name}}
{{/each}}

An else branch can be provided that will be rendered when the array is empty.

{{#each bands as |band|}}
  {{band.name}}
{{else}}
  No bands here, move along.
{{/each}}

Bonus points

An index may also be provided as the second parameter of the block:

{{#each bands as |band index|}}
  {{index}}. {{band.name}}
{{else}}
  No bands here, move along.
{{/each}}

Furthermore, a key may be given that tells Ember whether a DOM representation of an item can be reused or needs to be rerendered (i.e., whether the item has changed). This can improve rendering speed.

{{#each bands key="name" as |band index|}}
  {{index}}. {{band.name}}
{{else}}
  No bands here, move along.
{{/each}}
Fields marked with an asterisk (*) are required
Comment submitted succesfully. Thank you.
We are going to review the comment and get back to you as soon as possible.
* There is more to interviewing than tricky technical questions, so these are intended merely as a guide. Not every “A” candidate worth hiring will be able to answer them all, nor does answering them all guarantee an “A” candidate. At the end of the day, hiring remains an art, a science — and a lot of work.
Submit an interview question
Submitted questions and answers are subject to review and editing, and may or may not be selected for posting, at the sole discretion of Toptal, LLC.
All fields are required
Thanks for submitting your question.
Our editorial staff will review it shortly. Please note that submitted questions and answers are subject to review and editing, and may or may not be selected for posting, at the sole discretion of Toptal, LLC.
Sergey Kutsko
Ukraine
Sergey is an excellent developer and a responsible and proactive team member. He learns quickly and is very results-oriented. He is passionate about creating great software and has a strong record of going above and beyond for his clients to ensure rapid product delivery and high performance.
Graham Powrie
United States
Graham is a designer and developer focused on creating custom web applications that automate and improve business processes, while remaining flexible for iteration. He's a full-stack developer who can deliver a complete product from start to finish individually, or alongside a team.
Dustin Farris
United States
Dustin has a burning desire to create new things, and he has boundless energy. He loves all aspects of modern development and treats his talents as art. From building scalable applications to managing complex datasets, his experience has taught him to always think outside the box.