This Ember style guide recommends best practices so that real-world Ember programmers can write code that can be maintained by other real-world Ember programmers. A style guide that reflects real-world usage gets used, and a style guide that holds to an ideal that has been rejected by the people it is supposed to help risks not getting used at all – no matter how good it is.
The guide is separated into several sections of related rules. Rationale for each the rules is provided unless it's fairly obvious.
- Use a consistent structure in your class definitions.
// import statements go first
import Ember from 'ember';
// namespace deconstruction
const { Component } = Ember;
// default export
export default Ember.Component.extend({
// body omitted
});
- Deconstruct local namespaces. This pattern also serves as a handy tool for developers to know what you actually used in a file.
import Ember from 'ember';
// bad
export default Ember.Component.extend();
// good - single namespace
const { Component } = Ember;
export default Component.extend();
// bad
export default Ember.Component.extend({
foo: Ember.computed('bar', function() {
// return something...
})
});
// good - newline multiple namespaces
const {
Component,
computed
} = Ember;
export default Component.extend({
foo: computed('bar', function() {
// return something...
})
});
- Deconstruct methods with care.
// bad
const {
Component,
moment: { add }
} = Ember;
export default Component.extend({
session: service();
});
// good - Ember Core methods can be safely deconstructed
const {
Component,
inject: { service }
} = Ember;
export default Component.extend({
session: service();
});
- Use a consistent structure for route handlers.
export default Route.extend({
// service declarations
// lifecycle hooks in order of execution
model() {},
setupController() {},
// custom functions
// actions separated by a newline
});
- Pass models to controllers using descriptive name on an
attrs
hook. This distinguishes items passed to the controller from items on the controller itself and avoids questions of which model is being passed in.
// bad - it is unclear which model the controller has been passed
export default Route.extend({
model() {
return this.store.findAll('contact');
}
});
// good
export default Route.extend({
model() {
return this.store.findAll('contact');
},
setupController(controller, model) {
controller.set('attrs.contacts', model);
}
});
// good - use RSVP.hash for multiple models
export default Route.extend({
model() {
return RSVP.hash({
followers: this.store.findAll('follower'),
following: this.store.findAll('following')
})
},
setupController(controller, model) {
controller.set('attrs', model);
}
});
-
Handle application state in routes and component state in components.
-
Rollback attributes on model edit route exit.
-
Use controllers to decorate the model, handling query params, and where reduced component nesting noticeably improves performance on mobile devices.
-
Use a consistent structure for controllers.
export default Controller.extend({
// passed properties
attrs: {},
// computed properties
sortedContacts: computed('attrs.contacts.[]', function() {
return this.get('attrs.contacts').sortBy('createdAt').reverse();
}),
});
- Use a consistent structure for components.
export default Component.extend({
// service declarations
// native component properties
tagName: 'nav',
// passed properties
attrs: {},
// custom properties
foo: 'foo',
// lifecycle hooks in order of execution
init() {},
didReceiveAttrs() {},
didInsertElement() {},
// computed properties
fancyFoo: computed('foo', function() {
// body omitted
}),
// native functions
// actions separated by a newline
actions: {
doFoo() {
// body omitted
},
doBar() {
// body omitted
}
}
});
- Set tag name. Components don’t need to be wrapped in a div as they are by default. Use the
tagName
property to modify a component before wrapping it.
// good
export default Component.extend({
tagName: 'nav'
});
-
Don't over nest your components. Consider if your nesting adds better details or obfuscation for new developers. Heavy DOM nesting negatively impacts performance on mobile devices.
-
Align properties in Handlebars.
- Use a consistent structure for models.
export default Model.extend({
// service declarations
// attributes
title: attr('string'),
body: attr('string'),
// relationships
comments: hasMany('comment'),
author: belongsTo('user'),
// computed properties
fancyTitle: computed('title', function() {
// return something
});
// custom functions
});
-
Declare and use an application serializer and inherit from it when changes are required.
-
Declare and use an application adapter and inherit from it when changes are required.
-
Avoid importing properties from other files. For example, a constant on Model A should not be imported to Model B in most cases;
- Newline after mixin declarations.
// good
export default Component.extend(
Mixin, {
// body omitted
});
// good
export default Component.extend(
Mixin1,
Mixin2 {
// body omitted
});
- Use a consistent directory structure.
# good
/mixins
/models
/routes
/components
- Name actions descriptively.
// good
launchConfirmDialog
submitConfirm
cancelConfirm
-
Don't use jQuery accessors.
-
Leverage hardware accelerated properties for animations. For example, use Liquid Fire for transitions.
-
Within Cordova/Phonegap applications, abstract general Cordova to a service.
-
Return promises. It's a good thing.
The Ember Style Guide takes inspiration from the Ruby Style Guide and the Rails Style Guide while the introduction is credited to those guides.