Migrating Controllers to top-level Components in Ember
Tags: ember, javascript, webdev
Published: 2021-04-06 13:25:33

## Introduction

A while ago I came across the following tweet

{% twitter 1361347296605200393 %}

After reading it, I had a few flashbacks to discussions in the community about routable components taking the place of controllers. That transition never happened, and controllers are still around.

Given controllers are long-lived entities in the framework, they are a source of a lot of bugs while writing ember apps. The classic bug is to forget to reset certain state in a controller, so when the user re visits the route, the state of the controller is not automatically reset.

After reading this response:

{%twitter 1361490151029940231 %}

I decided to take a stab at migrating a controller on a real app and write about the process, so I could actually see how it would look in real life and also share knowledge with the community.

## Real world example

At Mimiquate, we have developed an open source app called [Timo](https://timo.mimiquate.xyz), that aims to find acceptable time slots to have meetings for remote teams with team members all round the world. If you are interested, you can check the [article](https://www.linkedin.com/pulse/timo-simplifying-scheduling-virtual-meetings-juan-azambuja) I wrote on it's release. I decided to migrate Timo's largest controller, and write about the process while doing it.

Here is a link to [the commit where the change is implemented](https://github.com/mimiquate/timo/commit/08b881424a358e0ef9412fd8d29fa5adebb71f3e) if you want to go straight to the point.

Here are a few details to go over, the template of the route is a lot simpler now, which was expected.

{% gist https://gist.github.com/juanazam/52bbf19c51fe1b73e08a86d8af48393f file=team.hbs %}

All of its content was moved to the new top-level component.

Regarding the component file, most of the changes are straight forward: basically stop relying on the model property, and used the passed arguments instead. I also had to make sure I imported the store and router services, given those are not automatically available within components. This resulted in a few small changes, for example, updating transitions to other routes.

## Small hiccup

I would have thought this was the end of it, but it wasn't. When doing this migration you would assume the state of the component would always be refreshed, given that we we have switched from a controller to a component, but that wasn't the case.

On Timo, most of the action happens on the team route, and the user usually moves from one team to the other. This means the component is not destroyed, and only its arguments are updated. As a result, the state of the component never resets when navigating from team to team.

With controllers, the solution would be simple, we would just reset the state of the controller on the `resetController` hook of our route and that would be it.

If we were using Ember Classic components, we could use the `didUpdateAttrs` hook to reset these variables, since the parameters only update when the route is refreshed, but we don't have that possibility in Octane.

### Enter ember-modifiers

The simplest solution I could find to this problem was to use ember-modifiers.

{% github https://github.com/ember-modifier/ember-modifier %}

The addon comes with helpers we are familiar with, in this case, I used the `did-update` helper, as shown below.

{% gist https://gist.github.com/juanazam/52bbf19c51fe1b73e08a86d8af48393f file=template.hbs %}

I added a container div with the `did-update` modifier, which is called every time the current team is updated. When that happens, the `resetCurrentTime` function is executed, and the state is reset.

```
<div {{did-update this.resetCurrentTime this @team.id this}}>
```

## Conclusion

Migrating to top-level components is not hard at all, but there are a few issues that can be found after testing your app in different circumstances.

There are times when we will have to deal with Controllers like it or not. One of these situations, is when dealing with query params. Right now, the only way of dealing with them is via `queryParams` interface on controllers, which has its fair share of quirkiness.

Regarding issues related to glimmer components, most solutions can be found [here](https://guides.emberjs.com/release/upgrading/current-edition/glimmer-components/).

As always, Ember's documentation goes above and beyond.

In summary, not using controllers is definitely possible, but it’s not a solution that the framework fully supports, and that always has a cost. Although some controllers can be fully moved to components, others can’t, which means there is no uniform way of transforming all the code base this way without compromises.

Special thanks to

{% user nullvoxpopuli %}

For helping me discuss some of the issues I faced while implementing these changes.