How to create a inject helper for something other than service? - ember.js

https://github.com/emberjs/ember.js/blob/5fd2d035b30aa9ebfe73de824b3b283ec8e589cc/packages/ember-runtime/lib/system/service.js#L31
In the line I reference above the ember-core team imports this createInjectionHelper and uses it to add a clean/simple api for injecting services like so
App.ApplicationRoute = Ember.Route.extend({
authManager: Ember.inject.service('auth'),
model: function() {
return this.get('authManager').findCurrentUser();
}
});
How can I create something like this myself for a non service?

Your example usage will change slightly from what you have above. We will cover what the injectRepositories does in a little bit.
import injectRepositories from 'app/utils/inject';
export default Ember.Route.extend({
repository: injectRepositories('person'),
model: function() {
var repository = this.get('repository');
return repository.find();
}
});
The initializer can be improved with the following changes:
import registerWithContainer from "ember-cli-auto-register/register";
export function initialize(_, application) {
registerWithContainer("repositories", application);
application.inject("repositories", "store", "store:main");
}
export default {
name: "repositories",
after: "store",
initialize: initialize
};
Let's break down what is happening in each line.
registerWithContainer("repositories", application);
In the line above, we are deferring to the ember-addon ember-cli-auto-register. This addon will take a directory, in this situation, the repositories directory and register each object into the Ember container to able to be accessed via a lookup. They will be inserted as if doing the following:
application.register("repositories:person", PersonRepository);
Then we add a function to do the injection using the ember-addon ember-cli-injection:
// app/utils/inject.js
import inject from "ember-cli-injection/inject";
var injectRepositories = inject("repositories");
export default injectRepositories;
This then allows us the opportunity to use the newly created function above to access these objects with the code below:
import injectRepositories from 'app/utils/inject';
export default Ember.Route.extend({
repository: injectRepositories('person'),
model: function() {
var repository = this.get('repository');
return repository.find();
}
});
Since each object is now in the container, we can look it up and inject at runtime instead of during the initialization of the application. We register the repositories key in the function and this then returns a computed property (see code below from ember-cli-injection). We do this as a computed property to allow lazy loading. The object is not fetched from the container until the property is accessed.
import Ember from 'ember';
var injection = function(key) {
return function(name) {
return Ember.computed(function(propertyName) {
var objectName = name || propertyName;
return this.container.lookup(key + ':' + objectName);
});
};
};
export default injection;
We also allow for a name to passed to the repositories function, for example repository: injectRepositories('person'). This allows you to name your object whatever you would like when injecting it.
If you would like to just name the object the same as the name of the repository injected into the container you can alternatively do person: injectRepositories(). This will pass the person key to the computed property as the propertyName and since the name was left null when injecting, the objectName will instead be person. This matches the API produces similar results but is not the same as that of the Ember.inject.service and Ember.inject.controller API that is available as of Ember 1.10.

I don't think it's their intention for you to use it this way. The standard way is to use App.inject() if you're using plain ember, or do this in an initializer if you're using ember-cli.
In your case:
// Register the service
App.register('service:auth', {
findCurrentUser: function() {
// Do your magic
}
}, { instantiate: false });
App.inject('route', 'auth', 'service:auth');
Then in your model hook you can use this.auth.findCurrentUser();. You can also inject this into controllers and components if you need. Also note, to keep it clean, that you might want to include a separate module instead of defining your auth module in the service registration.
More info here:
http://emberjs.com/guides/understanding-ember/dependency-injection-and-service-lookup/#toc_dependency-injection-with-code-register-inject-code
NOTE
A service is also not a "special" thing. You can inject anything you want into pretty much anything you want using this method.

Related

Clean Ember 1.13+ way of knowing if child route is activated

