Inject a service into an Ember Object [not an Ember Controller] - ember.js

I'm trying to inject an Ember service into an Ember Object but keep getting the following error:
"Assertion Failed: Attempting to lookup an injected property on an
object without a container, ensure that the object was instantiated
via a container."
My code looks essentially something like the following:
const Model = Ember.Object.extend({
store: Ember.inject.service(),
destroyRecord() {...},
serialize() {...},
deserialize() {...},
});
let newModel = Model.create();
newModel.get('store');
Note: it does work if I inject the service into a Controller, but not an object. Haven't had any luck trying to figure out how to register the Object with the Ember container.

It works for an Ember.Controller because Ember controls the lifecycle of the object. In short, when Ember needs an instance of a certain controller, it asks the container for one, and the container will provide the instance, initializing if necessary.
What this implies is that for dependency injection to work, you would need to get a new instance of Model through the container. Assuming Ember 2.3 because of getOwner, and that this is somewhere inside the Ember application:
let owner = Ember.getOwner(this);
let newModel = owner.lookup('object:model');
newmodel.get('store');
You can consult the lookup documentation here.
But how to register? Use an application initializer:
$ ember generate initializer register-model
Then, open up the generated initializer and, assuming that your file is in app/folder/model.js, put something like:
import Model from 'app-name/folder/model';
export function initialize(application) {
application.register('object:model', Model);
}
export default {
name: 'register-model',
initialize
};
You can consult the register documentation here.
Hope this is helpful!

Well you need to passing in the container instance when you create a instance of your model. The container is accessible in the route, controllers, components with this.get('controller'). AFAIK basically anything created with the container gets the container property set. Thats why service injections work in controllers etc..
So if you are creating the model in a route's method. The code will look like below
App.IndexRoute = Ember.Route.extend({
model: function() {
var newModel = Model.create({
container: this.get('container')
});
return newModel.get('test').getText();
}
});
Here is a working demo.

Related

Accessing Ember Controller Properties within the same controller

I'm very new to EmberJS 2.0 and trying to slowly understand it by building my own website with it. Anyways, I've managed to get Firebase integrated with Ember and my controller is able to authenticate correctly. However, I'd like to understand why when I execute:
this.send('toggleModal');
inside the authenticate action property function (.then()) it doesn't work but if I execute it outside then everything works fine.
1) Is the 'this' keyword getting confused with something other than the Ember controller?
Here is the sample:
// /app/controllers/application.js
import Ember from 'ember';
export default Ember.Controller.extend({
isShowingModal: false,
actions: {
toggleModal: function() {
this.toggleProperty('isShowingModal');
},
authenticate: function(username, pass) {
this.get('session').open('firebase', {
provider: "password",
email: username,
password: pass
}).then(function (data) {
console.log(data.currentUser);
console.log(session.isAuthenticated); //Why is 'session' not defined?
this.send('toggleModal'); //This doesn't work. Throws an error.
});
this.send('toggleModal'); //This works.
},
logOut: function() {
this.get('session').close();
}
}
});
2) Also, I've noticed that when using Emberfire I'm able to use the property 'session.isAuthenticated' within the template application.hbs however, shouldn't 'session' be an object that is injected to all routes and controllers using Torii? Why is that property inaccessible/undefined within the application.js controller? I'm using https://www.firebase.com/docs/web/libraries/ember/guide.html#section-authentication as a reference.
3) In the guide above the actions for authentication are put inside the route. However, according to this quora post the route should only handle template rendering and model interfacing. Is this post incorrect? The authentication logic should reside in the application.js controller correct? https://www.quora.com/What-is-the-best-way-to-learn-Ember-js
1) Is the 'this' keyword getting confused with something other than the Ember controller?
Yes. This is one of the most common sticking points of Javascript. There's a lot of articles out there about it, but this one looked pretty good. To solve it you'll either need to use an arrow function, bind the function to the current context, or save the context in a local variable. (Read that article first though.)
2) Also, I've noticed that when using Emberfire I'm able to use the property 'session.isAuthenticated' within the template application.hbs however, shouldn't 'session' be an object that is injected to all routes and controllers using Torii? Why is that property inaccessible/undefined within the application.js controller? ...
That's because the template pulls the property from the current context (your controller). Inside of your controller you'll have to use this.get('session') instead. (After you fix the issue I mentioned above.)
3) ... Is this post incorrect? ...
I wouldn't say incorrect, just a bit oversimplified. I would follow whatever conventions the library uses as that's probably the best way given the library's requirements.
You're partially right about this although it's not really confused. this (where you're modal call doesn't work) isn't scoped to the Controller anymore, because it's inside a function. Either:
replace the function (data) call with data => if you're using ember cli. Or
var _self = this; up top and reference _self instead.
This should at least get you started.

How to get access to global variable in template?

I want to create global object with settings which I need to get from REST API. I need to make one request to REST API and get settings and after that I want to get access to these settings from any controllers and from any templates. What can you advice, what is the best practice for that problem?
Concept
Good practice would be to use initializers. They allow injection of any data to routes, controllers or any other kind of object.
Lets take an example ( example from Ember.js official site )
1 . You have an Application and you have a logger service like this -
App = Ember.Application.extend();
App.Logger = Ember.Object.extend({
log: function(m) {
console.log(m);
}
});
2 . Now you want to have this function log to available on all routes like this -
App.IndexRoute = Ember.Route.extend({
activate: function(){
// The logger property is injected into all routes
this.logger.log('Entered the index route!');
}
});
3. Tell ember to inject an object named Logger to all routes. Use initializer Like this
//I want to inject something
Ember.Application.initializer({
//this dependency name is logger
name: 'logger',
//whenever ember runs
initialize: function(container, application) {
//register my logger object under a name
application.register('logger:main', App.Logger);
//and use this service 'logger' in all 'routes'
application.inject('route', 'logger', 'logger:main');
}
});
With this you can have your application level data / code available in all routes and controller.
Once you get your data in controller you can use it in templates pretty easily.
How to make API call with initializers ??
Initializer can be used to run after some other services has been resolved. Like in our case store. store is the object we need to make API call to server in good way (We can use $.getJSON() or anything else no issues)
Tell the initializers to run after store loaded
//I want to inject something but only after store resolved
Ember.Application.initializer({
//this dependency name is logger
name: 'logger',
//wait for store object to be loaded, we need it to make API call
after : 'store',
//whenever ember runs
initialize: function(container, application) {
//grab the store object from container
var store = container.lookup('store:main');
//now you the store make that API call
self.store.find('user',{current:true}).then(function(data){
//we have the data we can inject it
data = data.get('firstObject');
container.lookup('controller:base').set('user', data);
//user lookup success
console.log("We have found an user. Yeah ember rocks.");
});
}
});
The settings object you are describing should probably live inside ApplicationRoute's model hook. You can then retrieve it in all your other models by saying modelFor('application') (see here). There is also a needs API (see here) that lets you share stuff between controllers in the application.

Where to store l20n ctx object in ember-cli app

I'm using the bower version of l20n to localize an ember-cli app.
I'm localizing the app using the following flow:
I've created an initializer which creates an l20n context object based off of the user's language setting.
Using an ember helper, I pass each word from the DOM onto the l20n context object to be translated.
In handlebars template :
{{l20n-helper 'stringVariable'}}
In ember helper:
export function translate(word){
return Ember.l20n.getSync(word);
}
export default Ember.Handlebars.makeBoundHelper(translate);
This works, but I've just tagged a global variable onto Ember in order to make the l20n context accessible to the helper. I know that is not best practice. I'm trying to figure out how to create an ES6 module that will serve only to store the l20n context object. That way, inside my ember helper, I could just do:
import l20n from "/????"
and prevent the l20n object from bogging down the rest of the app.
Thanks in advance for any help!!!
Update:
One solution is to use application.register, like so:
application.register('l20n:main', ctx, {instantiate: false});
BUT, then my problem would be accessing the container from my helper. This:
var l20n = this.container.lookup('l20n:main');
won't work because container is not available to helpers!
So, it turns out that my helper already had access to the container!
This is all I had to do to get it to work:
import Ember from 'ember';
export function translate(word) {
var ctx = this.container.lookup('l20n:main');
return ctx.getSync(word);
}
export default Ember.Handlebars.makeBoundHelper(translate);

How and when to use Ember.Application register and inject methods?

