Difference Between Property and Observer? - ember.js

Why would a computed property in a component not recognize a dependency changed within a view, but does recognize the change as an observer?
I tried two different approaches. The first one, a computed property that depended on the outside variable (projectId), only recognized the change some of the time. The other, an observer, sometimes recognized the change around the same time as the computed property, but also recognized it when the computed property did not. For example, it recognized both the property and observer when it inserted the element, only recognized the observer on a page transition within the same route, and only recognized the property on a page transition to a different route. Why is that? From what I understand, a dependency set on a computed property is similar to an observer, so why would it have trouble?
This is the component using the computed property:
import Ember from "ember";
export default Ember.Component.extend({
isFavorite: function() {
console.log("property");
// returns true or false
}.property("favorites", "projectId")
});
This is the component with an observer:
import Ember from "ember";
export default Ember.Component.extend({
isFavorite: null,
observesFavorite: function() {
console.log("observer");
// sets isFavorite to true or false
}.observes("favorites", "projectId")
});
Why do these two work differently? How would I be able to get the component to recognize the changes to favoriteId all of the time?
Edit
To clarify, the component worked with only a computed property when it was only present on the projects index page:
// app/templates/projects/index.hbs
<table>
{{#each sortedContent}}
<tr>
<td class="text-center">{{favorite-star action="updateFavorites" projectIdBinding="id" favoritesBinding="userFavorites"}}</td>
<td>{{name}}</td>
</tr>
{{/each}}
</table>
However, it stopped working when I added it to the project show page:
// app/templates/project/index.hbs
{{favorite-star action="updateFavorites" projectIdBinding="id" favoritesBinding="userFavorites"}} {{name}}
That was when I tried using the observer instead of the computed property to set the isFavorite property. However, when I tried that, it stopped working on the projects index page. Now, it only works when I have both the observer and the computed property, which I know is the incorrect way of creating this.
The only code I have for the component is in the app/components folder. It doesn't have a template in app/templates/components, and it worked just fine until I had to add it to the show template as well.
Why would this happen? Why would the computed property only work on projects index, and the observer only work on project show?
Edit Again
I've created two JSBins showing the issue. They're messy, but they work for this purpose. This is an example of it using a computed property to update the component and favorites array. It works normally on the index page, but breaks when the user tries to favorite or unfavorite it on the show pages. This is it using an observer, and works okay on the show pages, but not on the index.

Related

Route that observes property on component that links to it

I have a list if items in an items route that uses a component event-item to display each of them. This component has two computed's on it that are setting some classes right now to show the user some info about each item...
classNameBindings: ['winning','closed'],
item: null,
winning: Ember.computed('item.item_high_bid_user_id','userService.user_id',function(){
return this.get('item.item_high_bid_user_id') == this.get('userService.user_id');
}),
closed: Ember.computed('item.item_status',function(){
return this.get('item.item_status') === 2;
})
In the component template each item in the list is wrapped in a link-to that links to the item route, which displays a single item.
In the item template and even route I would like to observe the winning and closed computed's that are on the corresponding component to show or hide some things in the item template (IE. hid the bidding section if an item is closed, etc.)
What would be the proper way to do this?
BTW I'm on Ember 2.2.0 Ember Data 2.2.0 and Ember-cli 1.13.13
If your event-item component is linking to an item route, I assume you're passing the item model into the link-to helper, which means all the attributes needed to compute these properties are still going to be available in the item controller.
// templates/whichever-template-holds-items.hbs
{{#each items as |item|}}
{{event-item model=item}}
{{/each}}
// templates/components/event-item.hbs
<div>
{{link-to 'item' model}} // pass model to item route
</div>
// controllers/item.js
import Ember from 'ember';
export default Ember.Controller.extend({
// include userService
winning: Ember.computed.equal('model.item_high_bid_user_id','userService.user_id'),
closed: Ember.computed.equal('model.item_status', 2)
});
// templates/item.hbs
{{#if winning}}
// show winning stuff
{{/if}}
{{#if closed}}
// show closed stuff
{{/if}}
Also, I noticed you had a mix of both == and === for your conditionals in the code you posted. Most of the time you will want to use ===, see this post.
Almost forgot - Ember.computed.equal
UPDATE (in response to your comment below)
There are a couple ways to alert a controller that a value in a component has changed, but neither are really conducive in your current situation.
The first way (which is ok to do) would be to follow DDAU (data down, actions up) and send an action from your component up to your controller, but this only works if the component is inside the controller's view, which is not the case for what you're doing.
The second way (which is not really ideal IMO) would be to use a service in sort of a pub/sub fashion which would allow distant component/controllers to talk to each other (you can read more about this method here). You'll probably get mixed responses as far as doing things this way since it can be kind of disruptive to the data flow of your app. But sometimes you're choices are limited.
With all this said, I would probably stick with re-computing in the controller rather than trying to send data across your app from one controller to another. In the end it will still be less code and less work for the framework. Hope this was helpful.

Sorting Ember ArrayController when iterating the model

I have an ArrayController (documents) which displays a list of ObjectsControllers (document) for its content.
My ArrayController template (Documents):
{{#each document in model}}
{{render "document" document}}
{{/each}}
The issue I am facing is that the "sortProperties" and "sortAscending" properties on the ArrayController are no longer having any effect. I assume this is because I am looping "model". If I loop "each document in controller", the document ObjectControllers dont seem to get the model assigned to them as a call to .model then throws an undefined error. Should I be looping "controller" or "model"? If the answer is model, how can I sort it and if the answer is controller, how can I get the model set on each controller?
Rather than modifying the base model, the sorted collection is exposed as arrangedContent.
To explain why it's arrangedContent rather than arrangedModel, in Ember's ControllerMixin content is defined as an alias for model, but it used to be the other way around.
You may have to set the itemController property on your ArrayController (Documents).
See here: http://emberjs.com/api/classes/Ember.ArrayController.html
Edit: Also, you had it right the first time with {{#each document in controller}}. Doing it straight from the model will ignore configuration in your controller, which includes sorting rules you may have.

Emberjs - how do I set a property on an itemController from a route?

I have an ArrayController that uses associated itemControllers to track a checked property on the set of models (i.e. whether or not their checkbox is checked).
This part is currently working fine. When I come back to the route, I need to use some information off of the user model to "remember" which were checked.
How can I set the checked property on the specific itemController's? I can get references to the individual models that need "checking", but model.set('checked', true) doesn't seem to get picked up by the itemController in that scenario.
Any help would be appreciated!
From your array controller, iterate this and that will give you each individual itemController. Here's the basic concept
App.FooController = Em.ArrayController.extend({
someFunc: function(){
this.forEach(function(item){
item.set('checked', item.get('model.wasChecked'))
});
}
});

Difference Between controller and controller.content

I have an ArrayController and was using {{#each item in controller}} to iterate over the items in the controller. This was working fine while using the same controller however after switching to another route I ran into some weird behavior which stopped the items from being rerendered. Switching to {{#each item in controller.content}} solved this problem. However I am not sure how this even happened.
What's the difference between controller and controller.content in an each expression (or any where else).
What's the difference between controller and controller.content in an each expression (or any where else).
Basically there is no difference, for example when using an ArrayController which extends from ArrayProxy, then inside the controller this.pushObject(obj) will behave the same as doing this.get('content').pushObject(obj). See here for reference.
But IMO you are better of using model everywhere e.g. {{#each item in model}}.
Check also this answer which I guess will be useful: Ember iterations: when to use #each User, #each user in controller, #each user in model, etc
Hope it helps.

Ember Getting property from Controller in Model

I have a computed property on a model and in order to compute that I need a property from a controller (not the one that is controlling the model).
I know there is needs: but this is just on
a controller level.
How could I get a property in Ember from a controller other than the one that is managing the model?
I'm trying to do some formatting like the person that [asked this question][1] but I didn't succeed what has been suggested there.
So I try to do the formatting on the model with a computed property, but to calculate that property I need another property from a controller.
Any help is greatly appreciated! Thanks!
Note: I'm using EmberData to manage the model.
Edit:
In order to clarify what I'm trying to do I have set up an example that shows the problem
in a general way: The example application lets you input numbers, store them,
and show them in a list. You can also input a "conversion factor" which doesn't change the model data itself but the presentation on the template. Say, you input the number 2, 2 gets saved on the model but when it is shown in the list it gets "formatted" with the conversion factor you entered previously and the calculated value is shown in the template. The problem is that the value with which I want to format is stored on a different controller. Here's what I have tried so far:
#1 Approach:
Computed Property on the ArrayController - using needs: in the controller to traverse and get the value
-->jsfiddle
Problems I have encountered:
The ArrayController seems to break and the template renders as if there are no stored records at all (Note: the example uses local storage, so create some records and uncomment the computed property on the ArrayController and you'll see it works originally as expected and shows the records you entered).
#2 Approach:
Computed Property on model itself
-->jsfiddle
Problems I have encountered:
I have no idea how I can get a property from a controller while beeing inside the model
#3 Approach:
Handlebars Helper and needs: on the controller
1) Define a computed property on the controller (that handles the model) to get the value in question from the other controller
2) create a handlebars helper and pass in the value from the model and the value from the controller and return the calculated value
-->jsfiddle (You can find the link for the 3rd jsfiddle in the comments since I don't have enough reputation points yet).
Problems I have encountered:
Instead of displaying the formatted number I get "NaN" on every value in the rendered template.
If anyone has an idea how to solve this or can point me into the right direction would be great. Your help is really appreciated! Thanks for your time!
Accessing any controller from a model is really going against the grain of Ember's architecture. Most formatting problems are best solved with a Handlebars helper, but if you need to combine data from the controller and model in a really serious way, then you probably want a computed property on the controller.
Can you give a concrete example of what you're trying to do? That will make it a lot easier to suggest the right solution.
I got it working: I've used the #3 Approach (outlined in my question).
I'm using a computed property to proxy the value from the other controller to the controller that is bound to the template where I want to display the formatted value
I created a handlebars helper called converted that will do the formatting. The handlebars helper accepts two parameters: the value that I proxy from the controller (which in turn comes from a different controller) and the value from the model.
The thing that didn't work previously was that in the template when using the helper I would get "NaN" on all items instead of the formatted output.
What solved the problem was that instead of lopping through the model in the template with:
{{#each controller}}
<tr>
<td>{{converted amount conversionFactor}}</td>
</tr>
{{else}}
<tr>
<td>No amounts here yet</td>
</tr>
{{/each}}
I changed it to this:
{{#each item in controller}}
<tr>
<td>{{converted item.amount conversionFactor}}</td>
</tr>
{{else}}
<tr>
<td>No amounts here yet</td>
</tr>
{{/each}}
and it works perfectly!
Here's the working jsfiddle