I am trying to do conditional transition in my router.js.
I need to determine the condition(Which is eligibility) based on the boolean value returned by another rest service.
Can I write a function to determine that value in router.js and how to use that value to redirect my transition to two separate routes.
If the eligibity is true, I need to do
'
this.route('coverage', {
path: '/'
});
else
this.route('inEligible', {
path: '/'
});
'
Please provide me an example. I am very new to ember.
I use this pattern in my routes:
import Route from '#ember/routing/route';
import { inject as service } from '#ember/service';
export default class SettingsRoute extends Route {
#service currentUser;
async beforeModel() {
// inside the service, I have logic for checking if the user
// authenticated, not expired, and fetched
const isLoggedIn = await this.currentUser.isLoggedIn();
if (!isLoggedIn) {
this.transitionTo('setup');
return;
}
}
}
if you're using non-native classes:
import Route from '#ember/routing/route';
import { inject as service } from '#ember/service';
export default Route.extend({
currentUser: service();
async beforeModel() {
// inside the service, I have logic for checking if the user
// authenticated, not expired, and fetched
const isLoggedIn = await this.currentUser.isLoggedIn();
if (!isLoggedIn) {
this.transitionTo('setup');
return;
}
}
});
Related
I have the following route that will poll a model and refresh the data at a given interval. What I'm trying to do is trigger an alert when a new record is available in the model. I'm new to this, so I'm having some trouble figuring out how to trigger an alert site-wide without simply triggering it each time the model refreshes. I tried using 'didCreate' in the model, but it doesn't seem to recognize new records.
import Route from '#ember/routing/route';
import Ember from 'ember'
export const pollInterval = 8000 // time in milliseconds
export default Route.extend({
model() {
return Ember.RSVP.hash({
pat: this.store.findAll('pat'),
appt: this.store.findAll('appt')
})
},
getSMS () {
return this.get('store').findAll('smstext')
},
onPoll () {
return this.getSMS()
.then((users) => {
this.set('currentModel', users)
})
},
afterModel () {
let smsPoller = this.get('smsPoller')
if (!smsPoller) {
smsPoller = this.get('pollboy').add(this, this.onPoll, pollInterval)
this.set('smsPoller', smsPoller)
}
},
setupController(controller, models) {
controller.set('huddle', models.huddleappt);
controller.set('pat', models.pat);
}
})
I would recommend to use a service for this use case. You can inject your service wherever you need the data, and in the service you can handle the polling.
You can then display your data like this.
In your component file:
import Component from '#ember/component';
import { inject as service } from '#ember/service';
import { computed } from '#ember/object';
export default Component.extend({
smsService: service(),
smsData: computed('smsService.data')
// ...
And in your template you can access your data with the computed property from your components js file
My app is using a websocket service based on ember-phoenix to push new records from the API to the store. I would like these new records to render in my template when they're added.
I have a route where the model hook returns a filtered query promise:
import Ember from 'ember';
const {
get,
inject,
} = Ember;
export default Ember.Route.extend({
socket: inject.service(),
model(params) {
return this.store.query('my-model', {filter: {date: params.date}})
},
afterModel() {
get(this, 'socket').joinSchedule();
},
resetController() {
get(this, 'socket').leaveSchedule();
},
});
When new records are pushed to the store through the websocket, they are not rendered by my template because of how store.query works. If I change store.query to store.findAll the new records are rendered, but I want my route to only load a subset of all the records based on the date query param.
It seems like my only option is to just reload the route's model when a new record is pushed to the store. Is it possible to do this from the service? If not, is there a different approach I might want to explore?
The relevant parts of my socket service are below:
import Ember from 'ember';
import PhoenixSocket from 'phoenix/services/phoenix-socket';
const {
get,
inject,
} = Ember;
export default PhoenixSocket.extend({
session: inject.service(),
store: inject.service(),
joinSchedule() {
const channel = this.joinChannel(`v1:my-model`);
channel.on('sync', (payload) => this._handleSync(payload));
},
_handleSync(payload) {
get(this, 'store').pushPayload(payload);
},
});
Option 1
You can use Ember.Evented to subscribe and dispatch event. I have created twiddle for demonstration.
In socket service,
socket should extend Ember.Evented class
export default PhoenixSocket.extend(Ember.Evented, {
After updating store, you can just trigger myModelDataLoaded which will dispatch all the functions subscribed to myModelDataLoaded.
_handleSync(payload) {
get(this, 'store').pushPayload(payload);
this.trigger('myModelDataLoaded'); //this will call the functions subscribed to myModelDataLoaded.
}
In Route,
You can subscribe to myModelDataLoaded
afterModel() {
get(this, 'socket').joinSchedule();
get(this, 'socket').on('myModelDataLoaded', this, this.refreshRoute); //we are subscribing to myModelDataLoaded
}
Define refreshRoute function and call refresh function.
refreshRoute() {
this.refresh(); //forcing this route to refresh
}
To avoid memory leak need to off subscribtion, you can do it either in resetController or deactivate hook.
resetController() {
get(this, 'socket').leaveSchedule();
get(this, 'socket').off('myModelDataLoaded', this, this.refreshRoute);
}
Option 2.
You can watch store using peekAll with observer and refresh route.
In your controller,
1. Define postModel computed property which will return live record array.
2. Define postModelObserver dependant on postModel.[] this will ensure whenever store is updated with new row, it will be observed by myModelObserver and it will send action refreshRoute to route . where we will call refresh. As you know this will call beforeModel, model, afterModel method.
As you know computed property is lazy, when you are accessing it only then it will be computed. so if you are not using it in template, then just add this.get('myModel') in init method
Controller file
import Ember from 'ember';
const { computed } = Ember;
export default Ember.Controller.extend({
init() {
this._super(...arguments);
this.get('postModel');//this is just to trigger myModel computed property
},
postModel: computed(function() {
return this.get('store').peekAll('post');
}),
postModelObserver: Ember.observer('postModel.[]', function() {
this.send('refreshRoute');
})
});
Route file - define action refreshRoute for refreshing, since refresh is available only in route.
import Ember from 'ember';
const {
get,
inject,
} = Ember;
export default Ember.Route.extend({
socket: inject.service(),
model(params) {
return this.store.query('my-model', { filter: { date: params.date } })
},
afterModel() {
get(this, 'socket').joinSchedule();
},
resetController() {
get(this, 'socket').leaveSchedule();
},
actions:{
refreshRoute() {
this.refresh();
},
}
});
You can trigger an event from your websocket service when you receive a message on the socket, then subscribe to it in your route and then call refresh() to reload your model.
There is also https://github.com/ember-data/ember-data-filter - which retuns live array.
It is not a better way, but one way to do with your existing code is using a callback.
import Ember from 'ember';
const {
get,
inject,
} = Ember;
export default Ember.Route.extend({
socket: inject.service(),
model(params) {
return this.store.query('my-model', {filter: {date: params.date}})
},
afterModel() {
let cb = (myModelRecord) => {
this.get('model').push(myModelRecord);
};
get(this, 'socket').joinSchedule(cb);
},
resetController() {
get(this, 'socket').leaveSchedule();
},
});
Call callback method in socket service,
import Ember from 'ember';
import PhoenixSocket from 'phoenix/services/phoenix-socket';
const {
get,
inject,
} = Ember;
export default PhoenixSocket.extend({
session: inject.service(),
store: inject.service(),
joinSchedule(cb) {
const channel = this.joinChannel(`v1:my-model`);
channel.on('sync', (payload) => cb(this._handleSync(payload)));
},
_handleSync(payload) {
return get(this, 'store').pushPayload(payload);
},
});
I'm trying to implement custom auth with ember-simple-auth and I stuck at the start. I have app/autheticators/digest.js
import Base from 'ember-simple-auth/authenticators/base';
import Ember from 'ember';
export default Base.extend({
restore(data) {
//
},
authenticate(email, password) {
console.log(email, password);
return new Ember.RSVP.Promise(function(resolve, reject) {
Ember.run(function() {
resolve({email: email, password: password});
});
});
},
invalidate(data) {
//
}
});
app/authorizers/digest.js
import Base from 'simple-auth/authorizers/base';
import Ember from 'ember';
export default Base.extend({
header: function() {
return "test-digest";
},
authorize: function(sessionData, block) {
console.log('authorize...');
block('Authorization', this.get('header'));
}
});
Login component:
import Ember from 'ember';
import CryptoJS from 'npm:crypto-js';
export default Ember.Component.extend({
session: Ember.inject.service('session'),
actions: {
login() {
let { email, password } = this.getProperties('email', 'password');
this.get("session").authenticate('autheticator:digest',
email, CryptoJS.SHA256(password).toString()).catch((reason) => {
this.set('errorMessage', reason.error);
});
}
}
});
Authentication called properly (I hope), but "authorize" in authorizer never called. I also tried add some values to ENV:
ENV['simple-auth'] = {
authorizer: 'authorizer:digest',
crossOriginWhitelist: ['http://prod-drunkedguru.rhcloud.com:80/'] // ['*'] I also tried
};
But nothing changed. What I'm doing wrong?
P.S. I'm using EmberJS 1.13.0 with EAS 1.0.
I assume you're using ESA 1.0. In that version the authorizer isn't automatically called anymore but you need to call it manually. There is the DataAdapterMixin that you can use to automatically authorizer Ember Data requests though. See this blog post for guidance on migrating to 1.0: http://log.simplabs.com/post/131698328145/updating-to-ember-simple-auth-10
I have a initializer like this:
import Ember from 'ember';
import Session from 'simple-auth/session';
var SessionWithCurrentUser = Session.extend({
store: Ember.inject.service(),
currentUser: function() {
console.log(this.get('store'));
console.log(this.store);
console.log(this.container.lookup('service:store'));
}.property('secure.access_token')
});
export default {
name: 'custom-session',
after: 'ember-data',
initialize(registry) {
registry.register('session:withCurrentUser', SessionWithCurrentUser);
}
};
currentUser gets called on user interaction, long after my app has finished loading. Only the last container lookup gives the store, the other 2 is an object:
{
_lastData: Object,
key: "ember_simple_auth:session"
[..]
}
What's going on? Why can't I inject the store?
It's because store in the current version of simple-auth is being overridden by an instance-initializer with the session storage. The next major version of simple-auth will turn the session storage into a service and we'll be able to do:
import Ember from 'ember';
const { service } = Ember.inject;
export default Ember.Service.extend({
session: service('session'),
store: Ember.inject.service(),
account: Ember.computed('session.content.secure.account_id', function() {
const accountId = this.get('session.content.secure.account_id');
if (!Ember.isEmpty(accountId)) {
return DS.PromiseObject.create({
promise: this.get('store').find('account', accountId)
});
}
})
});
From the dummy app, once https://github.com/simplabs/ember-simple-auth/pull/602 is merged.
I have a multi-step flow that the user can go through sequentially or jump straight to a section (if the sections in between are completed). I think this logic should be in the Route object. However, from within the controller, how do I access the route instance. For example, it would be ideal to be able to do something like this in the controller:
App.Flow = Em.ObjectController.extend({
submit: function(){
// Validation and XHR requests
// ...
// Go to the next step
route.goToNextStep();
}
}
From within a controller, you can access the router via this.get('target'). So this.get('target').send('goToNextStep') should work.
Like so:
App.Flow = Em.ObjectController.extend({
submit: function(){
// ...
this.get('target').send('gotoNextStep');
}
}
App.FlowRoute = Ember.Route.extend({
events: {
gotoNextStep: function(){
// ...
this.transitionTo(routeName);
}
}
}
You need to get the route for such conditions,
so from the controller just say,
App.Flow = Em.ObjectController.extend({
submit: function(){
var self =this;
// Validation and XHR requests
// ...
// Go to the next step
self.send('goToNextStep');
}
}
and define your goToNextStep event in your route's event hash
'this' is what points to the router, but you shouldn't add any methods to that prototype. Instead, make some sort of event that triggers the transition to the next step.
In addition to target, another way to do this in Ember now is with getOwner.
For example, to send an action to your application route:
import Component from '#ember/component';
import { action } from '#ember/object'; // https://github.com/pzuraq/ember-decorators-polyfill
import { getOwner } from '#ember/application';
export default class MyTopLevelComponent extends Component {
#action
closeModal() {
getOwner(this).lookup('route:application').send('closeModal');
}
});