Assume we have an Article model as follows:
export default DS.Model.extend({
author: DS.belongsTo('user'),
tagline: DS.attr('string'),
body: DS.attr('string'),
});
Assume also that we have a lot of pages, and on every single page we want a ticker that shows the taglines for brand new articles. Since it's on every page, we load all (new) articles at the application root level and have a component display them:
{{taglines-ticker articles=articles}}
{{output}}
That way we can visit any nested page and see the taglines (without adding the component to every page).
The problem is, we do not want to see the ticker tagline for an article while it's being viewed, but the root-level taglines-ticker has no knowledge of what child route is activated so we cannot simply filter by params.article_id. Is there a clean way to pass that information up to the parent route?
Note:
This is not a duplicate of how to determine active child route in Ember 2?, as it does not involve showing active links with {{link-to}}
Ember is adding a proper router service in 2.15; this exposes information about the current route as well as some methods that allow for checking the state of the router. There is a polyfill for it on older versions of Ember, which might work for you depending on what version you're currently using:
Ember Router Service Polyfill
Based on the RFC that introduced that service, there is an isActive method that can be used to check if a particular route is currently active. Without knowing the code for tagline-ticker it's hard to know exactly how this is used. However, I would imaging that you're iterating over the articles passed in, so you could do something like:
export default Ember.Component.extends({
router: Ember.inject.service(),
articles: undefined,
filteredArticles: computed('articles', 'router.currentRoute', function() {
const router = this.get('router');
return this.get('articles').filter(article => {
// Return `false` if this particular article is active (YMMV based on your code)
return !router.isActive('routeForArticle', article);
});
})
});
Then, you can iterate over filteredArticles in your template instead and you'll only have the ones that are not currently displayed.
You can still use the link-to component to accomplish this, and I think it is an easy way to do it. You aren't sharing your taglines-ticker template, but inside it you must have some sort of list for each article. Make a new tagline-ticker component that is extended from the link-to component, and then use it's activeClass and current-when properties to hide the tagline when the route is current. It doesn't need to be a link, or look like a link at all.
tagline-ticker.js:
export default Ember.LinkComponent.extend({
// div or whatever you want
tagName: 'div',
classNames: ['whatever-you-want'],
// use CSS to make whatever class you put here 'display: none;'
activeClass: 'hide-ticker',
// calculate the particular route that should hide this tag in the template
'current-when': Ember.computed(function() {
return `articles/${this.get('article.id')}`;
}),
init() {
this._super(arguments);
// LinkComponents need a params array with at least one element
this.attrs.params = ['articles.article'];
},
});
tagline-ticker being used in taglines-ticker.hbs:
{{#tagline-ticker}}
Article name
{{/tagline-ticker}}
CSS:
.hide-ticker {
display: none;
}
I tried to extend the LinkComponent, but I ran into several issues and have still not been able to get it to work with current-when. Additionally, if several components need to perform the same logic based on child route, they all need to extend from LinkComponent and perform the same boilerplate stuff just to get it to work.
So, building off of #kumkanillam's comment, I implemented this using a service. It worked perfectly fine, other than the gotcha of having to access the service somewhere in the component in order to observe it.
(See this great question/answer.)
services/current-article.js
export default Ember.Service.extend({
setId(articleId) {
this.set('id', articleId);
},
clearId() {
this.set('id', null);
},
});
routes/article.js
export default Ember.Route.extend({
// Prefer caching currently viewed article ID via service
// rather than localStorage
currentArticle: Ember.inject.service('current-article'),
activate() {
this._super(arguments);
this.get('currentArticle').setId(
this.paramsFor('articles.article').article_id);
},
deactivate() {
this._super(arguments);
this.get('currentArticle').clearId();
},
... model stuff
});
components/taglines-ticker.js
export default Ember.Component.extend({
currentArticle: Ember.inject.service('current-article'),
didReceiveAttrs() {
// The most annoying thing about this approach is that it
// requires accessing the service to be able to observe it
this.get('currentArticle');
},
filteredArticles: computed('currentArticle.id', function() {
const current = this.get('currentArticle.id');
return this.get('articles').filter(a => a.get('id') !== current);
}),
});
UPDATE:
The didReceiveAttrs hook can be eliminated if the service is instead passed through from the controller/parent component.
controllers/application.js
export default Ember.Controller.extend({
currentArticle: Ember.inject.service('current-article'),
});
templates/application.hbs
{{taglines-ticker currentArticle=currentArticle}}
... model stuff
});
components/taglines-ticker.js
export default Ember.Component.extend({
filteredArticles: computed('currentArticle.id', function() {
const current = this.get('currentArticle.id');
return this.get('articles').filter(a => a.get('id') !== current);
}),
});

emberjs (unknown mixin) error when working with Ember.Object.Extend

