I am a relatively new Ember developer, and after digging through dozens of docs and articles I still do not have a full understanding of the purpose of controllers inside an Ember application. Many past discussions have alluded to an effort to completely move away from controllers in favor of routable components, an idea which, from what I have gathered, has been largely abandoned or at the very least put on hold (please correct me if I’m wrong).
The Ember documentation states that “controllers are still integral part of an Ember application architecture, and generated by the framework even if you don’t declare a Controller module explicitly.” However, controllers are not referenced in this diagram of core ember concepts, and the documentation page for controllers is very short and does not make it clear to me when to explicitly define my own controllers. In the project I have been contributing to, the few controllers that are present are mostly empty, and my peers on the project avoid creating controllers, instead relying mostly on components.
I’d really appreciate some clarity on the role of controllers in an Ember application, what logic a controller handles that cannot or should not be handled by the route or component files, and a concrete example of when defining a controller would be beneficial or necessary.
A controller is where you want to manage all your UI related state. Granted you could do it all in components, but Ember convention is that any state that your route’s template requires should go in the controller. This includes properties, computed properties, and actions that modify those things or bubble up to the route. For example if you want to define a computed property that the template can access you can’t do it in the route (the template can’t “see” it), so you’d have to either have to have a component which acted as your route template/controller or do it in the controller. Any services which the template reference must also be injected in the controller.
Controllers were kind of discouraged, but you’re right in that they’re now not going away any time soon, so using them is recommended for things that make sense. Again this is mostly just state management for the UI. For example if you had a select and you wanted to bind the value of the select to a property you’d want that property to be on the controller. As far as when to define a controller, any time you need extra CPs or processing for the UI state. Basically any js bits that aren’t related to the transition/data fetching (i.e. the route) and that aren’t encapsulated in a component.
So by this convention, each route should have an associated controller?
I guess I just don’t understand that if, as you say, all UI state related logic can done in components, then why use controllers at all? Why not encapsulate all of that logic into components? It seems to me it would make sense to encapsulate as many UI bits into components as you can, creating easy building blocks for the app.
Technically all routes do have controllers, but if you don’t define one of course it’s autogenerated and only gets the model and maybe one or two other things injected. I’d say you only need to define them if you need any js bits for your route template.
I guess compared to something like React where everything is a component controllers could seem odd, but I think they make sense in the Ember world (which includes a router, etc. by default) especially when considering the evolution of the framework.
A lot of that state may end up being in components, but it doesn’t always make sense. I personally tend to write a route template and controller and then abstract components out as necessary. Components hierarchies can add a lot of complexity if they are unnecessary or if you abstract too soon.
As an example of where I use a simple controller in one of our apps… we have an index route for users. You can search, page left/right, use a couple filters, and there’s a button (just a link-to) to create a new user. The pagination controls are a component, and so are some of the filter controls, however they send actions up to the controller, which manages the state (in this case the filter/search/page parameters which are tied to query params). When you set one of those props on the controller, it updates the query params for your route, which refreshes the model. So while there are components involved the controller coordinates the state in relation to the query params. I also tend to put the bulk of my actions in controllers instead of the route and leave the route as more strictly for transition/data fetching related operations.
In other controllers which are more complicated I also have some computed properties which I put in the controller, they observe the model directly and… say… filter it, and then that filtered list is rendered by a component or group of components. Sure, you could pass the model directly into a top level component, but then you have one more component which is essentially just doing what the controller is already there for. And if you’re not using that exact same component anywhere else you’re not getting any reusablity out of it.
Ahhh, now it’s starting to become more clear. The way I understand it, a controller is essentially the top-level component for a route, but it’s not really reusable in the sense that other components are. I guess this explains why the documentation states “Controllers behave like a specialized type of Component that is rendered by the router when entering a Route.”
So in the grand scheme of the data down, actions up paradigm, are controllers the base from which data flows down, and to which actions flow up?
Yup that’s pretty much it. And if you did want to reuse a controller that would be a good candidate for abstracting into a component.
I’d say typically yeah this would be the “base” for DDAU, though that’s more of an art than a science sometimes and it really depends on who “owns” the data. In probably the vast majority of cases the route/controller will “own” the data and so the actions will be passed up to them where the data will be mutated and passed back down.
I think this part is critical. Components are an abstraction for a particular piece of UI. Like any abstraction, they should not be the first tool you grab. It’s easy to pre-optimize and think you understand how a component will look, but it’s good to hold off a little. Laying things down in a controller/template is a good place to flush out your thoughts. Patterns will become a lot clear once you have something that actual works.
This is another great point. The line between a route/controller/component can be really fuzzy sometimes. The general rules I follow are:
Routes are for the network layer (ie: the fetching and saving of models)
Controllers are for managing UI state, typically between and for components
Components for the repeatable or clearly isolated aspects of a UI. (They can be a lot more, but for the general rules this works)
In case it didn’t make it in here (I scanned the answers quickly) - don’t forget that the component will be torn down and wont maintain a state. The example of a filtered list is a really good one to see both angles. If you use a top-level controller for that / it might be a longer file - but it’s less wiring up and is a singleton, which will maintain it’s state for the life of the application. If you were to use a component you’d need to take another step to save the filter parameters to a service or top-level controller (which are singletons) and then pass them in each time the component is rendered. There are use-cases for both / but in my experience - I reach for components too early.