I'm converting a globals based real-time Ember app to an es6 based app that utilizes ember-cli. In my app I need to know the current route fairly often. In the globals version, I was doing this.
Globals Pattern
var MyApp = Ember.Application.create({
currentRoute : ''
});
MyApp.Route = Ember.Route.extend({
actions : {
didTransition : function () {
MyApp.set('currentRoute', this);
}
}
});
I could then do MyApp.get('currentRoute') from within my session or offline controllers when determining how / where to transition when certain events occurred.
When using ember-cli, I import the app to be able to reference it from the necessary controllers.
import MyApp from "../app";
But it turns out that MyApp.currentRoute, MyApp.get, and MyApp.set are all undefined.
Part of me thinks this is a bug in ember-cli that the application instance no longer has bound getters and setters. Part of me realizes it's not a great practice to store things on the application instance either.
I could get around this issue by converting all instances of MyApp.get and MyApp.set to Ember.get(MyApp, ...) and Ember.set(MyApp, ...) respectively, but I thought I'd ask here first as this seems to either be an issue with Ember-Cli or else something where there's a better recommended way to achieve what I need.
If you look at app.js (what you are importing), it is not your application instance, it is exporting a subclass of Ember.Application. That's why get et al are not available on it.
var App = Ember.Application.extend({ <----- NOT create
modulePrefix: config.modulePrefix,
podModulePrefix: config.podModulePrefix,
Resolver: Resolver
});
export default App;
To get the actual application instance from within your route use:
Ember.getOwner(this).lookup('application:main')
Related
I'm using ember-cli 1.13.8 and I have a service that handles most of my logic. Right now I have a function that listens to whether certain things are true or false and then can make a route change based upon that. I'd rather not have to call that function from inside every route since I want it to happen on every route. Its goal is to determine whether the player won and every interaction in the game drives this.
Inside of my game service:
init() {
...
if(true) {
console.log("you've won!");
this.transitionTo("congratulations");
}
},
Of course, this fails because this isn't a route like Ember expects. I know I can call this method from inside of every route instead but I'm wondering if there is a better way to do this.
Thanks
Edit
So far I've tried importing in the App and then trying to extend the Router. This seems like a bad idea though.
You can use the routing service (which is a private API):
routing: Ember.inject.service('-routing'),
init() {
...
if(true) {
console.log("you've won!");
this.get("routing").transitionTo("congratulations");
}
},
As of Ember 2.15, there is a public router service for exactly this use case. Just add router: Ember.inject.service(), to your Ember class and call this.get('router').transitionTo(...);, easy!
Generally this is a bad idea, but in some cases it's easier than passing through route actions in 100 places (personal experience).
The better way to do this from anywhere is to look the router up on the container:
Ember.getOwner(this).lookup('router:main').transitionTo(...);
this has to be some container allocated Ember object, which includes components, services, and Ember Data models.
Note also that if this will be called a lot, you will want to store the router as a property. You can do this in the init hook:
init() {
this._super(...arguments);
this.set('router', Ember.getOwner(this).lookup('router:main'));
}
...
this.get('router').transitionTo(...);
Ember.getOwner(this) works in Ember 2.3+, prior to that you can use this.get('container') instead.
Ember 1.13:
Create another service called routing:
import Ember from 'ember';
export default Ember.Service.extend({
_router: null,
init() {
this._super();
this.set('_router', this.get('container').lookup('router:main'));
},
transitionTo() {
this.get('_router').transitionTo(...arguments);
}
});
Then you can:
routing: Ember.inject.service(),
goSomewhere() {
this.get('routing').transitionTo('index');
}
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.
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.
I asked a question similar to this, here, specifically about how to implement specific settings for a specific controller. In short, I wanted to implement checkInSettings for the whole CheckInController so that my index, settings, and reports templates and controllers have access to the checkInSettings.
I did get my answer to that; however, I think that specific settings might be limiting and it would be better served by making a settings object or store, and defining something like settings.checkIn for the check in settings.
I've looked for resources online but haven't come up with many answers... So, how should I best go about creating application wide settings, with sub settings for specific areas of my app?
A note: I would like to refrain from using Ember Data since it is not Production Ready yet, and this app will eventually be consumer facing.
Thank you!
Ember Data is a different beast. Store them on the application controller. Or if you don't want o clutter the application controller, create a singleton instance of a settings controller and store them there. (The same thing can be done just on the application controller, just use application instead of settings).
App.SettingsController = Ember.Controller.extend({
someSettingOn: false,
someOtherSetting: null
});
And then in other routes/controllers:
App.AnyRoute = Ember.Route.extend({
anyMethod: function(){
this.controllerFor('settings').toggleProperty('someSettingOn');
}
})
App.AnyController = Ember.Controller.extend({
needs: ['settings'],
anyMethod: function(){
var setting = this.get('controllers.settings.someOtherSetting');
console.log(setting);
},
anyProperty: function(){
if(this.get('controllers.settings.someSettingOn')){
return 'yes';
}
return 'no';
}.property('controllers.settings.someSettingOn')
})
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