I'm creating an initializer in my ember app (using ember-cli 0.2.0 beta). In it, I define a user object (that I define with Ember.Object.extend) that I want to register and inject into the app's controllers.
When i print the user object (right after defining it) to the console, Iā€™m getting ā€œ(unkown mixin)ā€. I've looked around but can't seem to find a solution or tell why this is the case. Here's what my initializer file looks like:
// app/initializers/application.js
var currentUser = Ember.Object.extend({
authToken: localStorage['authToken'],
isAuthenticated: function () {
return !!this.get('authToken');
}.property('authToken')
});
console.log(currentUser); // logs (unknown mixin)
export function initialize(container, application) {
// register current user factory
application.register('user:current', currentUser, {singleton: true});
// inject factory
application.inject('controllers', 'currentUser', 'user:current');
}
export default {
name: 'application',
initialize: initialize
};
I would suggest using Ember.Object.create(). What's the point of extending the object but never creating an instance of it?

Make data from store app-wide available?

I've got a base-route encapsulating all other routes in my application.
I'd like to make the categories I retrieve from the store via this.store.find('category') available everywhere in my application.
I tried retrieving it in the base-controller with this code:
import Ember from 'ember';
export default Ember.ArrayController.extend({
// retrieve all categories as they are needed in several places
categories: function() {
return this.get('store').find('category');
}
});
and creating an alias from child-controllers via:
categories: Ember.computed.alias('controllers.base.categories')
but it gives this error-message:
Uncaught Error: Assertion Failed: The value that #each loops over must be an Array. You passed function () {
return this.get('store').find('category');
}
How can I solve my problem?
Make it a computed property.
categories: function() {
return this.get('store').find('category');
}.property()
Controllers are being deprecated in 2.0, so I'd look into using a service architecture instead of a base controller architecture.

Injecting function with initializer in ember-cli

I have an app built on Ember-cli. I am trying to inject a method into all routes, controllers, and views. I am aware I could utilize the app/utils directory and import the method's module into all the files that call it, but I would like the method to be automatically available. Hence, I have chosen to inject the method using an initializer.
The initializer looks like this:
export default {
name: 'injectMethod',
initialize: function(container, app) {
var someFunction = function(message) {
};
app.register('function:main', someFunction);
Em.A(['route', 'controller', 'view']).forEach(function(place) {
app.inject(place, 'someFunction', 'function:main');
});
}
};
This results in the following error message: Uncaught TypeError: undefined is not a function. The error disappears when I remove the app.inject() line.
Are initializers handled differently in ember-cli and/or is something in the above code incorrect? Or is they a better way to achieve my goal than using an initializer?
Ember expects you to register a factory which it can create instances using the create method. If you are passing in an instance (or just a method) you would need to tell Ember not to attempt to instantiate it and just use the instance passed in.
export default {
name: 'injectMethod',
initialize: function(container, app) {
var someFunction = function(message) {
};
app.register('function:main', someFunction, {instantiate: false});
Em.A(['route', 'controller', 'view']).forEach(function(place) {
app.inject(place, 'someFunction', 'function:main');
});
}
};
Example: http://emberjs.jsbin.com/xaboliwu/1/edit

Get current route name in Ember

