8 Essential Ember.js Interview Questions *

Toptal sourced essential questions that the best Ember.js developers and engineers can answer. Driven from our community, we encourage experts to submit questions and offer feedback.

Hire a Top Ember.js Developer Now
Toptal logois an exclusive network of the top freelance software developers, designers, marketing experts, product managers, project managers, and finance experts in the world. Top companies hire Toptal freelancers for their most important projects.
1.

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.

View answer
// 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>
2.

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.

View answer

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>
3.

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);
  }),
});
View answer

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'),
});
4.

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?

View answer

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 [];
  })
})
5.

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?

View answer

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.

6.

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

View answer

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.

7.

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

View answer

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.

8.

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

View answer

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}}

These sample questions are intended as a starting point for your interview process. If you need additional help, explore our hiring resources—or let Toptal find the best developers, designers, marketing experts, product managers, project managers, and finance experts for you.

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

Looking for Ember.js Developers?

Looking for Ember.js Developers? Check out Toptal’s Ember.js developers.

Jakub Kanitra

Freelance Ember.js Developer
Slovakia
Toptal Member Since January 17, 2017

Jakub has worked as sole front-end coder, as a manager leading a team of five coders, and as a part of big team (ten developers). He created the front-end of a successful Slovak startup OrderLord in Ember.js, and now he's working on George, which is an SPA app of Erste Group's internet banking app written in Backbone.

Show More

Dusan Stanojevic

Freelance Ember.js Developer
United States
Toptal Member Since November 29, 2021

Dusan is a software engineer at heart who loves programming and problem-solving. He's been writing code constantly for the last nine years and doesn't plan on stopping any time soon. Dusan has been in charge of overseeing all of the engineering efforts in a bay area startup for over four years, and he'd love to share his experience helping other companies grow and implementing incredible products.

Show More

Eyüp Atiş

Freelance Ember.js Developer
United Kingdom
Toptal Member Since August 14, 2018

Eyüp is a seasoned software developer with over six years of hands-on experience with startups and digital agencies. He mainly specializes in web technologies such as Ruby, Ruby on Rails, Ember, and React, but he's also curious about mobile app development. Eyüp has worked across the stack, volunteered to mentor at Rails Girls Ankara, is active in the Turkish Linux community, and has the ability to lead teams.

Show More

Toptal Connects the Top 3% of Freelance Talent All Over The World.

Join the Toptal community.

Learn more