I am using the latest Ember, 2.1, and am wondering how to set some application wide variables, such as user id, username, email, etc, presumably on the application controller.
While ember applications have tons of files, I haven't really done a lot yet, I'm somewhat confident I'm sharing just the right code. I don't have a login route file. I have the ember simple auth plugin installed, but I'm not actually using/invoking it any special way, except for mixing it into my application route:
import ApplicationRouteMixin from 'simple-auth/mixins/application-route-mixin';
export default Ember.Route.extend(ApplicationRouteMixin)
My router:
this.route('login')
My login template:
<button {{action 'forceLogin'}}>Force login of devinrhode2#gmail.com by clicking this action button</button>
<p>Your account id is: {{account_id}}</p>
My login controller:
export default Ember.Controller.extend({
actions: {
forceLogin: () => {
var data = {
"account_id": 123,
"email":"devinrhode2#gmail.com",
"name":"Devin Rhode"
}
this.setProperties(data)
}
}
});
I have the forceLogin controller action being called, but, the {{account_id}} is not populating into the template. How could I get the account_id to render back into the template? How could I make the account_id globally accessible to my ember application by calling this.get('account_id') wherever I need it?
Currently I get the error:
Cannot read property 'setProperties' of undefined
You get the error because of the way you define forceLogin. Arrow functions are bound to the context where they're defined. Here's what your code compiles to:
var _this = this; // we capture `this` from out here!
export default Ember.Controller.extend({
actions: {
forceLogin() {
...
_this.setProperties(data) // `_this` is the window!
}
}
});
That's no good because this should be the instance of the controller and instead it's the window.
Instead you should define forceLogin like this:
export default Ember.Controller.extend({
actions: {
forceLogin() {
...
this.setProperties(data) // `this` is our controller instance
}
}
});
To get the account_id from somewhere else, you can inject the login controller:
// in some other controller
export default Ember.Controller.extend({
login: Ember.inject.controller(),
actions: {
doSomethingWithTheAccountId() {
var accountId = this.get('login.account_id');
...
}
}
});
It would be cleaner to move those properties to a service, which you can inject anywhere with Ember.inject.service()
Related
I use ember simple auth to authenticate my users.
It workls perfectly fine except when the user just logged in.
I have an helper that ask for the current user :
import Helper from '#ember/component/helper';
import { inject as service } from '#ember/service';
export default Helper.extend({
currentUser: service('current-user'),
compute([feature,currentUser]) {
let role = currentUser.get('user.role')
Helper is used in a template embedded in a template in application.hbs
{{#if (permission-for "users" currentUser)}}
<li>{{#link-to 'users'}}<i class="fa fa-user"></i> <span>Clients</span>{{/link-to}}</li>
{{/if}}
The current user service is a duplicate of this one from the doc : https://github.com/simplabs/ember-simple-auth/blob/master/guides/managing-current-user.md#loading-the-user-with-its-id
And my application.js is also using the code from the same guide.
From what I tried, the sessionAuthenticated start to run, and during await this._loadCurrentUser(); the helper is executed, and it finish to run sessionAuthenticated
What happen is that the user attribute I want is undefined and it make it impossible to display interface elements according to the role of the User.
How can I fix this problem?
Edit for related question :How does ember simple auth is supposed to be used in template to determine if User is supposed to see some part of the interface or not ? Like for example menu choices.
The problem with your approach is the helper is being executed synchronously and isn't "watching" the currentUser service in a way that would recompute. It would be a lot easier to use a computed property to do what you want:
controllers/application.js
currentUser: service(),
hasPermissionForUsers: computed("currentUser.user", function() {
const user = this.get("currentUser.user");
if (! user) return false;
return user.get("role") === "users"; // taking a random guess at what your helper does here
})
This property will now re-compute once the user is loaded:
application.hbs
{{#if hasPermissionForUsers}}
<li>{{#link-to 'users'}}<i class="fa fa-user"></i> <span>Clients</span>{{/link-to}}</li>
{{/if}}
If you have to use a helper, it is doable but it's not pretty. The reason it's not as elegant is because helpers don't have access to the same context so it's more difficult to recompute. You could do something like this:
import Helper from '#ember/component/helper';
import { inject as service } from '#ember/service';
import { run } from '#ember/runloop';
import { addObserver, removeObserver } from '#ember/object/observers';
export default Helper.extend({
currentUser: service(),
willDestroy() {
this._super(...arguments);
const container = this.get('_container');
removeObserver(container, 'currentUser.user', this, this.update);
},
compute([feature, container]) {
addObserver(container, 'currentUser.user', this, this.update);
this.set('_container', container); // used for cleanup
const user = this.get('currentUser.user');
if (! user) return false;
return user.get('role') === feature;
},
update() {
run.once(this, this.recompute);
}
});
Then use it like so:
application.hbs
{{#if (permission-for "users" this)}}
<li>{{#link-to 'users'}}<i class="fa fa-user"></i> <span>Clients</span>{{/link-to}}</li>
{{/if}}
First I would recommend to not pass the currentUser service to the helper if you inject it to the service.
Next you can let the helper recompute after the user was loaded:
export default Helper.extend({
currentUser: service('current-user'),
init() {
if(!this.currentUser.user) {
this.currentUser.runAfterLoaded.push(() => this.recompute());
}
},
compute([feature]) {
if(!this.currentUser.user)
return false;
}
let role = this.currentUser.get('user.role')
...
return ..;
}
});
you'll also want to add this.runAfterLoaded.forEach(x => x()) to the load() function in the current-user service after the this.set('user', user); as well as this.set('runAfterLoaded', []) to the init hook.
Another possibility is to build a mixin that you add to the relevant routes after the AuthenticatedRouteMixin that ensures the current user will be loaded.
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.
In my EmberJS application, I have a current user initializer, which injects the user into all controllers, routes, and views. It works great when logged in. I need the synchronous loading of the current user object so I can check some user permissions right off the bat.
Here's my initializer:
App.CurrentUserInitializer - Ember.Initializer.extent({
name: 'current-user',
after: 'authentication',
initialize: function(container, app) {
var store = container.lookup('store:main');
app.deferReadiness();
store.find('user', 'me').then(function (user) {
app.register('session:user', user, {instantiate: false});
_.forEach(['route', 'controller', 'view'], function (place) {
app.inject(place, 'currentUser', 'session:user');
});
app.advanceReadiness();
}).catch(function () {
app.advanceReadiness();
});
}
});
Where this breaks down for me, is during login. When the app boots, the initializer is run, but the /users/me route returns a 401 error. If I don't catch the error and advanceReadiness, booting halts. By catching the error, the app boots, but the initializer isn't run again after login, so the current user isn't loaded.
What are my options here? I can't use #marcoow's recommended method of adding a computed property to the Session, as I need the user loaded at boot time.
I've tried forcing loading the user object on the IndexRoute as a hack, but that doesn't seem to work.
Any tips are appreciated.
I'd register a session:current object with user property being null. That would be injected into controllers and routes (not sure injecting inside views is a good idea).
So at boot time the user is unknown, but the user lookup is done before the router goes deeper than application route, the root:
In the beforeModel hook of the application route, you'll load that current user. Then:
either you got the user and you set it this.set('session.user', model)
or you'll go in the error hook of the application route, in which case you'd have to check why, and if 401 then you can redirect the user to the login route this.transitionTo('login')
Don't forget to set a flag on session if you got the 401 so that transitionTo will make our user lookup of beforeModel to not happen again until we reach the login route
The code to be used to load the session user and initialise it could be placed in that session:current object so that you could call it from the application route or the login controller.
This is for example my session initialiser (not exactly as I explained, but loading in the initialiser, so closer to what you hve). I used a session model so that I do /session/current and then got a user into it (or not) which has the correct id and not me which then would make the store to load the same user with another id, and so have twice the same user as 2 different records:
app/models/session.js:
import DS from 'ember-data';
import Ember from 'ember';
export default DS.Model.extend({
user: DS.belongsTo('user'),
isAuthenticated: Ember.computed.bool('user.isClaimed')
});
app/initializers/session.js:
import Ember from 'ember';
export default {
name: 'session',
after: 'store',
initialize: function (container, app) {
var store = container.lookup('store:main'),
sid = Ember.$.cookie('sid');
// used to register a session
var register = function (session) {
app.register('session:main', session, {instantiate: false});
app.inject('route', 'session', 'session:main');
app.inject('controller', 'session', 'session:main');
};
// used to create a new session and trigger the backend to get details about it
// useful if the server is able to give an existing session while the browser doesn't know about it
// with external providers for example
var newSession = function () {
var session = store.createRecord('session');
// be sure to wipe out any invalid session ID
Ember.$.removeCookie('sid');
register(session);
return session.save().then(function (model) {
// if we got a valid new session, save its ID
Ember.$.cookie('sid', model.get('id'));
}).catch(function () {
Ember.debug('error saving new session: ' + Array.prototype.join.call(arguments, ', '));
});
};
// overall logic ==================
app.deferReadiness();
if (sid) {
// try to load the existing session
store.find('session', sid).then(function (model) {
register(model);
app.advanceReadiness();
}).catch(function () {
// there was a cookie for the session but it might have expired or the server invalidated it
Ember.debug('error loading session: ' + Array.prototype.join.call(arguments, ', '));
newSession().finally(function () {
app.advanceReadiness();
});
});
}
else {
// we don't have any trace of a session, let's just create a new one
newSession().finally(function () {
app.advanceReadiness();
});
}
}
};
app/router.js:
import Ember from 'ember';
var Router = Ember.Router.extend();
Router.map(function () {
this.resource('session', {path: '/'}, function(){
this.route('login');
this.route('logout');
});
});
export default Router;
app/templates/application.hbs (as an example):
<h2 id='title'>Welcome to my app</h2>
{{#if session.isAuthenticated}}
<a {{action 'session.logout'}}>Logout</a>
{{else}}
{{#link-to 'session.login'}}Login{{/link-to}}
{{/if}}
{{outlet}}
Then once in login controller, when the user actually logs in, the server will return the session model with the user linked into it, so the Ember binding magic will just update the session object.
I am trying to implement an application-level error handler to catch failed promises within my application. Most of the suggestions I've seen around this have to do with error logging, but I actually want to trigger a modal window to let my users know they're no longer connected to the internet + can only read data.
I'd like to trigger an action from within the RSVP.onerror handler. The only way I've managed to do this so far is with something like this:
Ember.RSVP.configure('onerror', function(e) {
window.App.__container__
.lookup('controller:application')
.send('showModal', 'disconnected');
});
but it seems a bit hacky to me.
Is there somewhere more appropriate to write this code? More generally, are there any best practices around notifying users of a lost internet connection?
Ember has a built in error route (docs here).
Depending on how you want to handle other kinds of errors in your app, you could add views/error.js and run a method on willInsertElement in that view that tests whether the user is offline and, if true, sends the showModal action.
Alternatively, failed promises can automatically be caught in the original promise and trigger some method that has been injected into the controller or whatever instance is making the promise:
Controller:
this.set('thing').then(function() {
// Success
}, this.offlineError()); // Failure
Initializer (note the syntax is for ember-cli):
export default {
name: 'offline-error',
initialize: function(container, app) {
var offlineError = function() {
// or something like this
container.lookup('controller:application').send('showModal', 'disconnected');
};
app.register('offlineError', offlineError, { instantiate: false });
// Inject into routes, controllers, and views.
Em.A(['route', 'controller', 'view']).forEach(function(place) {
app.inject(place, 'offlineError', 'offlineError:main');
});
}
};
The above code makes the offlineError method available in all routes, controllers, and views but the beauty of it is that the method has access to the container without using the hacky private property.
I ended up wrapping Offline.js in a service. I'm using ember-cli.
// services/connection.js
/* global Offline */
import Ember from 'ember';
// Configure Offline.js. In this case, we don't want to retry XHRs.
Offline.requests = false;
export default Ember.Object.extend({
setup: function() {
if (Offline.state === 'up') {
this._handleOnline();
} else {
this._handleOffline();
}
Offline.on('down', this._handleOffline, this);
Offline.on('up', this._handleOnline, this);
}.on('init'),
_handleOffline: function() {
this.set('isOffline', true);
this.set('isOnline', false);
},
_handleOnline: function() {
this.set('isOnline', true);
this.set('isOffline', false);
}
});
I injected the service into controllers and routes:
// initializers/connection.js
export default {
name: 'connection',
initialize: function(container, app) {
app.inject('controller', 'connection', 'service:connection');
app.inject('route', 'connection', 'service:connection');
}
};
Now in my templates, I can reference the service:
{{#if connection.isOffline}}
<span class="offline-status">
<span class="offline-status__indicator"></span>
Offline
</span>
{{/if}}
(Offline.js also has some packaged themes, but I went with something custom here).
Also, in my promise rejection handlers I can check if the app is offline, or if it was another unknown error with the back-end, and respond appropriately.
If anyone has any suggestions on this solution, chime in!
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')
{
// ...
}