Ember Best Practices: Extend vs Mixin

mix
Romina Vargas

Developer

Romina Vargas

Code duplication: can’t live with it, can live without it. As developers, we’re always trying to find ways to DRY up our code. No matter how big or how small an application, code duplication manages to sneak its way into your files. So far in this Ember best practices series, we’ve learned how to write DRYer computed properties and DRYer tests. What about more general application code?

In Ember, there are two main concepts that we can adapt in order to share code between different parts of our application: Ember.Mixin and Ember.Object.extend(). Both of these essentially achieve the same end result, but depending on the scenario, one may be a better bet. Let’s look at a brief overview of each one.

Ember.Object.extend()

Deep down, all Ember objects that we create and interact with are an extension of the Ember.Object. Ember.Object itself extends Ember.CoreObject, which includes the Ember.Observable mixin, and it’s what gives Ember its “properties and property observing functionality.” Without these base objects, there would be no Ember.

We can create a new subclass by calling the extend() method on any Ember object. This will give the new subclass all of the properties of the parent class, as well as any properties defined in the new one. You’re also allowed to overwrite any properties found in the parent by defining them in the new class. If you’ve written an Ember App, you will be familiar with the following:

// routes/index.js

export default Ember.Route.extend({
  // properties and methods
});

In this case, IndexRoute will inherit properties, methods, and anything else defined in the Ember.Route object, as well as everything down its prototype chain:

IndexRoute -> Ember.Route -> Ember.Object -> Ember.CoreObject

Keep in mind that any properties defined in the parent object will be shared among any child object. For this reason, you may want to initialize certain properties inside the init constructor, which gets called whenever an instance of an object gets created. This way, each object instance gets its own unique set of properties.

Ember.Mixin

Unlike the Ember.Object, mixins don’t get extended. Instead, they get created via Ember.Mixin.create(). When we include the mixin inside an Ember object, we’re extending the constructor’s prototype. What this means is that like using extend(), any properties or functionality that is defined inside the mixin will be shared among all classes containing this mixin.

// mixins/foo.js
export default Ember.Mixin.create({
  bars: []
)};
// components/A.js
import FooMixin from ../mixins/foo’;

export default Component.extend(FooMixin);
// components/B.js
import FooMixin from ../mixins/foo’;

export default Component.extend(FooMixin);

The above will result in both component A and component B sharing the same bars array. Any changes made to the array will be reflected in both components. If you want to play around with this sharing business, you can go here!

Mixin me, Mixin me not…So which do I use?

Ultimately, the decision will differ on a case by case basis. We can think of it in terms of inheritance vs composition.

A base class can usually live on its own or be extended. Inheriting from a parent object will provide the child with the full functionality of the parent, plus any additional properties and behavior that are specific to the child. The child should behave like the parent. Extending works well when you need several variations of a parent object. Take a form, for example. You may have differing templates, but will have mostly the same component logic. Extending each component from a base class would make sense, and their own component-specific properties would be defined in the new subclass.

The thing about mixins is that they encapsulate succint pieces of functionality. The code that they contain can be reused throughout different parts of the application, and is not a concern of any one route, controller, component, etc. They’re also meant to be used with other objects, not as a stand alone piece; they don’t get instantiated until you’ve passed them into an object, and they get created in the order of which they’re passed in. Mixins can help to avoid long chains of inheritance and adds flexibility if the need for making changes arises.

Mixin example, plz

A good candidate for a mixin? Pagination! Pagination logic is not very extensive and it can be sprinkled throughout an application. Applications tend to need pagination for displaying a number of things; a mixin provides us with the ability to add this to different routes, without caring about the type of model or object that we’re trying to display on the page. Let’s say we have the following routes:

export default Ember.Router.extend({
  this.route('authors');
  this.route('blog', function() {
    this.route('comments');
  });
});

All of these routes need to have pagination built-in. Even though the routes are not related, we can utilize the same pagination mixin for all of them. Each of these three routes will have to include the mixin like so:

import PaginationMixin from '../mixins/pagination';

export default Route.extend(PaginationMixin);

Extend example, plz

A good candidate for extension? Authentication! More often than not, we need two different types of routes: authenticated and unauthenticated. The best way to handle this is to create a base class for each of those (or just one, depending on your needs!), and extend the routes which need the particular functionality. If we have the following routes,

export default Ember.Router.extend({
  this.route('blog');
  this.route('account', function() {
    this.route('profile');
    this.route('settings');
  });
});
// routes/authenticated.js

export default Ember.Route.extend({
  // check for session & transition accordingly
});

in account/index.js we would just extend authenticated.js

// account/index.js

import AuthenticatedRoute from 'routes/authenticated';

export default AuthenticatedRoute.extend();

Doing this will make sure that account and any child route is off limits to users who are not signed in. We simply apply this same pattern to any other parent routes that need authentication. If we were to use a mixin instead, we would end up with having to import the mixin all over the application - and you’re more likely to forget to add it.

Sum it up!

Hopefully this has helped in better understanding the differences between using Ember.Object.extend() and Ember.Mixin as it’s not always black or white on whether to use one concept over the other. Ask yourself what the intent of the code is, where and how often it’s going to be used, and make your best judgement. All in all, stay DRY!

Newsletter

Stay in the Know

Get the latest news and insights on Elixir, Phoenix, machine learning, product strategy, and more—delivered straight to your inbox.

Narwin holding a press release sheet while opening the DockYard brand kit box