What's the proper way to route from components?


#1

Inside a component, this.transitionTo or this.TransitionToRoute do not work.

My current work around is to inject the router service, for example:

    export default Ember.Component.extend({
      router: Ember.inject.service('-routing'), 

      actions: {
        submit: function() {
          this.get('router').transitionTo('home');
        }
      }
    });

Is this bad practice? Is there a better way to handle routing from components (that does not include controllers)?


#2

This is indeed considered a bad practice because the -routing service is a private one, so you have very few guarantees that it won’t change beneath your feet. On the other hand, using controllers is generally fine so that’s what I’d suggest despite your parenthesis. Bind a closure action down from the controller and call the action in the component:

export default Ember.Controller.extend({
  actions: {
    submit() {
      this.transitionToRoute('home');
    }
  }
});
{{my-component submit=(action 'submit')}}
    export default Ember.Component.extend({
      actions: {
        submit() {
          this.get('submit')();
        }
      }
    });

#3

Is having the action(s) handled by the route bad practice as well?

I was under the assumption that all actions bubbled up to the route. But if I specify a closure action for a component without a controller being present, then the component does not render. Anways, I was able to get this to work with the ember-route-action-helper.


#4

Closure actions do not, they are like callbacks that you can call, you have to explicitly bubble from the controller. Out of curiosity, why the insistence on not using controllers?


#5

I’d prefer to handle all actions in a central place, i.e., have them bubble up to controller or to a parent component. My initial resistance towards controllers was because of all the talk about routable components when I was learning Ember, plus the fact that controllers are barely mentioned in the Ember guides.


#6

Controller are to be deprecated in the future, so I’d recommened using https://github.com/DockYard/ember-route-action-helper.

Add this action to the application route:

toRoute() {
  this.transitionTo.call(this, ...arguments);
  return true;
}

Now you can bind this action directly to the submit action of your component:

{{my-component submit=(route-action 'toRoute' 'my.awesome.route')}}

Or ofcourse pass in another route action that handles the transition.


#7

Controllers are pretty simple, so there’s not much else to mention :stuck_out_tongue:

If/when they get deprecated, it will because we have an upgrade path in place and will communicate as much. Avoiding controllers at all costs won’t give you benefits, may even be prejudicial depending on the workarounds you might have.


#8

I thought the same about controllers. The earlier docs put me off using them, I tried for awhile but actually you end up with more mess trying to avoid them.


#9

Well, to be honest, I’m not sure if controllers will be deprecated/removed in the near feature. Seems like the Ember core team is too afraid to break existing Ember apps. Lauren Ten once recommended to move all controller logic to components and just add those components to the route templates, so that’s what we did (see example below). Personally I still prefer this, since controller are persistent singletons. The only drawback is that you need to do a lot of this.set('controller.someValue', 'Some value') in the routes, but it’s no big deal.

// awesome.hbs
{{route-awesome model=model}}

On topic: a router service will land in the near future. It will allow you to inject the service in any component and make a transistion.

import Ember from 'ember';

export default Ember.Component.extend({
  router: Ember.inject.service(),

  actions: {
    goToMars() {
      this.get('router').transitionTo('planet.mars');
    }
  }
});

#10

This whole service thing I think seems easy to abuse as well. Being able to inject any route into components seems like an anti-pattern. Components are supposed to be reusable.

Triggering actions is one thing, but explicitly calling functions like transitionTo, in the component, seems like heading for trouble.


#11

I absolutely agree with you. The example is actually taken from the RFC to show how it works in general. For a generic UI component that should have no clue about the consuming application, it’s very bad practice. But then again, I don’t expect anyone with some programming experience to make that mistake.

The consuming app should supply an action (preferable) or the target route as a string (quick fix).


#12

Having said that, when you are several layers down in components, having to pass down a callback just to fire a transitionToRoute also seems wrong. After all, the {{#link-to}} helper in the component template works, why can’t the component’s js do it without the faff?


#13

I’m facing this same dilemma, my component must be re-used out of my router, and I need to rely on actions. I really don’t want to make any controllers, but the route action helper doesn’t seem to work on the latest Ember.

For now I’ll use a controller, but realistically this is the only route to component limitation I’ve ran into.


#14

This is what I’m using!

Ember.getOwner(this).lookup('router:main').transitionTo('products');


#15

What version of Ember do you use? I tested with 2.14.0 and no problem at all.


#16

Great news for anyone struggling with this, Ember public routing service is coming soon :sunglasses:


#17

@thisFunction not coming soon, here. The release post is saying that the router service was made generally public in 2.15 :wink: I would recommend using it if you’re on 2.15 of Ember, or using the polyfill linked at the bottom of the Router service section.


#18

Oops! :blush: Thanks for the clarification @locks! Ember 2.15 here I come :smiley:


#19

Does anyone here is thinking about testing? Imagine your component has several transition then it is impossible to test. In integration test you can only stub a router service, but that won’t help to verify your component transits as expected.


#20

A late call, but for anyone reading this now or in the future:

Use link-to for transitioning in your app, use actions for anything else.

This will improve the a11y of your application. So instead of relying on an action to transition, you could pass in an array (using the array-helper) and have the component render a link.

// Route template
{{awesome-component
  someTransition=(array 'awesome.route' model.id)
}}

// Component template
{{#if someTransition}}
  {{#link-to params=someTransition}}Let's go!{{/link-to}}
{{/if

This also solves @Jeffrey_Cheung his problem. Your integration tests can easily make two assertions:

  • A link is rendered when someTransition is passed in;
  • The link’s href attribute is set correctly.