I'm trying to understand how to use Ember.Application register & inject methods
What use case are these functions designed for?
How are they to be used and when?
I'd really like to know!
Ember by default does dependency injection when it boots your application using mostly conventions, for example if you use ember-data then an instance of the store class is injected in every route and controller in your application, so you can later get a reference by simply doing this.get('store') inside any route or controller.
For example here is a code extract where the default store get's registered (taken from the source)
Ember.onLoad('Ember.Application', function(Application) {
Application.initializer({
name: "store",
initialize: function(container, application) {
application.register('store:main', application.Store);
...
}
container.lookup('store:main');
}
});
And then injected (source)
Application.initializer({
name: "injectStore",
initialize: function(container, application) {
application.inject('controller', 'store', 'store:main');
application.inject('route', 'store', 'store:main');
application.inject('dataAdapter', 'store', 'store:main');
}
...
});
In other words register and inject are methods to register dependencies and inject them yourself.
Let's assume you have a Session object which you populate after a server request on application start, and which you want to have a reference to in every controller, you could do something like this:
var App = Ember.Application.create({
ready: function(){
this.register('session:current', App.Session, {singleton: true});
this.inject('controller', 'session', 'session:current');
}
});
App.Session = Ember.Object.extend({
sessionHash: ''
});
This code would set the session property of every controller instance to a singleton instance of App.Session, so you could in any controller do this.get('session') and get a reference to it, and since it's defined as a singleton it would be always the same session object.
With register you can register controllers, models, views, or any arbitrary object type. inject, in the other hand, can inject onto all instances of a given class. For example inject('model', 'session', 'session:current') would also inject the session property with the session:current instance into all models. To inject the session object, let's say onto the IndexView you could do inject('view:index', 'session', 'session:current').
Although register and inject are very powerful you should use them wisely and only in the case you really know there is no other way to achieve your goal, I guess the lack of documentation is an indicator for discouragement.
Update - No good explanation without a working example
Since It's mostly a must to provide a working example with an explanation, there it goes: http://jsbin.com/usaluc/6/edit. Notice how in the example we can simply access the mentioned sessionHash by referring to the current controller's session object with {{controller.session.sessionHash}} in every route we are in, this is the merit of what we have done by registering and injecting the App.Session object in every controller in the application.
Hope it helps.
A common use case is to provide the current loggedin user property to controllers and routes as in https://github.com/kelonye/ember-user/blob/master/lib/index.js and https://github.com/kelonye/ember-user/blob/master/test/index.js

Cannot create controller binding since changing to router v2

Have just upgraded my application to 1.0.0-pre.4 and am in the process of changing my router to the new router API, however I cannot seem to be able to create a binding between my controllers anymore.
So in my main ApplicationController, I have the following:
App.ApplicationController = Em.ArrayController.extend({
user: App.User.create()
});
And then in v1 of the router API, I had the following:
App.IndexController = Ember.ArrayController.extend({
userBinding: 'App.router.applicationController.user',
});
However, with changing over to v1 of the router API, App.router is no longer defined. Everything I try does not seem to work, even setting userBinding to 'App.ApplicationController.user' does not work - it's as if the applicationController no longer is working.
What I am trying to achieve is to create an instance of my user model and then share it across a number of routes/views.
Any ideas would be appreciated.
Unfortunately Ember have hidden all instances of the singleton controllers to prevent users from implementing bad practice code. You shouldn't be referencing controllers explicitly, and instead you should be decoupling everything and using dependency injection to pass in things to your controller.
In the previous releases of Ember, we had connectControllers which allowed you to connect controllers to one another, but now with this latest release of Ember, we just use "set" in the router to pass in other controllers.
In your example you have a an IndexController and a UserController, to get access to the userController from within the indexController, you'll need to do something like the following:
(Bear in mind that all of this takes place in Ember's Router, which you can read more about here: http://emberjs.com/guides/routing/setting-up-a-controller/)
App.UserRoute = Ember.Route.extend({
setupController: function(controller) {
this.controllerFor('index').set('userController', controller);
}
});
Your indexController will now have the ability to read information from the userController. In a template this may look like the following:
{{controller.userController.name}}
There is another workaround using "needs" in ObjectControllers.
Here is a reference on how to use this.
http://eviltrout.com/2013/02/04/ember-pre-1-upgrade-notes.html