I need to get the current route name in my ember application; I tried this:
Ember App.Router.router.currentState undefined
but it doesn't work for me (there is probablig something i'm missimg...) I use Ember rc6 and I have a multilingual app; in the applicationRoute I detect the browser's language and I redirect to the correct page with:
this.transitionTo(userLang);
but I would like this to be executed only when user are on the home page, so something like this:
if (currentRoute == 'home'){
this.transitionTo(userLang)
}
NOTE: as of Ember 3.16, the original answer is not only recommended, but observers are strongly discouraged.
To get the current route name, you can utilize the Router Service: https://api.emberjs.com/ember/3.18/classes/RouterService/properties/currentRouteName?anchor=currentRouteName
export default class MyComponent extends Component {
#service router;
get activeRoute() {
return this.router.currentRouteName;
}
}
Original answer below
You could observe the application's currentPath and set it to the current route accordingly when it changes:
App = Ember.Application.create({
currentPath: '',
});
App.ApplicationController = Ember.Controller.extend({
updateCurrentPath: function() {
App.set('currentPath', this.get('currentPath'));
}.observes('currentPath')
}),
This way you have access to the currentPath when ever you want with App.get('currentPath');
E.g.
if (App.get('currentPath') == 'home'){
this.transitionTo(userLang);
}
Hope it helps.
This worked for me on 1.3.0-beta (and a quick glance at the source for 1.1.2 suggests it would work there too):
App.__container__.lookup('router:main').location.lastSetURL
Note that the documentation states:
At present, it relies on a hashchange event existing in the browser.
However, I believe it's strongly suggested that App.__container__ not be used in production code. A more acceptable alternative would be to use App.Router.router.currentHandlerInfos, which provides information on the current Ember route.
Yet another option is currentRouteName on the ApplicationController. You can add needs: ['application'] to your controller, then access the route name with controllers.application.currentRouteName. This will return something like posts.index.
With the shift to components, it is harder to get route name. The best way is to add an initializer such as
ember g initializer router
(from command line), and
export function initialize(application) {
application.inject('route', 'router', 'router:main');
application.inject('component', 'router', 'router:main');
}
export default {
name: 'router',
initialize
};
in a initializers/router.js. You can also inject into controller if you need to. Then just do simply
this.get('router.currentRouteName');
in JS, or
{{router.currentRouteName}}
in template.
This is the only way I have found to get it reliably, and observable in Ember 2.4
If you want to get current route in your component or controller you can inject routing service (routing: Ember.inject.service('-routing'))
(for more) and use:
this.get('routing.currentRouteName') or this.get('routing.currentPath')
Example with component and computed property:
import Ember from 'ember';
export default Ember.Component.extend({
routing: Ember.inject.service('-routing'),
checkMyRouteName: Ember.computed('routing.currentRouteName', function() {
return this.get('routing.currentRouteName');
})
})
Example with controller and computed property:
import Ember from 'ember';
export default Ember.Controller.extend({
routing: Ember.inject.service('-routing'),
checkMyRouteName: Ember.computed('routing.currentRouteName', function() {
return this.get('routing.currentRouteName');
})
})
Current route in your route you just need this.routeName
Example with route:
import Ember from 'ember';
export default Ember.Route.extend({
checkMyRouteName() {
return this.routeName;
}
})
Just as an update, in Ember 1.8.1, we can get the routeName inside an Ember.Route object by doing this.routeName.
Currently as of Ember 1.7.0 you can get the current route from within a route by calling this.routeName.
The Ember namespace API now has a getOwner method, which is very useful for looking up the currentRouteName, or, other route properties.
const owner = Ember.getOwner(this);
const currentRoute = owner.lookup('router:main').currentRouteName;
const routeInfo = owner.lookup(`route:${currentRoute}`).get('info');
// etc.
I've created an Ember Twiddle example to demonstrate. Use the text input above the "Output" pane to hit other routes like /blue, /green, or /red.
Ember has a RouterService since 2.15. It provides the name of the current route as currentRouteName property. A polyfill exists for Ember 2.4 - 2.14 if you are still on such an old version.
import Component from '#ember/component';
export default Component.extend({
router: service(),
isHomeRoute: computed('router.currentRouteName', function() {
return this.router.currentRouteName === 'home';
}),
});
All other solutions mentioned here are relying on private API that might already be deprecated / removed. Using RouterService is working at least up the current version, which is 3.12 at the time of writing this.
Please note that the "home" is not /. The root URL is called "index".
I had the same problem for a while. then i started exploring router. It always have a state object which can be obtained from any route using
var route = this;
var handlerInfos = route.get("router.router.state.handlerInfos");
var currRouteHandlerInfo = handlerInfos[handlerInfos.length-1];
var currRouteName = currRouteHandlerInfo.name; //"home"
that's it. Now you have the current route name!
if you want the current route params,
var routerParams = this.get("router.router.state.params");
var currRouteParams = routerParams[currRouteName]; //{ homeId : "1" }
You can simple parse the current URL. This way you can use your full url for example:
http://127.0.0.1:8000/index.html/#/home
and extract from this string the suffix:
/home
which is the current route name.
A simple JS function (that works regardless to your Ember version) will be:
function getCurrentRoute()
{
var currentRoute;
var currentUrl = window.location.href; // 'http://127.0.0.1:8000/index.html/#/home'
var indexOfHash = currentUrl.indexOf('#');
if ((indexOfHash == -1) ||
(indexOfHash == currentUrl.length - 1))
{
currentRoute = '/';
}
else
{
currentRoute = currentUrl.slice(indexOfHash + 1); // '/home'
}
return currentRoute;
}
Example of use:
if (getCurrentRoute() == '/home')
{
// ...
}