Ember transitionToRoute cleanly in a component without sendAction - ember.js

How can transitionToRoute be called cleanly from within an Ember component?
It works with injecting a controller into the component and calling the controller's transitionToRoute function, however I'd like something a little more elegant if possible.
What it currently looks like inside the component's javascript:
// this.controller is injected in an initializer
this.controller.transitionToRoute("some.target.route.name");
What would be nicer in the component's javascript:
transitionToRoute("some.target.route.name");
One goal is do this without using sendAction as this particular component has a single purpose and should always transition to the same route. There's no need for any other Ember artifacts to be aware of the route this component always transitions to, there's no need for the associated indirection. The responsibility for the target route is owned by this component.

UPDATE Please see the other more recent answers for how to achieve this with less code in newer Ember versions, and vote those up if they work for you - Thanks!
Inject the router into the components and call this.get('router').transitionTo('some.target.route.name').
To inject the router into all components, write an initializer at app/initializers/component-router-injector.js with the following contents:
// app/initializers/component-router-injector.js
export function initialize(application) {
// Injects all Ember components with a router object:
application.inject('component', 'router', 'router:main');
}
export default {
name: 'component-router-injector',
initialize: initialize
};
Sample usage in a component:
import Ember from 'ember';
export default Ember.Component.extend({
actions: {
submit: function() {
this.get('router').transitionTo('some.target.route.name');
}
}
});

Jan 22, 2018 update
As of Ember 2.15, phase 1 of the public router service is implemented.
Transition to a route from inside a component:
import { inject as service } from '#ember/service';
export default Ember.Component.extend({
router: service(),
actions: {
someAction() {
this.get('router').transitionTo('index');
}
}
});

Use
router: service()
instead of
router: service('-routing')
import Component from '#ember/component';
import {inject as service} from '#ember/service';
export default Component.extend({
router: service(),
actions: {
onClick(params) {
let route = this.getMyRoute(params);
this.get('router').transitionTo(route);
}
}
});

If you want to use the router only in a specific component or service or controller, you may try this:
Initialize an attribute with the private service -routing. The - because it's not a public API yet.
router: service('-routing'),
And then inside any action method or other function inside the service or component:
this.get('router').transitionTo(routeName, optionalParams);
Note: It'll be transitionToRoute in a controller.

You can use container to get access to any needed part of application. To get application controller :
this.container.lookup('controller:application')
But what about structure of application - components should generate events - so my opinion it's better to use sendAction. Cause in future you can get situation, when you need to filter such behavior ( for example ) or other application-specific logic before transition

Related

Ember dependency injection not working with router in Ember 1.11

I have an ember component where I need both the store and the router. I'm trying to inject them using Ember.inject.service. It works when I inject the store, but not the router:
import Ember from 'ember';
const MyComponent = Ember.Component.extend({
store: Ember.inject.service('store'),
routing: Ember.inject.service('-routing'),
classNames: ['my-component'],
schema: Ember.computed('store', 'routing', function schema() {
// This works. Shows up when I log it.
const store = this.get('store');
// This doesn't
const routing = this.get('routing');
// return schema from store. This works.
}),
});
export default MyComponent
I'm on Ember version 1.11.3. Am I missing something?
I'm not sure about Ember 1.13, but in my Ember 2.x app, I inject the router as you described above and can then transition as follows:
this.get('routing.router').transitionTo('routeName', model, {
queryParams: {
queryName: queryValue
}
});
ps: It is generally considered best practice to pass an action closure into a component which would take care of the route transition, rather than making the component responsible for handling the transition.

Ember deprecation: replacing a view with a component

Hey I'm facing a problem with removing a view.
The view is used as navbar
{{view "inner-form-navbar" navbarParams=innerNavObject}}
Where params look like this
innerNavObject: {
...
routeToReturn: 'someroute.index',
...
},
On the navbar there's a small "back" button when it's clicked the parent index route is opened.
It currently works like this:
this.get('controller').transitionToRoute(routeToReturn);
But this won't work in a component and is sketchy anyways. Do i need to somehow inject router to component? Or has anyone gotten a solution for this? The navbar is used in so many places so adding a property to navbarObject to have certain action defined is not a really good solution imo.
Went for this solution :
export default {
name: 'inject-store-into-components',
after: 'store',
initialize: function(container, application) {
application.inject('component', 'store', 'service:store');
application.inject('component', 'router', 'router:main');
}
};
Now i can do
this.get('router').transitionTo('blah')
Well you can try to use a service that provides the routing capabilities and then inject into the component.
There's an addon that seems to do just that - ember-cli-routing-service
Example taken from the link, adapted for you scenario:
export default Ember.Component.extend({
routing: Ember.inject.service(),
someFunc () {
this.get('routing').transitionTo(this.get('innerNavObject'). routeToReturn);
}
});
Having a component control your route/controller is typically bad practice. Instead, you would want to have an action that lives on your route or controller. Your component can then send that action up and your route or controller will catch it (data down, actions up).
In your controller or route, you would have your transition action:
actions: {
transitionFunction(route) {
this.transitionTo(route);
}
}
You would also define the the current route name in your route or controller and pass that to your nav bar component. Controller could then look like:
export default Controller.extend({
application: inject.controller(),
currentRoute: computed('application.currentRouteName', function(){
return get(this, 'application.currentRouteName');
}),
actions: {
transitionFunction(route) {
this.transitionTo(route);
}
}
});
Then call your component and pass the currentRoute CP to it:
{{nav-bar-component currentRoute=currentRoute action='transitionFunction'}}
Then, in your component, you can have a function that finds the parent route from the currentRoute:
export default Component.extend({
click() { // or however you are handling this action
// current route gives us a string that we split by the . and append index
const indexRoute = get(this, currentRoute).split('.')[0] + '.index';
this.sendAction('action', indexRoute);
}
});
Extending a route
Per your comment, you may want to have this across multiple routes or controllers. In that case, create one route and have your others extend from it. Create your route (just as I created the Controller above) with the action. Then import it for routes you need:
import OurCustomRoute from '../routes/yourRouteName';
export default OurCustomRoute.extend({
... // additional code here
});
Then your routes will have access to any actions or properties set on your first route.

