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 NowSketch 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>
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>
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'),
});
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 [];
})
})
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.
Hooks (called after entering):
beforeModel
model
afterModel
redirect
setupController
renderTemplate
Actions:
willTransition
didTransition
loading
error
Bonus points if the candidate mentions these hooks:
-
activate
(afterredirect
) -
deactivate
(first hook when exiting a route) -
resetController
(called afterdeactivate
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.
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.
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}}
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.
Looking for Ember.js Developers?
Looking for Ember.js Developers? Check out Toptal’s Ember.js developers.
Jakub Kanitra
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 MoreDusan Stanojevic
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 MoreEyüp Atiş
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 MoreToptal Connects the Top 3% of Freelance Talent All Over The World.
Join the Toptal community.