I am confused and in a fix with regards to controllers and components detecting changes in models. According to the Ember Official Documentation --
"By default, the value returned from your model hook will be assigned to the model property of the associated controller. For example, if your App.PostsRoute returns an object from its model hook, that object will be set as the model property of the App.PostsController."
Therefore, shouldn't the controller update when the model changes in the route or, asynchronously by way of an external function?
App.IndexRoute = Ember.Route.extend({
model: function(){
App.set('localStore', this.get('store'));
App.localStore.createRecord('stats', {'name': 'cde'});
return this.store.find("stats");
}
});
App.IndexController = Ember.Controller.extend({
modelObs: function() {
// Never triggered!
console.log("CONTROLLER: model updated!");
}.property('model')
});
// Component with the controller's model property passed to it as localModel
// in the template
App.NewCompComponent = Ember.Component.extend({
localModel: null,
modelObs: function() {
console.log("COMPONENT: Model updated!");
}.property('localModel')
Here's a Jsbin that illustrates the problem - http://jsbin.com/tavis/3/edit
Is there anything I might be doing wrong? How to get a component detect a change in a passed model from the controller, and similarly how to get the controller detect a change in the route's model? Perhaps I'm missing a thing or two -- pointers are appreciated! Thanks
Computed properties aren't evaluated unless they are used. They are lazily evaluated.
Using either of those properties will cause them to be evaluated and your logs will occur.
http://jsbin.com/suheroya/1/edit
Additionally it's important to know that the model itself isn't changing, so that computed property won't be called over and over. Properties on the model are changing, and if you want the computed property to be called over and over you'll need to watch those properties.
Related
I am using Ember 2.3.
Like the title says I am trying to pass a computed property from my controller to my component. I don't know what I am doing incorrectly but it is not working for me. This is what I am doing:
In my controller:
import Ember from 'ember';
export default Ember.Controller.extend({
someProperty: 'someValue',
myComputedProperty: Ember.computed('someProperty', function(){
return {property: this.get('someProperty')};
})
});
My component:
export default Ember.Component.extend({
myProperty: {}
});
In my template:
{{my-component myProperty=myComputedProperty}}
What I am seeing is that my myProperty in my-component is always set to {}. It never gets the value I hand in the template. Note I've also tried just defining a property as a string literal on my controller and handing that in and it is not recognized either. Also of note is that I did originally try defining the computed property in my route but I found the only way I could access it was if it was defined in the model hook itself, like:
model(params) {
return {
myComputedProperty: Ember.computed()...........
};
}
But this wasn't working for me because I needed values from the controller that were not available when the model hook was called.
I don't know what I am doing wrong here. I'm sure it's something simple, but I am running out of ideas. Could anyone tell me what I am doing wrong here? Is passing a computed property from a controller to a component bad practice in any way? Any advice would be appreciated. Thanks much!
Passing computed properties into a component from a controller is fine.
The code you have should be working. The one question/change is: Do you need to be returning a nested object in you controller's computed property? {property: this.get('someProperty')}`
If so, then you need to call .property on it in your component in order to access the controller's someProperty value.
my-component/template.hbs
{{myProperty.property}}
Here's an ember-twiddle demonstrating the code working.
I have the following code where I am trying to set the model for ApplicationRoute but it doesn't seem to work. I have a few doubts regarding the Ember code. Firstly, Can I set a model for application route? Secondly, if the model for the route has fields named count and fileName, do I need to declare these fields in the controller also. It looks like if I do so, the value in the controller takes precedence over the model value. Also can I do something like this.set('total',5) in the setupController even though total isn't defined anywhere.
App.ApplicationRoute=Ember.Route.extend({
model:function(){
console.log('model called');
return {count:3,fileName:'Doc1'};
},
setupController:function(){
console.log(this.get('model').fileName);
this.set('count',this.get('model.count')); //Do I manually need to do this?
this.set('fileName',this.get('model.fileName')); //Do I manually need to do this?
}
});
App.ApplicationController=Ember.Controller.extend({
count:0,//Is this necessary?? Can I directly set the property with declaring it like this
fileName:''
});
You can do:
App.ApplicationController=Ember.Controller.extend({
count: function(){
return this.get('model').get('count');
}.property('model.count')
});
So anytime model.count changes, the propery would get updated automatically.
And yep, you can set the model directly on the route. When you do this.set('total', 5) in the controller, you only set that property on the controller and not the model. In order to update the model, you would need to do:
var model = this.get('model');
model.set('total', 5);
Lastly, your setupController code isn't correct. Here is the sample method found on the Ember docs (located here):
App.SongRoute = Ember.Route.extend({
setupController: function(controller, song) {
controller.set('model', song);
}
});
I am getting a little deeper into my first functional app and need to better understand what it going on in my controller.
Here I have a controller that handles the action when a user clicks on an 'Option'. Looking at the this object raises a few questions:
What exactly is this? I would expect it to be an instance of my Option model, but it is missing some properties (like "identity: 'model: Option'").
If this is an instance of my Option model, why is the 'model' property undefined? Why doesn't it just know that?
What is this.content? It looks like some stuff is inside content (id and isSuppressed) and some is not (this.isSelected) - why is that?
Disclaimer: Though there aren't any presenting problems so far, there certainly could be errors in my ember app architecture.
Screenshot debugging controller:
Option Model & Controller
App.Option = Ember.Object.extend({
identity: 'model: Option',
id: '',
cost: '',
isSelected: false,
isSuppressed: false
});
App.OptionController = Ember.Controller.extend({
actions: {
toggleOption: function() {
this.set('isSelected', !this.get('isSelected'));
var id = this.get('content.id');
this.send('deselect', this.get('content.id'));
}
}
});
App.OptionsController = Ember.ArrayController.extend({
actions: {
deselect: function(exception) {
var opts = this.rejectBy('id', exception)
opts.setEach('isSuppressed', true);
}
}
});
It depends where this is, if your in the controller it's the controller. If your controller is an ObjectController/ArrayController it will proxy get/set calls down to the underlying model. content/model are the same thing in the context of the controller.
The properties rarely live directly on the instance, usually they are hidden to discourage accessing the properties without using the getters/setters.
In your code above there is a good chance your OptionController should be extending ObjectController. Unless the controller isn't backed by a model. If you use Ember.Controller.extend then it won't proxy getters/setters down to the model, it will store, retrieve properties from the controller itself.
Image you have an ArrayController which displays a list of items, each item using its own ObjectController. Now you have an action handler on the ArrayController which should change some property of all items. This property should not be persistend, it's only for the view state. An example would be isSelected.
Looking at the Todos Example (http://todomvc.com/architecture-examples/emberjs/) it seems very similar to the isCompleted property. But the main difference is that this property is meant to be stored in the db and so the model is the right place here.
In Ember.js, controllers allow you to decorate your models with display logic. In general, your models will have properties that are saved to the server, while controllers will have properties that your app does not need to save to the server.
Is there any way to loop through all item controllers of any ArrayController and update the controller's property? Would this be the correct approach?
It sounds like the correct approach to me. Controllers as you quote are meant for holding properties that doesn't need to be persisted. These properties held application state and/or are used in display logic.
The main difference with the Todos example is that you would modify the property in the itemController and not in the model. (Having in mind you don't want the property to be stored on the server).
What I'd do would go something like this:
App.MyArrayController = Ember.ArrayController.extend({
itemController: 'item',
actions: {
toggleSelection: function() {
this.forEach(function(item, index) {
item.toggleProperty('isSelected');
});
}
}
});
In case you'd want the action to change the model, you'd have to access the content propery of each itemControlller.
Hope it helps!
Is there any reason why setupController would not get called when using {{linkTo}}? I have two instances in my app where linkTo is being used, and in the second case. It doesn't work. The only difference that I can see is that in the first case linkTo is being used in a loop, and in the second it's not. Below is relevant code for the non-working one:
App.Router.map(function() {
this.resource("search", { path: "/search/:args" });
});
App.SearchCriteria = Ember.Object.extend({ });
App.SearchRoute = Ember.Route.extend({
serialize: function(model, params) {
// .. some code that converts model to a string called args
return {'args': args}
},
model: function(params) {
// convert args, which is query string-formatted, to an object
// and then make a App.SearchCriteria object out of it.
return App.SearchCriteria.create($.deparam(params.args));
},
setupController: function(controller, model) {
controller.set("searchCriteria", model);
}
});
In the search template:
{{view Ember.Checkbox checkedBinding="searchCriteria.music"}} Music
{{#linkTo search searchCriteria}}Search{{/linkTo}}
The last thing I see in the logs is:
Transitioned into 'search'
Normally, I'd see the setupController being called at some point, but it's not happening or some reason. I even tried using the {{action}} method to call a handler and then use transtionTo, but that had the same results.
UPDATE 1: Adding more details
The only difference between the working and non-working cases is that in the working case, the {{linkTo}} is being called from the same template as that of the controller and router (i.e., the linkTo is in the search template and it's invoking the SearchRoute). In the working case, the linkTo is being called on the SearchRoute but from a different template belonging to a different router).
After some Chrome debugging of Ember code, I found out that the router isn't being called is because partitioned.entered is empty. In the working case, it is non-empty.
var aborted = false;
eachHandler(partition.entered, function(handler, context) {
if (aborted) { return; }
if (handler.enter) { handler.enter(); }
setContext(handler, context);
if (handler.setup) {
if (false === handler.setup(context)) {
aborted = true;
}
}
});
UPDATE 2: Root issue found - bug?
I think I understand the root cause of why the handler isn't being triggered, and I think it's because the partitionHandlers(oldHandlers, newHandlers) method doesn't think that the model has changed, thus doesn't fire the handler.
To be specific, this is the relevant part of the view:
{{view Ember.Checkbox checkedBinding="searchCriteria.music"}} Music
{{#linkTo search searchCriteria}}Search{{/linkTo}}
Although the user checks off the checkbox (thus changing the state of searchCriteria), Ember doesn't think that searchCriteria is any different, thus doesn't do anything.
Is this a bug?
I'm not sure what your problem is, but this may help.
setupController is called every time the route is entered. But model hook may not be called every time.
See Ember guide: http://emberjs.com/guides/routing/specifying-a-routes-model/
Note: A route with a dynamic segment will only have its model hook called when it is entered via the URL. If the route is entered through a transition (e.g. when using the link-to Handlebars helper), then a model context is already provided and the hook is not executed. Routes without dynamic segments will always execute the model hook.
Genrally speaking, if you click the link generated by link-to to enter the route, Ember will not call model hook for that route. Instead it passes the model (link-to parameter) to that route.
The philosophy here is since the client already has the model context, Ember think there is no need to get it again from server (that's model hook's job).