Looking specifically for answers geared to Ember 2.0
I think it is pretty clear that the standard way to go is that the route sets up data and passes it into the component for rendering. But what about the case where one component is setting up another? For example, I have an application with a survey. A section
hasMany questions
. An answer
belongsTo a question
and an instance
of the survey.
Route is surveys/1/instances/1/sections/1
and in the route I have
setupController: function(controller, model) {
controller.set('attrs.section', model);
controller.set('attrs.instance', this.modelFor('instance')); //
}
In the template (soon to be component), I want to
{{#each question in attrs.section.questions}}
{{app-question question=question answer=answer answerChanged='answerChanged'}}
{{/each}}
But I want to run some code to setup answer
to pass in values to the app-question
component. Namely, I want to look in the store and see if there is already an answer
linked to that question
and instance
, and create it if not, so that it can be bound to an input in the app-question
component.
If I were rendering this from a route, I would do it in the model
hook, so what is the equivalent way to set up some data when rendering a nested component? didInsertElement
? Pass in the question
and the instance
and do it in the .on('init')
in the `{{app-question}} component?
1 Like
I’ll add that I know this could be done in .on('init')
inside the child component, but that bothers me because the way I understand it is that components are supposed to be supplied data, not set up their own. Having a component always be supplied data allows it to be more re-usable I think
I hope this question is clear. A more basic way to put it is, when we are rendering a collection of nested models in an {{each}}
helper, what is the preferred way to set up the context?
When setting up the context in a route, we rely on the model
and setupController
hook. Inside of an {{each}}
helper, we typically have the model passed down from above (i.e. {{#each comment in posts.comment}}{{app-comment comment=comment}}{{/each}}
But what is the recommended equivalent to setupController
to set up the component with some information that is specific to both the surrounding context and the specific comment
inside of the each?
I’ve been experimenting with the .on('init')
, and sending actions back up to the route, but it is trickier to manage and ensure things are properly loaded this way, and I’m thinking there must be a more straightforward declarative way to do this.
Hey @leejt489, since you’re focusing on future Ember here, you can use a new feature called Block Parameters. Here’s a JSBin that illustrates how to send data out from a parent component and pass it into a child: http://emberjs.jsbin.com/baferezinu/1/
Is that along the lines of what you’re looking for?
@heycarsten Thanks for your reply. I understand how to pass the data down; that’s not a problem. It’s more that what if I want to execute some logic to set a property (e.g. commentTitle
) on the child component that has to be computed from information in both the child and the parent. One solution is to pass in the parent and compute commentTitle
in the child. But then say I want to use the child component in a different context with a different type of parent and in this context the commentTitle
property is computed in a different way. So now it doesn’t make sense to store the logic to set commentTitle
on the child.
Right now I am listening to .on('init')
in the child and sending an action back up to the parent with a reference to the child component as an argument and using that to set up these properties and it works, but what I would really want is something where I could define computed properties that are bound within the each, something like:
eachComputedProperty: function(item) { //item is passed in
var prop = item+1; //execute some logic here
return prop;
}.property('childItems.@each')
and then
{{#each childItems as |item|}}
{{eachComputedProperty}}
{{/each}}
…now that I think about it this way, it could probabaly be done with a custom helper
@leejt489 if the way the commentTitle
is computed depends on the parent component that wraps it, you could always override the _yield
function to pass a pointer to either a). the parent component (achieving the same effect as passing the component itself as an attribute of the child component without having to actually pass it manually each time) or b). a function that can be used to set the commentTitle
.
I did just this today. I looked through the Ember 1.10 source and found that the {{yield}}
makes a call to the component’s private _yield
function:
_yield: function(context, options, morph, blockArguments) {
var view = options.data.view;
var parentView = this._parentView;
var template = get(this, 'template');
if (template) {
Ember.assert("A Component must have a parent view in order to yield.", parentView);
view.appendChild(Ember.View, {
isVirtual: true,
tagName: '',
template: template,
_blockArguments: blockArguments,
_contextView: parentView,
_morph: morph,
context: get(parentView, 'context'),
controller: get(parentView, 'controller'),
component: view //My custom property that gets set on each child view
});
}
}
My child component can access the property in the template via {{_view._parentView.component}}
or in the component via this._parentView.component
. If you follow this approach, make sure you place test coverage around the yield code because I have seen this helper change. You can also add an Ember.assert
to make sure the component property is available to the child component if its required, or have a default mechanism for setting the commentTitle
if it’s not present