Data down in components: any ways to implicitly access it?


#1

I’m building form -> field nested components. What I want to do is to pass model to form and field names to fields. Like this (in Emblem):

ac-form model=content
  ac-input field='first_name'

ac-form component’s template is = yield model which supposed to inject model in the scope of block. And it does, but only accessible from whithin nested component when stated explicitly, like:

ac-form model=content
  ac-input model=model field='first_name'

Is that how it supposed to be and no way to access to the properties of parent component?


#2

Right or wrong I just push down the model I need to “access” or “change”. If that requires the entire model I’ll push that down and my child/nested component can access that directly. This might not scale on bigger forms/component trees but for simple forms w/ a few sub components it’s done well for my team.


#3

How exactly do you do that? Are you talking about access to something provided by route/controller or by parent component?

My idea was to do that to enforce isolation. So that there could be two forms on one page with different sourcing objects.


#4

Ah, you must be further down the journey than I :smile:

Honestly what I suggest violates the isolation/data shouldn’t be mutated ideas (readonly computed might/or might not allow this even if you are doing that yet).

If you truly want the isolation you “could” do a readonly CP in the child/nested template. Then fire an action like so (on the change event) **read more about this pattern from Ben (great blog post btw)

{{my-time-input value=(readonly job.startTime) on-time-change=(action (mut job.startTime))}}

Instead of the mut like you see above you could instead throw the action up to the parent component to “change” that model property. I’m still unsure if this is required/what the ember core team wants everyone doing. I ask because I’m not entirely sold that we should throw out all 2WDB like you see in the post from Ben/hear others talk about.

I’ve only had pain w/ 2WDB when I’m unsure why something changed suddenly. And in my 3+ years writing ember this only happens when I use an observer (heck it just happened to me again 2 weeks ago). I’m trying to balance “don’t reinvent 2WDB with actions/helpers” because in my view it still has a valid place. If you find the data flow is hard to reason about/or you literally can’t find out why something changed it’s time to re-think. I just haven’t had that happen yet with components/ models getting passed around.

All that to say … I’m in the minority these days (not using ember-data for example) but it’s worth asking “what caused us harm” in the 2WDB paradigm and is it worth “re-inventing 2WDB in a more verbose way for every scenario” ?


#5

Interesting post and whole point, though I don’t see how it could help with input components. As in the end they’d translate set action anyway. Also I use plain Ember.Object populated with the data excerpt from the actual model. So It wouldn’t harm anything if it gets mutated on the side.

I was talking more about form <-> model isolation from others. And I was going to setup structure where each field of a form would know which object’s part it’s connected with.


#6

So this is really my hang up with DDAU. What’s the advantage of having a readonly value then an action and mut helper? Isn’t this just the same with less confusion:

{{my-time-input value=job.startTime}}

I mean, I certainly understand the value of having the my-time-input have an action for change, if you wanted to save a model or trigger some additional property change. But if you’re just modifying a value, why not use a two way binding?


#7

Having a function to modify the data gives you more control over when it gets done. Two-way binding could cause a cascade of updates on computed properties and observers every time the data changes (which could be every button press). Remember components now also rerender whenever their properties change.

Having to use a method guides you into not falling for this trap, and allows you to do things like using a timeout to make sure that the updates only happen after the user hasn’t modified anything for 400 ms, etc…


#8

I think changes to editable model should be made immediately, other thing is the action that’s happening afterwards. And there Ember run-loop comes to rescue. I use debounce method to wrap action, it helps to ensure that action happened only once in the defined time interval. (sort of typeahead, it will not run until you stop typing)


#9

I asked @stefan if he could drop in and comment on this issue to help provide a little guidance to the community :slight_smile:


#10

Yeah, thanks. The whole thing of nested components and passing data/events needs to be explained or modified to be more transparent. The way how it worked with nested views was perfectly fine. I mean, that we could access anything from the scope of parent view.

And by the way, with the current thing I had an issue with passing up the actions too, and the only solution I’ve found was triggering an event on parent component by using this.get('parentView'), but I thought views are gone. Or they just became internal concept?


#11

@Ivan_Youroff There is now a much nicer answer to your original question. The contextual components feature is available on canary, and lets you implement something like:

{{#my-form model=model as |form|}}
  {{form.input field="firstName"}}
{{/my-form}}

very elegantly.


#12

Oh, looks very rails-like :smile:

Where can I see the details on that new feature? Is form just a context of my-form component and input is another component evaluated within a context?


#13

The implementation of my-form could be as simple as:

{{yield (hash input=(component "my-input" model=model)) }}

This is looking up another component named “my-input” and pre-setting the model attribute. hash just creates a POJO with the given properties, which gets yielded and ends up as the value of form in my example above. We could also pass a submit component like:

{{yield (hash input=(component "my-input" model=model) submit=(component "my-submit" form=this)) }}

And then use it like:

{{#my-form model=model as |form|}}
  {{form.input field="firstName"}}
  {{#form.submit}}Let's Go!{{/form.submit}}
{{/my-form}}

#14

The feature is behind a flag on canary, I don’t know about API docs yet, but there is a pretty detailed RFC for it.


#15

Is there way to programmatically define that hash?


#16

Yes, in the sense that you can implement your own helper where I used hash, and you can pass it whatever component factories you want, and also pass it a reference to this (the component doing the yielding).

No, in the sense that there is not yet a public API for doing the component lookup and currying within your component’s JS file. I think we should probably create one, but need to figure out the details.