Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Angle Bracket Invocations For Built-in Components #459

Merged
merged 7 commits into from
Mar 15, 2019
Merged

Conversation

chancancode
Copy link
Member

@chancancode chancancode commented Mar 6, 2019

Copy link
Member

@rwjblue rwjblue left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏 I like it, thank you for putting the plan together @chancancode!

text/0459-angle-bracket-built-in-components.md Outdated Show resolved Hide resolved
text/0459-angle-bracket-built-in-components.md Outdated Show resolved Hide resolved
rwjblue and others added 2 commits March 6, 2019 12:46
Co-Authored-By: chancancode <godfreykfc@gmail.com>
Co-Authored-By: chancancode <godfreykfc@gmail.com>

...becomes...

<Input @type="checkbox" @name="email-opt-in" @checked={{this.model.emailPreference}} />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we were building these components from first principals, would we choose this API? Might this be an opportunity to build something better, removing type and the overloaded input API and instead provide dedicated components such as <TextInput /> and <Checkbox />? (with possibly better names)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there are advantages sticking close to underlying HTML element that it is trying to bridge (even though we don't fully support all the input types too). Also, this RFC just tries to fix a narrow problem (provide a 1:1 transition path from the curly invocations) without opening the can of worms of "hey.. what if we also just fix this one thing......", they can be explored in separate RFCs IMO, so long as they are codemod-able I don't think there is a lot of extra cost to do them later.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 thanks, agreed. Having the opportunity to codemod our way to some possible future API means that this is a nice incremental improvement

Copy link
Member

@rwjblue rwjblue Mar 6, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we were building these components from first principals, would we choose this API?

No, but it is not this RFCs intent to "fix the design of the underlying components", that should be done separately.

tldr; what Godfrey said 😸

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the same point I came here to make. In particular, given ...attributes and the semantics of type vs. @type on a regular <input>, it seems like it might be worth deprecating {{input}} (along the lines proposed here) and using the internal mechanics which create the correct input type to offer good suggestions. "You attempted to create a checkbox with {{input}}; we recommend <CheckBox />..." etc.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think both could work as @rwjblue points out this RFC is to bridge the gap with the commonly used components today. For instance if a Ember dev reads the guide materials on curly vs angle bracket and sees {{input type="number"}} being used it's a much smoother path to change it to <Input @type="number"> vs having to know <TextInput type="number" /> or all of the different variations.

A more angle bracket 1st set of input components would be great! We could even use codemods or build time AST transforms to rewrite to these new components in the future. But, for now let's make a way to use the built-in components we have.

@chancancode chancancode added T-templates T-framework RFCs that impact the ember.js library T-components Octane labels Mar 6, 2019
@btecu
Copy link

btecu commented Mar 6, 2019

Wasn't recommended to pass an id to link-to as opposed to the model? I remember a lot of discussions around this. If that's the case, model might not be the best name for that argument.

@ddoria921
Copy link

ddoria921 commented Mar 6, 2019

By migrating to the angle bracket invocation does that break the two-way binding that the {{input}} component provides today?

@chancancode
Copy link
Member Author

By migrating to the angle bracket invocation does that break the two-way binding that the {{input}} component provides today?

@ddoria921 it does not, angle bracket invocations does not behave any differently there

@mydea
Copy link
Contributor

mydea commented Mar 7, 2019

I would suggest to re-think the proposed deprecations on {{link-to}}. I believe this would result in a lot of churn, and you'll need to "fix" it anyhow when you migrate to <LinkTo>. Basically, if I have the following code:

{{#link-to 'help'}}Help{{/link-to}}

And I see a deprecation along the line Do not use positional arguments for {{link-to}}, I would just refactor the code to

{{#link-to route='help'}}Help{{/link-to}}

Then, I'll still have to refactor that again later to angle bracket invocation.

I believe this would introduce a lot of churn, as we use {{link-to}} very often, and of course with that syntax (as it used to be the only syntax).

Instead, I think it is better to eventually deprecate the whole curly brace invocation form, and directly suggest the use of a codemod.

@gossi
Copy link

gossi commented Mar 7, 2019

One of the best things we were doing, was to avoid {{input}} component/helper lately, since it had two-way binding and thus was doing something unpredictable at some point. So we wrote our own <Input> component, which explictely is one-way-binding. It could be, companies having their <Input> components following their styleguide.

My concerns are, that until we have imports to specify which <Input> (as example here) components to take, it could break existing code?

Here is an OSS example of mine: https://github.com/gossi/spomoda/tree/master/client/src/ui/components/input My hypothesis is, there is a lot of private implementations as well, that said, the number of those is unknown.

Are my concerns real? Like would an <Input> in an app receive priority when being resolved? Happy to hear what would happen. Also might be good to address this in the RFC, too - thanks.

@rwjblue
Copy link
Member

rwjblue commented Mar 7, 2019

My concerns are, that until we have imports to specify which (as example here) components to take, it could break existing code?

The input component in your app would “win”, and thus this change should not cause any issues.

@rwjblue
Copy link
Member

rwjblue commented Mar 7, 2019

Instead, I think it is better to eventually deprecate the whole curly brace invocation form, and directly suggest the use of a codemod.

Hmm, yes I see what you mean. I think this logic does make sense. What do you think @chancancode?

@crhayes
Copy link

crhayes commented Mar 7, 2019

By migrating to the angle bracket invocation does that break the two-way binding that the {{input}} component provides today?

@ddoria921 it does not, angle bracket invocations does not behave any differently there

@chancancode So to clarify, angle bracket components will only have two-way binding in these few scenarios? If that is the case, are we worried about inconsistency of the API?

@ddoria921
Copy link

By migrating to the angle bracket invocation does that break the two-way binding that the {{input}} component provides today?

@ddoria921 it does not, angle bracket invocations does not behave any differently there

@chancancode So to clarify, angle bracket components will only have two-way binding in these few scenarios? If that is the case, are we worried about inconsistency of the API?

@crhayes I was confused about this too, but I think I understand the way it works now. All ember components have two-way binding and all glimmer components will have one-way binding. I think it starts getting confusing because both can be used with angle bracket invocation. Sounds like these built-in components will be ember components to maintain backwards compatibility.

And I can be totally wrong about this so @chancancode can chime in and correct me if I am 😅

@chancancode
Copy link
Member Author

@ddoria921 @crhayes That's pretty much correct. The rendering engine exposes the low-level capability to do "two way bindings", Ember.Component happens to take advantage of it. @glimmer/component decided that it is not a good idea from a programming model's perspective, so doesn't use the feature. The built-in component may or may not be built on Ember.Component, but since it's built-in, and the rendering engine has the capability, we will make it work somehow 👋.

However, all of these are API-design decisions/implementation details of the components being invoked, and not related to how you invoke them. The curly/angle bracket invocation style is purely a syntatical difference on the caller side, and the component doesn't care or know about which one you used (other inferring it from the fact that you can't pass positional arguments with angle bracket, so if you did, you must have used curlies).

@chancancode
Copy link
Member Author

@gossi even though it may work, I would strongly suggest that you (and other addons) rename them into something else to avoid confusion and other tooling issues (codemods, template lints, etc). Typically, <Input> and {{input}} (more generally, <Foo> and {{foo}}, <FooBar> and {{foo-bar}}) are just two different ways of invoking the same component. This is only not true in your app right now because we took some implementation short cuts implementing {{input}}. If we had implemented it like as a "real component" from day one, there would have been no way to override one but not the other.

This RFC is really just fixing that inconsistency, and I think it's quite possible that after things are re-implemented "correctly", your component will also override {{input}} globally, which may break your app or other addons in unexpected ways.

@chancancode
Copy link
Member Author

chancancode commented Mar 7, 2019

@mydea @rwjblue I do think that running the codemod and using angle brackets is the correct way to resolve those deprecations.

The curly invocation is only "kept around" in the sense that all components can be invoked in either style (curlies or angle bracket). It would be inconsistent for the built-in components to behave differently on that front. Just because you can doesn't mean you should though, and I think with Octane, the idea is to push the whole community to use angle bracket exclusively for all components. The only exception would be for control-flow-like "components", such as liquid-if or await, which should use curlies/positional arguments to better match things like {{#if}} and {{#each}}. However, these stylistic concerns are best addressed in template linters, rather than making them hard errors in the rendering layer.

I agree the deprecation messages should just suggest the angle bracket invocation style. I do think {{#link-to route='help'}}Help{{/link-to}} should work without deprecation, but I would be happy to see it trigger a lint error, and I agree we should make that the deprecation message does not explicitly recommend this style as a fix.

In terms of a rollout plan, ideally I think everyone should just run the codemod as soon as they upgrade, so none of these should matter. I don't know if that's what people do in practice. If it causes problem, I suppose we can rollout the deprecation a few releases later, after people had the chance to run the codemod. I'm not sure if this is good or necessary though. I would prefer that people can run the codemod on the whole app, and get immediate negative feedback when they used the old style in new code out of habit.

Maybe this wouldn't work because old addons will cause too much deprecation noise? (Do addons really use links though?)

@chancancode
Copy link
Member Author

chancancode commented Mar 7, 2019

Would this satisfy your concern if the deprecation messages looks something
like this?

{{#link-to 'help'}}Help{{/link-to}}
           ~~~~~~

Passing positional arguments to the `<LinkTo>` component is deprecated.
Use the `route` named argument instead:

<LinkTo @route="help">Help</LinkTo>

See RFC #459 for details.

Tip: use XYZ codemod to automatically migrate to the new API.
{{#link-to 'post' post}}Read {{post.title}}...{{/link-to}}
           ~~~~~~~~~~~

Passing positional arguments to the `<LinkTo>` component is deprecated.
Use the `route` and `model` named argument instead:

<LinkTo @route="post" @model={{post}}>Read {{post.title}}...</LinkTo>

See RFC #459 for details.

Tip: use XYZ codemod to automatically migrate to the new API.
{{#link-to 'post.comment' post comment}}
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~
  Reply to {{comment.author.name}}
{{/link-to}}

Passing positional arguments to the `<LinkTo>` component is deprecated.
Use the `route` and `models` named argument instead:

<LinkTo @route="post" @models={{array post comment}}>
  Reply to {{comment.author.name}}
</LinkTo>

See RFC #459 for details.

Tip: use XYZ codemod to automatically migrate to the new API.
{{link-to 'Help' 'help'}}
          ~~~~~~~~~~~~~

Using `<LinkTo>` without a block is deprecated. Use the block form instead:

<LinkTo @route="help">Help</LinkTo>

See RFC #459 for details.

Tip: use XYZ codemod to automatically migrate to the new API.

@crhayes
Copy link

crhayes commented Mar 8, 2019

@chancancode Thanks for the response. Please correct me if any of the following is inaccurate.

Although angle brackets can be used to instantiate both ember and glimmer components, in the future I imagine angle brackets will become synonymous with glimmer components.

In a world where all (or 99.9%) of components are glimmer components invoked with angle bracket syntax, and args are bound one-way, it would be unintuitive for these input components to continue leveraging two-way binding.

Is that a reasonable concern?

@mydea
Copy link
Contributor

mydea commented Mar 8, 2019

Personally, I think it would not be ideal to update e.g. from Ember 3.10 to 3.11 (as an example), and suddenly getting 300 deprecation warnings (as we have about 300 {{link-to}} in our app). And the only way to get rid of them would be to either manually change all of them (nightmare!) or run a codemod, forcing them to AngleBracket style.

We aren't quite ready yet to go all-in on AngleBracket invocation (mainly blocked by #457, so hopefully not for long ;) ), and we'd like to do the migration in our own time/on our own terms.

(We have never used the inline form, so I have no opinion about that)

@gossi
Copy link

gossi commented Mar 8, 2019

Hmm, I'm still not convinced. Most of the things, that the original helpers were for are nowadays all achieveable with modifiers, e.g. ember-on-modifier for the inputs and there were/are a couple of RFCs for {{link-to}} alternatives. I would consider these changes just changes to help transition to potentially proper modifers?

Second is that <Input> or <TextArea> are hinting, they provide full proper implementation, as e.g. a styleguide addon would. That has the assumption ember would deliver such thing right out of the box. However, there are shortcomings on the one or the other hand (depending on who might be using it). I think this is ember sending a wrong messagE?

Also, I can see a need to update legacy stuff to nowadays API. As a maybe good compromise, those 3 things can be moved into an addon, that will be installed as part of the official blueprints (until a certain version?). Existing helpers will throw a deprecation message with the hint to the addon (if it's not installed). That would mean, people have a change to opt-out 🙈

@chancancode
Copy link
Member Author

chancancode commented Mar 8, 2019

@gossi @michaelrkn As mentioned in the motivation section, this RFC is not really about fixing/changing the existing APIs. I think it would be fine to open a separate RFC to discuss adding more features, fixing bad APIs moving these into an addon, deprecating them, etc.

The main problem we are trying to solve is that:

  1. These are built-in components that are already shipping with Ember today.
  2. In Ember today, you are supposed to be able to invoke components with either {{foo or equivalently <Foo (see RFC Introduce <AngleBracketInvocationSyntax /> #311).
  3. In Octane (Coming Soon™), we would like to update the guides to consistently recommend using angle bracket style for all component invocations. New users working on new code should never have to learn/encounter the legacy curly invocation syntax for components.

That leaves us with three problems:

  1. <LinkTo> is already equivalent to {{link-to}} today, as you would expect. However, there is no way to pass some of the required arguments, so that leaves <LinkTo> possible but unusable. (It is also no longer idiomatic for components to take positional arguments, so seems bad for such a high-profile built-in component to do that.)
  2. <Input> does not do the same thing as {{input}} at all.
  3. <Textarea> does not do the same thing as {{textarea}} at all.

1 is a bit embarrassing, but 2 and 3 on the list are especially bad because they break the mental model. Personally, I would just consider them a BUGFIX given the angle bracket syntax is supposed to be more or less a syntactic sugar of the curly invocation style (like the difference between { foo: foo } and { foo } in JavaScript POJOs, or arrow functions, etc).

This RFC tries to propose a very targeted fix to these inconsistencies in current versions of Ember (which is the goal of Octane: a coherence checkpoint). It does not preclude making any further changes to these APIs.

Basically what @wycats said in RFC #457.

@chancancode
Copy link
Member Author

chancancode commented Mar 9, 2019

In the future I imagine angle brackets will become synonymous with glimmer components.

In a world where all (or 99.9%) of components are glimmer components invoked with angle bracket syntax, and args are bound one-way, it would be unintuitive for these input components to continue leveraging two-way binding.

@crhayes I don't think that is quite right.

Angle vs curly are just two invocation syntaxes that ultimately said nothing about the thing you are invoking.

For example, these are different but equivalent invocation syntaxes in JavaScript:

  • foo()
  • foo.call()
  • foo.apply()
  • Function.prototype.call.call.call.call.call(foo) 🤯

However, just from this, there is no way to know what foo is. Is it an arrow function? Async? Generator? Native code? Dunno.

Components are the same, it is not possible to tell how a component is implemented from the calling syntax. I think for the foreseeable future, there will be a mix of Ember.Component, @glimmer/component, internal Magic Components™ and custom components. That is by design. We are moving to a world where we aren't constrained to have only One True Component API™ anymore, and I expect to see more specialized component types via the custom component API going forward.

Sometimes, some of these components (especially internal ones, but also custom components) may leverage unusual capabilities. In the JavaScript analogy, generally functions can't "block", but in the browser, functions like alert, prompt and confirm do block (and plenty of native functions in node as well). The only way to know is from reading the documentation of the thing you are invoking.

I personally don't think two-way bindings for this use case is actually Bad™, albeit a little unusual. But we could also have the conversation of redesigning these APIs to do something else, but as per above, that's a different RFC.

@chancancode
Copy link
Member Author

Changes based on feedback from framework core team meeting today:

  • Remove {{link-to}} deprecations (for now, at least)
    • Replace with template lints
  • Use <Textarea> (as opposed to <TextArea>) to match the underlying HTML element and smoother migration
    • Add a helpful message in the error to catch <TextArea> typos

@crhayes
Copy link

crhayes commented Mar 9, 2019

@chancancode I was just thinking in the future glimmer components would be preferred over ember components, and so for new people coming to the framework their mental model of components will be that of glimmer backed angle bracket components. These components will have one-way binding semantics. When they see <Input @value=someValue> I imagine they would expect this to behave with one-way binding semantics.

@mixonic
Copy link
Sponsor Member

mixonic commented Mar 9, 2019

Thanks for summarizing our feedback from the meeting today @chancancode, and folding it in on that last commit.

The Ember.js core team has agreed to move this into Final Comment Period with those changes. This is a quick moving RFC, but as it basically suggests making currently available components invokable with < (which is how we want to suggest most developers write invocations for their own components) the design scope is very small. It doesn't dramatically improve the existing components, but it does fill an important gap in the cohesiveness of the Octane story. This RFC doesn't preclude us from continuing to improve the form component and routing component story in another effort.

@wycats
Copy link
Member

wycats commented Mar 9, 2019

@crhayes I think that you're right that in some future, lots of Ember developers will learn the Glimmer Component API first, and come to form intuitions about the behavior of components from that experience. (aside: with enough time, I don't think there will be a big difference between angle brackets and curlies from this perspective -- people's impression of components will match what they learned: the behavior of the Glimmer component base class).

Between now and then, we will need to come up with a plan, as a community, for what people should do about situations that feel like a good fit for two-way bindings. Maybe the solution will be to double-down on DDAU, but as @rtablada said, that can get fairly verbose. Maybe we'll try something like a better version of mut, or maybe we'll come up with a better idea in the meantime.

Either way, we will need to find a long-term solution for how to think about "data-up" situations, probably soon.

In the meantime, we'd like to be able to recommend that people use angle-bracket syntax for components in general, and avoid unnecessary caveats. #457 is about eliminating one of those caveats (directory nesting) and this RFC is about eliminating another (built-in components).

@crhayes
Copy link

crhayes commented Mar 9, 2019

@crhayes I think that you're right that in some future, lots of Ember developers will learn the Glimmer Component API first, and come to form intuitions about the behavior of components from that experience. (aside: with enough time, I don't think there will be a big difference between angle brackets and curlies from this perspective -- people's impression of components will match what they learned: the behavior of the Glimmer component base class).

Between now and then, we will need to come up with a plan, as a community, for what people should do about situations that feel like a good fit for two-way bindings. Maybe the solution will be to double-down on DDAU, but as @rtablada said, that can get fairly verbose. Maybe we'll try something like a better version of mut, or maybe we'll come up with a better idea in the meantime.

Either way, we will need to find a long-term solution for how to think about "data-up" situations, probably soon.

In the meantime, we'd like to be able to recommend that people use angle-bracket syntax for components in general, and avoid unnecessary caveats. #457 is about eliminating one of those caveats (directory nesting) and this RFC is about eliminating another (built-in components).

Sweet, thanks for the insight. Agreed on removing impediments to using angle bracket components... I personally can't wait!

@chriskrycho
Copy link
Contributor

Just wanted to note my above comment was one of those "GitHub didn't tell me there were more comments"; I have no objections given the additional clarification that was provided between when I started the comment and when I posted it. I'm 👍 on this as it stands.

@luxzeitlos
Copy link

luxzeitlos commented Mar 10, 2019

Will passing attributes work as expected? So can I do <Input class="foo" @value={{bar}} />?
Then what will <Input @value={{myVal}} onchange={{action 'foo'}} /> do?


Another thing: While I totally understand that waiting for the perfect solution leaded to problematic situations (controllers) do we have any idea how we can ever get rid of the two way binding of <Input in the future? I really want a one-way bound version for some situations at least.

Thats exactly why I'm asking for onchange={{action 'foo'}}. It feels like this would be undefined behaviour, and I think we really should avoid this. Will it override the default mutation of myVal or not? How will calling .preventDefault() on the event affect this (if the native way is implemented as classic actions)?

@chriskrycho
Copy link
Contributor

@luxferresum in your desired scenario, is there a reason not to simply use plain-old <input> bound that way? You can do exactly what you're asking for there. (That's actually one of the reasons I changed my mind on the flow here. We need something like <LinkTo>… not so much with Input, except for a migration story, at least IMO.)

@luxzeitlos
Copy link

I use 100% native <inputs. I've two concerns here:

Confusion

My primary concern is confusion between <input and <Input. They look very much alike. Also I think that <Input class="" probably should work as expected. {{input class="" works as expected, and while using <Input @class="" could be an option it would be quite awkward.

This leaves it open to what should happen when the user does <Input onfocus={{action.... Currently {{input value=foo focusOut=(action 'test')}} works, so maybe we want <Input onfocus= to work as expected.

It seems this RFC leaves the whole attributes story out! This means undefined behaviour and this is very bad. So I ask for clarification. This RFC could define that passing attributes to <Input is not supported, that they throw errors or that we lint against them. Or and if they should behave.

Now assuming we support <Input onfocus=, either by undefined behaviour or officially supported.
This means <Input onchange={{action (mut myVal) value="target.value"}} /> will work identical to <input onchange={{action (mut myVal) value="target.value"}} />.
If this is by undefined behaviour this is extremely bad because a simple typo means this code could break with any minor release.
However even if its supported it will still add a massive source of confusion.

And what if a user then does <Input value={{myVar}} onchange={{action (mut myVal) value="target.value"}} />? Will <Input and <input work identical if no @value is passed? I think this could be a nice solution, however it should be by definition and not by undefined behaviour!

If we just ignore all attributes and don't throw then <Input @value={{myVar}} onchange={{action (mut myVar) value="target.value"}} /> will work as expected, but not because the onchange action is called but because {{myVar}} is two way bound.
Now if another developer tries to change this to <Input @value={{myVar}} onchange={{action 'customHandler' value="target.value"}} /> it will not work.
This could be extremely difficult if a native input is used in the line above:

assuming firstname needs a custom handler
 <input value={{this.firstname}} onchange={{action 'changeFirstname' value="target.value"}} />`
 <Input @value={{this.lastname}} onchange={{action (mut lastname) value="target.value"}} />`
and this onchange was introduced by accident

and then a developer tries to add a handler for the lastname:

 <input value={{this.firstname}} onchange={{action 'changeFirstname' value="target.value"}} />`
 <Input @value={{this.lastname}} onchange={{action 'changeLastname' value="target.value"}} />`

Here the @value vs value is the massive visible difference while input vs Input is almost invisible. So a newcomer could think this @ breaks the onchange. While this will not help him it will add massive confusion.

And if this is only by undefined behaviour and we ever decide to support passing attributes this could break a lot of code.

The future of <Input

while <input value={{myVar}} onchange={{action (mut myVar) value="target.value"}} /> works, especially the value="target.value" is a lot of noise. Often I wish for a simple <Input component that I can pass a change action that just receives the value. So I just ask if we have a idea to get rid of the two way bound @value= in the future if we ever want to.

@gossi
Copy link

gossi commented Mar 11, 2019

@chancancode

1 is a bit embarrassing, but 2 and 3 on the list are especially bad because they break the mental model. Personally, I would just consider them a BUGFIX given the angle bracket syntax is supposed to be more or less a syntactic sugar of the curly invocation style (like the difference between { foo: foo } and { foo } in JavaScript POJOs, or arrow functions, etc).

I was also concerned about the mental model breakage, so this explanation should go into the RFC. Basically, the BUGFIX is also introducing tech debt. Shouldn't this be addressed in the RFC, too? Sth: There will be a future RFC coming, that will handle this, e.g. a possible solution would be to move those into a legacy-compatibility-addon?

buschtoens added a commit to buschtoens/ember-link that referenced this pull request Mar 11, 2019
@buschtoens
Copy link
Contributor

buschtoens commented Mar 11, 2019

I made ember-link which gives you a renderless version of <LinkTo> and complies with the API proposed in this PR.

<Link
  @route="some.route"
  @models={{array 123}}
  @query={{hash foo="bar"}}
as |l|>
  <a
    href={{l.href}}
    class={{if l.isActive "is-active"}}
    {{on "click" l.transitionTo}}
  >
    Click me
  </a>
</Link>

@chancancode chancancode self-assigned this Mar 12, 2019
Co-Authored-By: chancancode <godfreykfc@gmail.com>
@chancancode
Copy link
Member Author

@luxferresum <Input class="foo" ...> is certainly supported and encouraged.

More to the point: this is not really a question about <Input> specifically, just a property of angle bracket invocation when combined with components and "splattributes". See #435. <Input> is not at all special in this case--that's the entire point of this RFC.

Generally, any component is allowed (and broadly speaking, encouraged) to forward attributes. Being able to add classes to the output of any components with a consistent API is one of the biggest benefit of this API (again, see #435).

As for passing the onchange "attribute", there are a couple of things to say about it, but TL;DR it "works" but I think it's a bad idea and you shouldn't do it. (If you haven't read the discussion on RFC #435, you should do that first.)

First of all, despite what it looks like, this really isn't "setting an attribute" (it wouldn't make sense to set an HTML "attribute" to anything other than a string). For historical reasons, it will set the onchange property on the element, which is part of an ongoing "props vs attributes" design question. Personally I wouldn't feel comfortable relying on those semantics in general, both in components or HTML elements. If you must, I would prefer that you use the {{on change=...}} modifier (ember-on-modifier) instead (which uses addEventListener).

Second, the semantics of this is no more "undefined" than using the onchange (onclick, etc) attribute on any component that supports splattributes. It has a well defined meaning, in that it would set the onchange property on the component's element to the passed-in function. In this case, that's the input tag.

However, it's indeed the case that if you do this, you are likely interfering with the component's inner working which would make it a bad idea. As we discussed on RFC #435, the fact that you can do this at all certainly makes it a sharp tool. On the other hand, it's not that different from adding a wrapper around the component and capturing the event there, or rendering an element inside a <Button /> component that has its own event handling. I think that composes poorly, and you should not do it, but it's a natural fallout of the different pieces, none of which are specific to the <Input /> component here.

In general, when you assign an event handler to a component using splattributes, you can't predict the exact behavior that will result, because you're interfering with the component's event handling. In this case, the input component never specified what events it relies on, so interfering with any of them could be problematic.

Personally, I think this is not any more of a risk than, say, passing onclick to a custom button component. Since they are built-in, it would be possible to add a template lint to these components if you think it's important, but personally I think that would be a bit arbitrary.

My primary concern is confusion between <input and <Input. They look very much alike.

Again, this is true for angle bracket components in general, and I suspect it's just a matter of getting used to the syntax. When we originally considered this syntax, we felt that the experience from React developers shows that people can differentiate easily between components starting with capital letters and HTML elements starting with lowercase letters. When we made this decision, we also felt that proper syntax highlighting would further reduce the risk of confusion.

They look very much alike. Also I think that <Input class="" probably should work as expected.

It would.

This leaves it open to what should happen when the user does <Input onfocus={{action..... Currently {{input value=foo focusOut= works.

You should use @focusOut but the other thing "works" as per above. Sure, I think if we were to design the API from scratch, perhaps we might not have needed that argument (I honestly am not sure which way it would go) and could just document using splattributes, but we are trying not to redesign the APIs here.

Often I wish for a simple <Input component that I can pass a change action that just receives the value.

It seems like you are unhappy with the current APIs of the <Input component, which I can understand. One good option would be to create a <InputField (or whatever name works best for you) in your app, or as an addon, that wraps the native <input element, with the API/customization you like best, and use that in your app. I would just avoid calling it the same name as the built-in one to avoid confusion.

This RFC was narrowly scoped to bring the existing {{input}} API to angle-bracket invocation, and I intentionally avoided attempting to fix aspects of the API surface at the same time.

@chancancode
Copy link
Member Author

1 is a bit embarrassing, but 2 and 3 on the list are especially bad because they break the mental model. Personally, I would just consider them a BUGFIX given the angle bracket syntax is supposed to be more or less a syntactic sugar of the curly invocation style (like the difference between { foo: foo } and { foo } in JavaScript POJOs, or arrow functions, etc).

I was also concerned about the mental model breakage, so this explanation should go into the RFC. Basically, the BUGFIX is also introducing tech debt. Shouldn't this be addressed in the RFC, too? Sth: There will be a future RFC coming, that will handle this, e.g. a possible solution would be to move those into a legacy-compatibility-addon?

When I said "mental model breakage", I was referring to model proposed in the accepted RFC #311. Namely – angle bracket vs curly braces is purely a syntactic thing, and works across the board for all components, regardless of their implementation (Ember.Component, @glimmer/component, built-ins, etc) and behavior (bindings, DOM output, etc). IMO, that was already fully addressed in the linked RFC #311, which originally introduced this syntax.

Specifically, we explicitly decided not to do what was proposed in a very very early version of the proposal: making angle brackets syntax the opt-in for one-way bindings. That plan was abandoned a very long time ago. Instead, we decided to make angle brackets semantically equivalent to curly syntax to facilitate migration. Those early design ideas may be where some of these confusions are coming from?

Basically, the BUGFIX is also introducing tech debt.

I don't see what technical debt this is incurring. Can you elaborate?

There will be a future RFC coming, that will handle this, e.g. a possible solution would be to move those into a legacy-compatibility-addon?

I personally haven't formed an opinion around that, and I don't think it would be appropriate for this RFC to speculate about that. Instead, I tried to focus on what we need to do to allow existing built-in components to work correctly.

@pzuraq
Copy link
Contributor

pzuraq commented Mar 15, 2019

We decided to accept this RFC at the framework core team meeting today. Thanks @chancancode for working on this and everyone else for the discussion 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet