Ember setupController and transient controllers - ember.js

I'm trying to use setupController method to pass some data to the controller from the route and it only works if the controller is a singleton.
The setupController method is called in both situations but the variables are only set on the controller if it's a singleton.
How can I pass data from a route to a transient controller?
Here's a twiddle:
http://ember-twiddle.com/ba55734e925664e363f4
Uncomment/comment the following line to toggle between singleton/transient:
//application.register('controller:application', 'ApplicationController', { singleton: false });
I have not been able to find any information about whether or not this should work. I'm using Ember 1.13.6.
controllers/application.js:
import Ember from 'ember';
export default Ember.Controller.extend({
appName:'Ember Twiddle'
});
initializers/application.js:
export function initialize(container, application) {
//application.register('controller:application', 'ApplicationController', { singleton: false });
}
export default {
name: 'application-init',
initialize: initialize
};
routes/application.js:
import Ember from 'ember';
export default Ember.Route.extend({
setupController: function(controller,model) {
this._super(controller,model);
controller.var1 = "Variable 1";
}
});
templates/application.hbs:
<h1>Welcome to {{appName}}</h1>
<br>
<br>
{{var1}}
<br>

This appears to be an actual bug, since the instance of the controller is different from the instance you have in setupController and the one backing the view.
A workaround would be overriding the renderTemplate hook on your route to pass the instance of the controller versus a string reference which is looked up by default (and creating a new instance of the controller!).
export default Ember.Route.extend({
setupController(controller, model) {
this._super(...arguments);
controller.set('var1', 'foo');
},
renderTemplate(controller, model) {
// note: don't call super here
this.render('application', {
controller: controller,
model: model
});
}
});

Related

Need help to understand how ember.js Ember Data works

I am very new to ember.js.
I have the following code which I need to change to retrieve data from the server using multiple models (using multiple JSON/RESTful calls).
This (single model version) WORKS:
In app/routes/index.js:
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return this.get('store').queryRecord('wallet', {balance: true});
}
});
and in wallet-balance.hbs:
<div>Your Total Score: {{wallet.balance}} </div>
I changed to this and it WORKS:
import Ember from 'ember';
import RSVP from 'rsvp';
export default Ember.Route.extend({
model() {
return RSVP.hash({
wallet: this.get('store').queryRecord('wallet', {balance: true})
});
}
});
and in wallet-balance.hbs:
<div>Your Total Score: {{wallet.wallet.balance}} </div>
BUT if I change to the following ("wallet" -> "anythingelse"), it WON'T WORK:
import Ember from 'ember';
import RSVP from 'rsvp';
export default Ember.Route.extend({
model() {
return RSVP.hash({
anythingelse: this.get('store').queryRecord('wallet', {balance: true})
});
}
});
and in wallet-balance.hbs:
<div>Your Total Score: {{anythingelse.wallet.balance}} </div>
I'm trying to understand why. Where is it picking up from for the definintion "wallet" - and why changing to "anythingelse" won't work? Where else is the code for "wallet" referring to?
Components are isolated from their surroundings, so any data that the component needs has to be passed in. so you need to understand how to pass properties to component.
I assume, in all the above three examples you are including wallet-balance component like this.
{{wallet-balance wallet=model}}
If you want to make {{anythingelse.wallet.balance}} this one work for 3rd example, then you need to include the component like {{wallet-balance wallet=model.anythingelse}}
For debugging in template hbs file, you can make use of log helper, like {{log 'model object' model}} this will print model object in console.
Here is the reasoning behind the screen,
Whatever is returned from model hook will be set in corresponding controller's model property by default through setupController hook method.
In your case, you didn't override setupController so default behavior is applicable.
return this.get('store').queryRecord('wallet', {balance: true});
queryRecord will return Promise and it will be resolved to single record wallet and it will be set in controller's model property. now model is equivalent to single wallet record object. you can access it in template `{{model.balance}}
return RSVP.hash({
anythingelse: this.get('store').queryRecord('wallet', {balance: true})
});
queryRecord will return Promise and it will be resolved to single record wallet and it will be set in inside the object {anythingelse:walletRecord} now model is equivalent to {anythingelse:walletRecord}. you can access it in template like {{model.anythingelse.balance}}
You need to set your model in setupController().
import Ember from 'ember';
import RSVP from 'rsvp';
export default Ember.Route.extend({
model() {
return this.get('store').queryRecord('wallet', {balance: true});
},
setupController(controller, wallet) {
controller.set('wallet', wallet);
}
});
Hope this helps.

Computed property on a service that accesses the store