How to define a large number of resources that use the same logic without creating boilerplate modules in ember-cli?

I have a large number of resources that use exactly the same logic. Each resource has a <resource> route and a <resource>.show route. I've defined BaseRoute, BaseShowRoute, BaseController, BaseShowController and corresponding templates to capture this common logic. I set the appropriate controller/templates on the route objects:
// routes/base.js
import Ember from 'ember';
export default Ember.Route.extend({
controllerName: 'base',
templateName: 'base'
});
// routes/base/show.js
import Ember from 'ember';
export default Ember.Route.extend({
controllerName: 'baseShow',
templateName: 'baseShow'
});
But in order to use these route prototypes with my resources, I have to have two modules for each resource:
// routes/<resource>.js
import BaseRoute from './base';
export default BaseRoute.extend({
});
// routes/<resource>/show.js
import BaseShowRoute from './base/show'
export default BaseShowRoute.extend({
});
This seems silly. I would like to specify that all of these resources should use BaseRoute and BaseShowRoute without needing to create these modules. It would be reasonable to have this option in Router.map. Something like this:
Router.map(function(){
this.resource('articles', { extends: BaseRoute }, function() {
this.route('show', { path: ':article_id', extends: BaseShowRoute })
});
});
But to my knowledge there is nothing like the extends option I'm using above.
The only documentation I can find for the route and resource methods invoked in Router.map is in the Routing Guide. The only option that you can pass to these methods seems to be path. The ember-cli user guide does say that you can override the base class for all generated routes if you define routes/basic.js, but this is not enough for me-- I need multiple base classes.
How can I get rid of my boilerplate route modules?
In our application we have exactly the same usecase as you. As far as I know there isn't such API in Router. The boilerplate is needed only for routes, so the controller module is not needed and we define it the same way as you do:
// routes/base/show.js
import Ember from 'ember';
export default Ember.Route.extend({
controllerName: 'baseShow',
});
The problem is that Ember picks the same instance of Controller for every resources. That is ok for most of our resources, but some need their own state (for example: the form has more than one tab and we want to remember the last active tab for each resource) and then we define the controller and override controllerName:
// routes/user/show.js
import BaseShowRoute from './base/show'
export default BaseShowRoute.extend({
controllerName: 'user/show',
});
It would be great if Route had a property controllerClass telling Ember: Create instance of this Controller for this Route.

didInsertElement from controller?

I have an ember application with a controller header.js and a template header.hbs.
Now I have some javascript I need to execute at document $( document ).ready()
I saw on Ember Views there is didInsertElement but how do I do this from the controller?
// controllers/header.js
import Ember from 'ember';
export default Ember.Controller.extend({
});
// views/header.js
import Ember from 'ember';
export default Ember.Controller.extend({
});
// templates/header.js
test
I read several times it's not good practice to be using Ember Views?
the controller is not inserted (the view is) hence there is no didInsertElement.
If you need something to run once, you can write something like this:
import Ember from 'ember';
export default Ember.Controller.extend({
someName: function () { // <--- this is just some random name
// do your stuff here
}.on('init') // <---- this is the important part
});
A more actual answer (Ember 2.18+) while honouring the same principle as mentioned in user amenthes' answer: it's no longer advised to use Ember's function prototype extensions. Instead you can override the init() method of a controller directly. Do note that you'll have to call this._super(...arguments) to ensure all Ember-related code runs:
import Controller from '#ember/controller';
export default Controller.extend({
init() {
this._super(...arguments); // Don't forget this!
// Write your code here.
}
});

Calling a controllers method in another controller Ember

I am using Ember's Need Api to call a method of a controller in another controller. I am able to get the instance of the controller but when I am calling it method it returns me this error TypeError: Object [object Object] has no method.
This is how I am calling it:
Cards.CardsIndexController = Ember.Controller.extend({
needs: 'account_info',
actions: {
accountInfoStart:function(){
console.log(this.get('controllers.account_info').test()); // error here
}
}
});
This is the controller whose function I want to call
Cards.AccountInfoController = Ember.Controller.extend({
actions:{
test: function(){
alert(1);
}
}
});
How can I solve it?
test is not technically a method, but an action or event. Use the send method instead:
this.get('controllers.account_info').send('test', arg1, arg2);
As per Ember documentation; create a property that lazily looks up another controller in the container. This can only be used when defining another controller.
legacy ember application example:
App.PostController = Ember.Controller.extend({
accountInfo: Ember.inject.controller()
this.get('accountInfo').send('test')
});
modern ember application example:
// in an ember app created with ember-cli
// below snippet would be the app/controllers/post.js file
import Ember from 'ember';
export default Ember.Controller.extend({
appController: Ember.inject.controller('application')
});
You can find more documentation about Ember.inject here
From the Updated Ember Documentation :
import { inject } from '#ember/controller';
export default Ember.Controller.extend({
appController: inject('application')
});
For further reference, you can find out by this link https://guides.emberjs.com/release/applications/dependency-injection/#toc_ad-hoc-injections