ember computed alias breaks when getting first or last object - ember.js

The goal was to have a computed property based on whether there was an existing object in the store or not... Based on this answer, I believe that it should be okay to get the first object (or last) in the context of a computed alias. So here is what I'm trying to do:
import Ember from 'ember';
export default Ember.Controller.extend({
sessions: function () {
return this.store.peekAll('session');
}.property(),
// this doesn't work...
currentSession: Ember.computed.alias('sessions.firstObject'),
// this seems to work...
lastSession: function () {
return this.get('sessions.lastObject');
}.property('sessions.lastObject'),
...
});
I'm using ember 1.13.8 and didn't know if this might be related to an existing ember issue or if it's a bug or just me trying to use ember in a way that it wasn't meant to...
Just to clarify, it would appear that once the alias is computed, that 'getter' becomes undefined for the sessions array. So after that point, (in the above example) calling this.get('sessions.firstObject') would return undefined, as would calling this.get('sessions').firstObject
Is this a bug? If not, what's happening behind the scenes here?
Thanks!

This does work actually... I think it is likely something to do with the manner of testing in which I was setting properties on the window so that I could inspect them in the console. Maybe some other code was interfering, I have no clue, but it's working now.

Related

How do you set variable in route and display in template?

I am looking to set a variable for each page that is set to text to display as a dynamic header sort of thing. How would I set a variable in a route and display it in the corresponding template? So far everything I have tried is not working.
The context of a template in a route is its controller, not its route. Try moving the action to the controller.
See updated twiddle: https://ember-twiddle.com/b454e10355ae8c708c3b8dc24b51e44e?openFiles=controllers.my-route.js%2C
For more information about controllers: https://guides.emberjs.com/v2.16.0/controllers/
You can only pass route variable to the template by using model hook. However, model is called only once. In order to update route variable and see its final value in the template, you need to wrap the variable. After that, you need to update the variable inside wrapped and the referance of the wrapped will not be changed. Your code will be like:
route.js:
model(){
this._super(...arguments);
this.set('myWrappedVariable', {myVariable: 1});
let modelResult = {myWrappedVariable: this.get('myWrappedVariable')};
return modelResult;
},
actions:{
increaseVariables(){
Ember.set(this.get('myWrappedVariable'), 'myVariable', this.get('myWrappedVariable.myVariable')+1);
}
}
template.hbs
myWrappedVariable: {{model.myWrappedVariable.myVariable}}
Take a look at this twiddle for this usage.
#Gaurav answer is totally right and should be considered as best practice. However if you have a good reason for setting a variable, which is not the model, in a Route, you could use setupController hook therefore:
import Route from '#ember/routing/route';
export default Route.extend({
setupController(controller, model) {
// Call _super for default behavior
this._super(controller, model);
// Set a variable on controller
controller.set('foo', 'bar');
}
});
As shown in the api docs you could also use setupController to set a variable on another controller by getting an instance of it using controllerFor method. However I would not consider this a good practice, cause if it's not well documented throughout the application, it's quite hard to maintain such code.
As shown by #Ahmet Emre Kılınç's answer you could also use a hash as model. If at least one of it's properties is a Promise, you should return a RSVP.hash(). Otherwise model hook would not block the transition until the Promise is fulfilled.

Difference between Ember.get() and this.get()

