Routes and their future role in Ember

Coming from this thread https://mobile.twitter.com/oligriffiths/status/993837170317234181 I decided to post my thoughts in more then just a tweet :slight_smile: and get others to reply.

Looks like there are a few ideas and projects that are rethinking the role of the Route in an Ember app. One of those early ideas was “Routable Components” and I think there is still something there that can be salvaged to make Ember a simpler and more coherent framework.

There are several ways that this could go, the two most obvious are:

  1. Route → Component
  2. Route → Template → Component (https://mobile.twitter.com/Thoov/status/993733133894799361)

One downside I see to #1 is that it’s not as clear which component is associated with a route. Does that have to be set in the Route or should it be a convention like <routeName>-route component, e.g. users-route. The other issue is how to define the data and the actions on the component without that intermediate template. I modified @thoov’s gist for #2 to reflect #1 here: Thought exercise on "Ember Controllerless" · GitHub just to get some brainstorming going.

With #2 doing ember g route <routeName> also creates a template, but that template doesn’t automatically have a component so that’s an extra step and there is no way to know if the template rendered without a component with the APIs currently in the Route. If the controller is removed, which data is passed to this template and does that data include actions as well? So there are a few unresolved questions.

I’d like to see a discussion about which convention is more fitting for Ember, or if it’s another one altogether. What other pros/cons are there for each solution?

2 Likes

I stick with routes being just there to resolve your models and nothing else. Then, I like to think that the template-controller pairing as a container component in Redux land. It’ll always just call a bunch of presentational components inside it.

Edit:

Not sure if I’m contributing to the topic haha. Are you trying to brainstorm a possible RFC?

One thing worth pointing out about option 2 is that with the advent of @name arguments, you can pass freeform arguments to template-only components.

You could imagine a router API like:

export default class extends Route {
  async args(params, queryParams) {
    return RSVP.hash({
      person: this.store
        .find("person", params.person_id)
        .filter("gt", queryParams.age_gt)
    })
  }
}

Which would produce a @person in the template. In this case, and especially with outerHTML template-only components, it’s hard to see why it needs a component class.

I agree mostly, but there are two important scenarios to consider:

  1. Component Hooks (didInsertElement,didReceiveAttrs, etc)
  2. Actions

I could see “route templates” becoming template only components, with the option to add a component class for the didInsertElement behavior, but for actions I’m not so sure. Also didReceiveAttrs would fire on QP changes in Route (?).

Maybe an actions object that has all of the actions which are bound to the route if no component class, otherwise to the component (that sounds kinda bad), or maybe the component actions get merged with the route actions (if actions defined in the component class) … not really sure.

Hello! :wave:

I have been thinking about this topic lately.

One thing that is very clear for me is that controllers must die. They are very difficult to explain to people that are just getting started with Ember.

I believe routes should be just a “type of component”.

They should behave exactly the same as other components, with regular event hooks (didInsertElement etc), but with some additional hooks to support components to be routable.

The params that we current receive on the model hook, could just be an argument to the component, as well as the query params. They would be accessible via this.args, similarly to glimmer components.

Managing QueryParams could be extracted to a service that would be injected into the component, lots of work here, but the learning curve for this programing model would way lower.

One thing that one would need to figure out is how the loading and error states would play into this “routable components”.

Something like this would be very interesting:

export default class extends RouteComponent {
  async data() {
    return RSVP.hash({
      person: this.store
        .find("person", this.args.params.person_id)
        .filter("gt", this.args.queryParams.age_gt)
    })
  }

  didInsertElement() {
    console.log('element inserted into dom!'); 
  }

  sayHello() {
    // not sure how to access the data here, but would be nice to
    // have it as this.data.person.name or something like that.
    console.log('Hello'); 
  }
}
{{log @params}}
Hello {{data.person.name}}!
<button onclick={{action this.sayHello}}>Say Hello</button>

Anyway, these are just some thoughts. I’m sure there are many flaws on my example above, but a simpler programing model would come a long way.

2 Likes

Hello!

As a new Ember developer (~2 months into Ember, came from PHP, very basic JS knowledge) - I’d like to qualify your suggestion.

IMO - controllers aren’t a problem - conceptually, it’s mostly just the same as a component, it’s not a hard thing to grasp.

What is a problem is that separation of controllers and components adds yet another concept which new users must learn. As a newb, I found working with deeper layers of Ember to be overwhelming - mostly because of the plethora of concepts to keep track of.

Personally, I think that the problem is the distinction between a controller and a component - they both achieve mostly the same thing - customisation of the thing on the page.

So, I do agree with your general point - a consolidation between components and controllers (aka ‘routable components’) - from a newbie perspective - just-makes-sense.

I believe that module unification will expose this shortcoming, and TBH I feel that once MU lands, people will begin to realise how much of a fudge conceptually separating controllers and components is.

Also, another thought I had last night on the general topic of ‘how to improve routers’…

I think what would be excellent would be to separate the core concern of routing (i.e tracking state through a path) from URL routing.

I’m currently working on a multi-path on-boarding wizard using the excellent Ember Steps add-on (https://alexlafroscia.com/ember-steps/latest/docs) and what struck me is that at it’s core, it’s also just a router, albeit one where the developer has had to re-invent the wheel.

If there were a general ‘routing’ component which abstracted general routing mechanics from URL routing, I can see this being super powerful tool.

Or as an alternative, perhaps making query params an even more prominent part of (routable?) components would also allow something similar…

We may already have the separation you want between the router and the URL, which is Location. You can implement a custom Location which lets you have full control over where the router is reading/writer its state to (which can be the URL or anywhere else).

A missing piece that would make this totally general is a clear way to instantiate multiple parallel routers. But for many cases a custom Location is enough.

One general thing to keep in mind that I think some of the earlier suggestions in this thread overlook: Route has a very different lifecycle and statefulness from Component.

I am 100% on board with solutions that will replace all Controllers with Components. People have already laid out a clear explanation for why controllers are just weird components with an unhelpful distinction.

But Routes are not at all like Components, and I don’t think they should be. The major difference is that Route is already nearly stateless, and the remaining bits of statefulness in them are IMO mistakes / bugs. A huge amount of routing complexity goes away once you can treat routes as purely functional transformations between the URL (or whatever Location) and the desired tree of templates & their data.

It may be possible to design API that puts the stateless route-ish things onto components, but it’s easy to fall into an uncanny valley. For example, a model() hook absolutely should not be an instance method on Component (because it should not be doing anything to this). If it goes there at all it should probably be a class method, or a separate named export from the same file.

5 Likes

Recently I came across to this project that allows using routes only without controllers and templates. It essentially is an implementation of the Routable Components RFC.

https://wongpeiyi.github.io/ember-component-routes/

I was playing with it and it works great. The power of not needing to think about routes vs controller vs query params is huge.

Here is a usage example directly taken from their docs.

// post/route.js

import { ComponentRoute } from 'ember-component-routes';

export default ComponentRoute.extend({

  model({ id }) {
    return this.store.findRecord('post', id);
  },

  attributes(model, params, actions) {
    return { model, params, actions };
  },

  renderComponents() {
    this.renderComponent('post-page');
  },

  actions: {
    reload() {
      this.refresh();
    }
  }
});
{{!--  post-page/template.hbs--}}
<h1>{{@route.model.title}} – #{{@route.params.id}}</h1>

<button onclick={{@route.actions.reload}}>Reload</button>
2 Likes