I wrote a service for loading notifications:
import Ember from 'ember';
export default Ember.Service.extend({
sessionUser: Ember.inject.service(),
store: Ember.inject.service(),
read() {
let currentUserId = this.get('sessionUser.user.id');
return this.get('store').query('notification', {
userId: currentUserId,
read: true
});
},
unread() {
let currentUserId = this.get('sessionUser.user.id');
return this.get('store').query('notification', {
userId: currentUserId,
read: false
});
}
});
I want to change the colour of an icon in the navigation bar when there are unread notifications. The navigation bar is a component:
import Ember from 'ember';
export default Ember.Component.extend({
notifications: Ember.inject.service(),
session: Ember.inject.service(),
hasUnreadNotifications: Ember.computed('notifications', function() {
return this.get('notifications').unread().then((unread) => {
return unread.get('length') > 0;
});
})
});
And the template then uses the hasUnreadNotifications property to decide if the highlight class should be used:
<span class="icon">
<i class="fa fa-bell {{if hasUnreadNotifications 'has-notifications'}}"></i>
</span>
However, it doesn't work. Although the store is called and notifications are returned, the hadUnreadNotifications doesn't resolve to a boolean. I think this is because it returns a promise and the template can't deal with that, but I'm not sure.
Questions
Is it idiosyncratic ember to wrap the store in a service like this. I'm doing this because it feels clumsy to load the notifications in the application route just to show the count.
Why doesn't hasUnreadNotifications return a boolean?
Is it possible to make read and unread properties instead of functions, so a computed property can be created in the service to calculate the count?
Returning promise from computed property will not work. Computed properties are not Promise aware. to make it work you need to return DS.PrmoiseObject or DS.PromiseArray.
You can read other options available from this igniter article.
import Ember from 'ember';
import DS from 'ember-data';
export default Ember.Component.extend({
notifications: Ember.inject.service(),
session: Ember.inject.service(),
hasUnreadNotifications: Ember.computed('notifications', function() {
return DS.PromiseObject.create({
promise: this.get('notifications').unread().then((unread) => {
return unread.get('length') > 0;
})
});
})
});

Create Custom Emberjs Service Error: Attempting to inject an unknown injection: `service:titleService`

I hit the above Error: Attempting to inject an unknown injection: service:titleService with the following code:
// initializers/titleService
export default {
name: 'titleService',
initialize: function(container, application) {
application.inject('route', 'titleService', 'service:titleService');
}
};
// services/titleService.js
import Ember from 'ember';
export default Ember.Service.extend({
title(name) {
this.set('title', name);
}
});
// routes/login.js
import Ember from 'ember';
export default Ember.Route.extend({
titleService: Ember.inject.service(),
actions: {
didTransition: function() {
this.set('titleService.title', 'Login Page');
}
}
});
// templates/application.hbs
<div class="page-header">
<h1>{{titleService.title}}</h1>
</div>
{{outlet}}
am I missing anything?
You have to follow the naming conventions of Ember - If you're referring to your service as titleService, then you want the file to be title-service.js, not titleService.js.
Seems like there is an issue where you are trying to inject route into your TitleService. It's probably a typo that should be router instead of route. If you want to use the router inside your service you could also inject the -routing service, but be careful since it is part of the private API.
Example:
import Ember from 'ember';
export default Ember.Service.extend({
routing: inject.service('-routing'),
someFunc() {
const router = get(this, 'routing').router;
// Do something with the router here
}
});
More information can be found in this thread: http://discuss.emberjs.com/t/routing-as-a-service/8550/3

Ember queryParams not updating URL

I'm trying to set queryParams in an Ember controller, but they don't seem to be updating the URL at all.
I have this abbreviated mixin being applied to the route:
import Ember from 'ember';
import ControllerPaginationMixin from './controller-pagination';
export default Ember.Mixin.create({
setupController(controller, model) {
this._super(controller, model);
controller.reopen(ControllerPaginationMixin);
}
});
And here's the abbreviated controller mixin that is applied above:
import Ember from 'ember';
export default Ember.Mixin.create({
sortKey: null,
queryParams: ['sortKey'],
actions: {
sort(key) {
this.set('sortKey', key);
}
});
When I call the sort method from a component, I can see in the Ember Inspector that the sortKey property has been changed to the correct new value, but the URL remains unchanged. Am I missing something?
Your problem is that you're trying to customize the controller class at the runtime.
You will reopen the controller every time a user visits the route, that's ridiculous.
Simply extend the controller definition with the mixin and you're good to go.

Ember Mixins and the needs property

I have sort of a general question on how the controller needs property works with regard to mixins
Lets say I have a shopping card model that resides in the application controller so it's available everywhere in the app
// application route
export default Ember.Route.extend({
setupController: function(controller, model) {
this._super(controller, model);
controller.set('cart', this.store.createRecord('cart'));
}
});
Now any other controller that needs to use the cart, I want to provide a mixin:
// mixins/cart-access
export default Ember.Mixin.create({
needs: ['application'];
cart: Ember.computed.alias('controllers.application.cart')
});
// some controller
export default Ember.Controller.extend(CartAccess, {});
This is all well and good, but will it cause problems if in another controller I set the needs property to something else?
// some other controller
export default Ember.Controller.extend(CartAccess, {
needs: ['some-other-controller'] // not inlcuding application
});
Went ahead and did an experiment, and the needs from the mixin will be merged with the needs from the controller.
Example: https://github.com/DVG/need-experiment
//application route
export default Ember.Route.extend({
setupController: function(controller, model) {
this._super(controller, model);
controller.set('hello', "Hi!")
}
});
//hi mixin
export default Ember.Mixin.create({
needs: ['application'],
hello: Ember.computed.alias("controllers.application.hello")
});
//people contntroller
import Hi from 'needs/mixins/hi';
export default Ember.Controller.extend(Hi,{});
//posts controller
import Hi from 'needs/mixins/hi';
export default Ember.Controller.extend(Hi, {
needs: ['something-else']
});
//posts.hbs
{{hello}}
{{needs}}
The posts template displays "Hi!" and application,something-else