What are Controllers used for in Ember.js?

Few people use controllers to their fullest potential. Remember when the Wii came out, and we were all so excited to play WiiSports, and then no other games lived up to the dream? Let’s not fall prey to the same patterns. Learn when you should use Controllers in your Ember app and some core features that are available.

We’ll cover:

  • The essentials of what controllers are
  • What query parameters are
  • How controllers are defined outside Ember
  • Whether it’s ok to use controllers
  • When to make a controller
  • The relationship between routes and controllers

This article applies to Ember 1.13 onwards and was written as of 3.1.

The essentials to know

A Controller is a file where you can define URL Query Parameters, as well as actions and attributes that need to be shared with child Components. A Controller is usually paired with an individual Route. For example, if I have a blog-posts Route, I could also create a blog-posts Controller by doing ember generate controller blog-posts. Any attributes or actions that I want to share with components used on that Route could be defined on the Controller and passed down through the template.

The way I build my apps, the only things in a Route are the model hook and some setup in lifecycle hooks like didTransition.

You can see a basic functional example of controllers in use here in this Ember Twiddle (a site that’s like JSbin or Codepen for Ember). If you want to see some real-world controllers, check out some open sourced production apps.

Here’s an example of a Controller file:

Example of a controller with query parameters and an action that is passed down to a child component

For reference, the section on Controllers for The Guides is here. To make the most of Controllers, you’ll be spending a lot more time on their page in the API Docs.

What are query parameters anyway?

“Query Parameters” are the part of a URL that comes after a question mark. For example, if I want to look at all the issues for the Ember Learning Organization on GitHub, the url looks like this: https://github.com/issues?utf8=%E2%9C%93&q=is%3Aissue+org%3Aember-learn+

Have you ever experienced shopping for something online where a page refresh wiped out your search filters? That sure is annoying. Query Parameters are a common way to solve this problem. You can “save” filters or display toggles in the URL itself. That way, when a site visitor hits refresh, your app can grab the value of the Query Parameter and reapply the filters. The user sees the same thing. Their previous selections aren’t lost.

Whenever someone is talking about Query Parameters in Ember, they could be talking about one of two things:

  1. Query Parameters that the user sees in the URL, which are managed by the Controller. Read up on them in The Guides.
  2. Query Parameters that are used when making API requests, such as making a GET request to https://jens-awesome-api/blog-posts?limit=10 . You can learn more about how to use Query Parameters for back end request URLs in here.

There’s no automatic overlap between the query parameters in the user’s url and the query parameters used for API requests.

What is a Controller in general programming terms?

In general programming, there’s a concept known as MVC — Model, View, Controller. The definition of each term varies across tools and frameworks, but generally, the View is what the user sees, the Controller is what responds to interaction by modifying Model data/state, and then the View reacts to the data updates and displays the result.

Although many frameworks used to describe themselves in MVC terms, that’s not really a good term to use today to describe most modern frameworks, including Ember. With the rise of component-based development, where view and data state are tightly interlocked, it’s just a comparison that would be confusing to new developers. However, it’s useful to know the roots of the word so that you can remember what an Ember Controller is for. It can modify state like Query Parameters and receives user input.

Is it ok to use controllers?

Yes. If you read somewhere else that you shouldn’t use them because “they are going away,” that’s outdated information that is now considered bad advice. Please use controllers. It’s official.

When should you make a Controller?

This is somewhat opinion-based. Although Ember offers some good conventions and best practices, ultimately it’s up to you to figure out which files in your app need to be able to talk to other files, how you will save state, how DRY (“Don’t repeat yourself”) you want the code to be, etc. But I’ll try to give some guidelines:

Create a controller when…

  1. You need to use query parameters in the URL that the user sees (such as filters that you use for display or that you need to send with back end requests)
  2. There are actions or variables that you want to share with child components
  3. You have a computed property that depends on the results of the model hook, such as filtering a list