I'm new to Ember and it keeps confusing me about the difference between this.get() and Ember.get(). Can someone explain them briefly?
Welcome to Ember ;-)
Every object that extends Ember Observable mixin supports the get() method (among others).
When you call this.get(), the this must refer to such an object (Route, Controller, Component, your own class that extends Ember.Object and so on). Calling get() on plain object would cause a failure. Let me show the difference:
const emberObjectInstance = Ember.Object.create({
name: 'Bala'
});
emberObjectInstance.get('name'); // returns 'Bala'
const plainObject = { name: 'Bala'};
plainObject.get('name'); // causes a failure since get() is not a function
However, using Ember.get() successes in both cases:
Ember.get(emberObjectInstance, 'name'); // returns 'Bala'
Ember.get(plainObject, 'name'); // returns 'Bala', too
which can be also written with imports as follows
import { get } from '#ember/object';
get(emberObjectInstance, 'name'); // returns 'Bala'
get(plainObject, 'name'); // returns 'Bala', too
Note: not to forget, calling either of get() makes computed property get computed (in the most common cases, I don't want to dive deep now - lazy computation, volatile extensions etc), but for the sake of understanding the difference, we can work with plain values.
From own experience, I am using Ember.get() everywhere I know a plain object might be the object whose property I need to retrieve. A nice example is setupController() hook into which I may pass plain object from my unit tests to test setupController()'s functionality.
// some route:
setupController(controller, model){
this._super(...arguments);
const name = Ember.get(model, 'name'); // ***
controller.set('isNamePresentOnSetup', Ember.isPresent(name));
}
// in my unit tests I can use plain object:
...
const modelMock = { name: 'Bala' }; // plain object is enough because I use Ember.get instead of model.get() (see ***)?
const controllerMock = Ember.Object.create(); // has to be Ember.Object since I use controller.set() within setupController()
subject.setupController(controllerMock, modelMock);
assert.ok(controllerMock.get('isNamePresentOnSetup'), "property 'isNamePresentOnSetup' set up correctly if model name is present");
...
I could also user Ember.set(controller, 'isNamePresentOnSetup', Ember.isPresent(name)) and then pass plain controller mock into setupController(), too.
I think this is a good start since you are new in Ember and I am sure Ember gurus would have much more to add.
Relevant Ember docs:
https://guides.emberjs.com/v2.9.0/object-model/
https://guides.emberjs.com/v2.9.0/object-model/computed-properties/
https://guides.emberjs.com/v2.9.0/object-model/reopening-classes-and-instances/
UPDATE:
Using get() with chained paths works different than working with POJOs.
For example in objectInstance.get('a.b.c') if b is undefined the return value is undefined. Converting this to objectInstance.a.b.c when b is undefined would instead raise an exception.
There is none. foo.get('bar') is equivalent to Ember.get(foo, 'bar'). However because foo.get is defined on Ember.Object you can only call .get() on Ember Objects. Ember.get() will work on all ember objects. On Ember Objects Ember.get(foo, 'bar') is equivalent to foo.get('bar'), on every other object its equivalent to foo['bar'].
Please note that using Ember.get() or this.get() is not needed anymore for most use cases if running Ember >= 3.1, which was released in April 2018. You could now use native ES5 getters. A quick introduction to this change could be found in release notes for Ember 3.1. It's discussed more in detail in RFC 281.
There is a codemode available that helps you transition to ES5 getters: es5-getter-ember-codemod It could be run as part of ember-cli-update.
Please not that using Ember.get() or this.get() is not deprecated. It's still needed for some edge cases, that are listed in release notes linked above:
In fact there are several cases where you must still use get:
If you are calling get with a chained path. For example in this.get('a.b.c') if b is undefined the return value is undefined. Converting this to this.a.b.c when b is undefined would instead raise an exception.
If your object is using unknownProperty you must continue to use get. Using an ES5 getter on an object with unknownProperty will cause an assertion failure in development.
Ember Data returns promise proxy objects when you read an async relationship and from other API. Ember proxy objects, including promise proxies, still require that you call get to read values.
Please note that there is a special case if using ember-changeset. It provides it's own .get() implementation. Therefore Ember.get(this, 'value') and this.get('value') have different results if this is an ember-changeset. You find more information on that case in documentation of ember-changeset.

Ember link-to gets active class for the wrong transition

This is one of those Ember issues that I'm unable to replicate anywhere but my project. The visual effect of the problem is that the active class on a link-to is one transition behind. I'll click a link and the link that goes to the page I was just on is highlighted with the active class.
I've started digging into the link-to component code to figure out how active is computed. But it is based on _routing.currentState and I'm not sure what that is. The currentState, and other bits of info, are passed to the routing's isActiveForRoute which then calls the routerState's isActiveIntent. And that function calls another isActiveIntent and compares some more things together. All this seems like a large easter egg hunt for something (the root of my problem) that is probably not in Ember's code anyways.
I feel like the following snippet sums up the problem I'm having. The targetRouteName is the route that is being directed to by the link. _routing.currentRouteName seems to be pointing to the route the browser is currently looking at. The fact these match makes me feel like the link should be active, but the active function returns false.
> link.get('targetRouteName')
"parentRoute.pageA.index”
> link.get('_routing.currentRouteName')
"parentRoute.pageA.index”
> link.get('active')
false
For reference this is after finding the link via the Chrome extension and showing all components. I then did link = $E.
For the wrong link (the one that does get the active class) I get:
> link.get('targetRouteName')
"parentRoute.pageB.index"
> link.get('_routing.currentRouteName')
"parentRoute.pageA.index"
> link.get('active')
"active"
Additional Raw Information
The routes I'm dealing with are nested. But it is a pretty standard nesting, very much like the one I have in my ember-twiddle (e.g. page-a, page-b, page-c).
There is a model hook on the parent route and on the indexs of the children routes. But the children routes reference (this.modelFor(...)) the parent.
My template is referencing the .index of those routes. They are standard link-to components. They do not include model information.
I'm running Ember-cli 1.13.8, Ember 2.0.0, and Ember Data 2.0.0-beta.1.
What I have tried so far
Upgrading to 1.13.0
Moving the file structure to pods
Removing the functions in my authentication route which a lot of these routes inherit from.
Upgrading to 2.0.0
Trying to remove/add .index on my routes
Tried replicating on ember-twiddle
Doing ember init with ember-cli to see if my router or application setup was different from the standard layout and it doesn't differ in any significant way.
Adding model information to one of the links, that didn't change anything and since it didn't call the model hooks it messed up the view.
Asked on the slack channel
Please Help
I've had this issue for a couple weeks now and I'm not sure where else to look. I'd love any suggestions on how I can resolve this.
Update
This ended up getting fixed in 2.1.0.
This is common problem when you mess around with willTransition router action. For example,
IMS.ResultDetailsEditRoute = Ember.Route.extend({
actions: {
willTransition: function() {
this.controller.clearForm();
}
}
});
In this code snipped willTransition called controller's method "clearForm()" which no longer exists. For some reason, Ember doesn't throw an error, but it causes the problem that #RyanJM explained.
I have run into something similar when using a component with a nav. Here was my approach:
I added a controller (I know, you should be steering away form these, but I needed to). My controller:
import Ember from 'ember';
const {
Controller,
inject
} = Ember;
export default Controller.extend({
application: inject.controller(),
});
Then, in my template, I could pass application to my component.
{{account/account-icon-nav currentRouteName=application.currentRouteName}}
In my component, I set set up a function to test my current route names:
import Ember from 'ember';
const {
Component,
computed,
get
} = Ember;
const activeParentRoute = function(dependentKey, parentRouteName) {
return computed(dependentKey, {
get() {
return get(this, dependentKey).indexOf(parentRouteName) > -1;
}
});
};
export default Component.extend({
isYourProfile: activeParentRoute('currentRouteName', 'account.your-profile'),
isYourActivity: activeParentRoute('currentRouteName', 'account.your-activity'),
isYourGoals: activeParentRoute('currentRouteName', 'account.your-goals')
});
Then bind the active class yourself:
<div class="icon-nav md-hidden">
{{link-to "" "account.your-profile" classBinding=":profile isYourProfile:active" title="Your Life"}}
{{link-to "" "account.your-activity" classBinding=":activity isYourActivity:active" title="Your Money"}}
{{link-to "" "account.your-goals" classBinding=":goals isYourGoals:active" title="Your Goals"}}
</div>
I know this is a bit different since we are doing it within a component, but I hope it helps. You can bind these classes yourself by passing the application around.

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.

Ember 'currentUser' Model/Controller Setup

Ok, so here's what I'm trying to accomplish (in ember.js):
New model/controller to manage the current user and session information
The model needs to be available everywhere so I can just do something like currentUser.firstname (for instance, in the nav)
After lots and lots of research, it seems that setting up a separate controller/model is the best way to go. I tried doing everything in the application controller, but then I need to implicitly set the user model (somehow?) on that controller, which doesn't seem like a good idea (what if I need to do other things in the application controller?).
So here's what I've tried:
controllers/session.js
init: function() {
// this never gets called unless I call the setCurrentUser
// function from another controller using 'needs' or something
},
setCurrentUser: function() {
// you can ignore the authData.uid variable - it's something used
// by firebase, but not important for this example. Just assume I'm
// requesting and getting a user back.
this.store.find('user').then(function(users) {
this.set('currentUser', users.filterBy('uid', authData.uid)[0])
}
}
models/session.js
DS.Model.extend({
currentUser: DS.belongsTo('user')
});
I don't have a route or view/template associated with this model/controller because it really wouldn't make sense (the user doesn't need to see a page all about him/herself).
So I tried calling setCurrentUser from the application controller by doing something like this.get('setCurrentUser')() (which looks very weird - there's got to be a better way, but I think the answer may be not calling this from the application controller and initializing the controller in a different way?).
I would love some advice on how I can get this working. Sorry that I'm trying to develop and explain the architecture of the app all at the same time, so it is a bit messy - please let me know if anything is unclear.
Thanks for the help!