Some people like for their apps to have all API requests go through actions defined on the Controller, but that’s not strictly necessary. Components can load and modify data too. It’s just an architectural decision.

The relationship between Routes and Controllers

It’s important to note here that by default, if you put actions, variables, etc in a route’s JavaScript file, they aren’t available to pass down to child components. Furthermore, in a Route, if you do this.get('model') to try to use the results of the model hook, you’ll just get the function itself, not the data records it returns. However, the Controller has access to the results of the model hook (the data), and anything defined in the controller can be passed to child components.

What’s going on here? There’s a bit of a circular relationship between controllers and routes.

In the Route, the model hook receives an object called params , which can contain things like the dynamic segments of a url (the “3” in https://jens-app.com/blog-posts/3) and the query params.

model(params) {
console.log(params.blogPostTopic)
return this.store.findAll('blog-post')
// to use the query params in an API request, see docs for query and queryAll linked below
}

If you want the model hook to run again whenever the query parameters for that route change, you need to specify that in your route JavaScript, using refreshModel:true. If you’re using queryRecord or query, there’s a good chance you want the model to refresh!

queryParams: {
blogPostTopic: {
refreshModel: true
}
}

The results returned from the model function are then passed to the Controller. That’s why you can use this.get('model') in the Controller and then do work, like create computed properties based on the data records that came from the model. Next, the Controller passes its attributes and actions to the Route’s Handlebars template, where they can be shared with child components:

{{my-component someControllerAction=(action 'someControllerAction') someControllerVariable=someControllerVariable}}

Now, I mentioned earlier that usually a Controller goes together with an individual Route. However, there are ways to have Controllers inherit from other Route’s Controllers, as well as inject a Controller into other places. In my experience, the results of injecting Controllers in other places can be unexpected, so it’s a really good idea to read the API docs closely before you start trying to share a Controller’s state with other Routes. I once trolled myself because I thought I was changing state on an existing Controller, but really I had accidentally made a new instance of a particular Route’s Controller. Oops.

One step further

Earlier, I tried to answer the question, “What are controllers?” However, depending on who you talk to, the answer can vary greatly. Controllers do a lot behind the scenes — for example, every route technically has a controller that goes with it. When you create your own controller file, you’re just accepting the opportunity to add some more attributes or override default behavior.

It’s kind of like if you asked “What are pants?” I will say, “Clothes that cover your butt.” It would also be valid to answer “A protective layer composed of many different materials, which could be extended to the ankles.” You can try to define something technically, according to its responsibilities within the app, or by how it’s used by most people, and I’ve chosen the latter.

As you learn Ember.js, most of the time, all you need to know is how the files you personally work with fit together and some of the most common API methods. Later on, experienced developers tend to pick up more of what’s actually happening behind the scenes, so more knowledgeable people sometimes answer questions from the behind-the-scenes angle instead. You don’t actually need to know much about this second level in order to be good at building Ember apps. It’s similar to how you can be great at a video game without understanding the computer science logic of the armor or AI. Sure, if you want to be #1 at Call of Duty, you might have to study the nuts and bolts, but the rest of us can have a perfectly good time without peering behind the curtains.

Conclusion

Although it’s my most boring article to date, my hope is that this can serve as a reference that other people can link to when someone has questions. It’s also an exercise to see where the holes are in The Guides so I can fix them.

Most of the non-Guides knowledge in this article was shared with me by people like locks, alexspeller, bendemboski, rwjblue, and spikedkira over the past two years. Many thanks to the kind and generous developers who help others on Ember Discord, the Forums, and Stack Overflow ❤

If I made any mistakes, oversimplified, etc, please shoot me a message. Thanks for reading!


Jen Weber builds apps for science at BioBright. She’s a contributor to Ember.js, an open source, front-end framework that provides shared solutions for tough problems, where development is guided by the community rather than any one company. She works to transform tech into an industry that welcomes new people.