You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
An ability to specify default values for @named arguments #479
Previously, I was able to provide default values to arguments like this:
component.js:
Component.extend({foo: 'bar'})
template.hbs:
{{foo}}
result:
bar
It was a simple, efficient and very declarative way of specifying default values. It both worked well and was obvious to developers reading the code.
With named args, this no longer works. Though an argument passed externally does overwrite this.foo, setting an initial value to this.foo does not affect @foo in the template.
As a result, there is no way of specifying a default values for arguments!
The only workaround I'm aware of is to keep using {{foo}} or {{this.foo}} instead of {{@foo}}. This works, but it defeats the purpose of @-named args in templates.
Thus, we need a simple, declarative way of specifying default values for arguments used in components.
A developer should be able to tell which arguments are gonna have which default values by simply looking at the component JS file. Those definitions should not be obscured inside a hook such as init.
To be clear, I do NOT want to be using {{@foo}} for arguments without default values and {{this.foo}} for arguments with default values. Such approach creates a lot of confusion.
I want a way to provide default values for named args and keep using them as {{@foo}}, otherwise the named args RFC makes no sense to me.
This is true both for Classic and Glimmer components.
Update (2021-05-23) (and after having spent a lot more time with Octane, and teaching it to folks)
I no longer think the opposite of @rwjblue
I am more... 🤷
To be clear, I do NOT want to be using {{@foo}} for arguments without default values and {{this.foo}} for arguments with default values. Such approach creates a lot of confusion.
fwiw, I think the opposite.
we know that {{@foo}} comes from the caller.
we know that {{this.foo}} is a properties in the current context/component.
(just for clarity, and thoroughness), at present, the only way to get the same behavior as in classic components is via:
and since args are readonly, we can't modify them -- we can't do any sort of dynamic default value setting (like what is possible in classic components).
The only option I really see for still using {{@foo}} and having a default value, is if we set static default values.
however, this introduces an extra layer of complexity, and the merge of args with defaultArgs happens behind the scenes, and may just add 'one more thing' to a beginners set of things to learn / have to deal with.
my preference for a default value pattern would be something like this:
and then still accessed via {{this.foo}}, because it's then a thing defined on the component.
However, this still uses a string to reference an arg, which I don't like, so the getter is my go-to for now.
(I avoid strings / any dynamic code via strings at all costs)
Reacted by Kevin Pfefferle, Ricardo Mendes, Jarek Radosz, flashios09, bekzod, Evan Butler, alan and Suman Hansada
One thing to consider for a design around this is that one of the points of arguments is that they're immutable; so you'd want an approach—whether using decorators or something else—that lets that guarantee still be upheld for the idea of defaults.
Personally, I strongly prefer the explicitness of using a getter and saying "This is my own derived state," however it is derived. That makes it clear in the template that when I see @foo I'm looking at something that was passed in without any kind of transformation or alteration. A thing that can be passed optionally or can be given a different value has different semantics.
That said, optional arguments with defaults in JS are a thing, so I'm not opposed to coming up with a comparable solution here; I just want it to be the case that we don't lose the other benefits of named args.
@NullVoxPopuli: my preference for a default value pattern would be something like this:
and then still accessed via {{this.foo}}
@chriskrycho: I strongly prefer the explicitness of using a getter and saying "This is my own derived state," however it is derived.
I do not want to access some of my arguments via {{@foo}} and others via {{this.foo}}. It's hard to remember which is which. When you have a dozen of arguments, it's a huge pain.
Also, default values for arguments are a very common use case. It's not some caprice that developers, be they quirky enough to desire it, should figure out on their own.
The worst is that when providing a default value with a getter but not passing any value from the parent template, {{@foo}} and {{this.foo}} will have different values. And if I confuse one for another, I'll have unexpected results, with the code looking completely legit and leaving me clueless.
I don't want this gotcha to be scattered all over my code like rakes (gif inside).
Either way you still need to revert to {{this.foo}} in templates, which defeats the purpose of @-named args.
@chriskrycho: That makes it clear in the template that when I see @foo I'm looking at something that was passed in without any kind of transformation or alteration. A thing that can be passed optionally or can be given a different value has different semantics.
But arguments passed externally do get written into the this context of the component! That's the only thing I can be 100% certain about.
And I want to avoid mistakes and confusion, my only option is to always use {{this.foo}}. It will work predictably and will never cause any issues.
But I don't want to be a black sheep. And I do want to see a distinction between arguments and properties in my templates. It's just arguments with default values are still arguments! They've always been.
@chriskrycho: Personally, I strongly prefer the explicitness of using a getter and saying "This is my own derived state," however it is derived. That makes it clear in the template that when I see @foo I'm looking at something that was passed in without any kind of transformation or alteration. A thing that can be passed optionally or can be given a different value has different semantics.
I can agree with that for Glimmer components. They are more strict and are a new entity, so a shift of a mental model is acceptable.
But everyone's mental model for Classic components is that an argument can be accessed in a template the same way, regardless of whether it has a default value or not. Adding (or removing, for that matter!) a default value to/from an existing argument did not force me to update my template. It just worked, and that's how it should be.
The named arguments RFC changed that. 😒 But note how adding a default value now requires rewriting a template from {{@foo}} to {{this.foo}}, but removing a default value does not require switching back to {{@foo}}.
It is an inconsistency and a source of confusion. I've been always subconsciously uncomfortable using @ & this in Classic component templates and now I understand why.
This was discussed elsewhere (apparently not on the RFC) and I seem to recall the answer (for the time being) was to be {{or @argName "static value"}} or {{or @argName this.argDefault}}. The only issue with that is what if the @argName is falsy and that's what you'd want. Maybe we need a {{default}} helper to only pass the second/default value when the arg is undefined. Related: #388
It's a pretty universal convention to have default values defined near the top of the component's JS class.
What if the value is used multiple times in a template? You'll have to provide the default value multiple times. It's tedious and prone to regressions.
@lolmaus
There are two huge issues with this approach:
It's a pretty universal convention to have default values defined near the top of the component's JS class.
What if the value is used multiple times in a template? You'll have to provide the default value multiple times. It's tedious and prone to regressions.
Putting defaults on the component JavaScript class won't be a viable solution for "template only" components. So the solution for this issue has to be in the template.
That's why I put {{or @argName this.argDefault}} as another example as a way to show the option of putting the default value in one place to reference. Another template-only solution would be to {{let "some default" as |argDefault|}} and then {{or @argName argDefault}}.
@lolmaus we absolutely think your concerns are legit.
For context, when originally designing namespaced args and Glimmer components, this issue did come up, and we discussed it in depth quite a few times. In all those discussions, we kept coming back to the many issues with the way that arguments work in classic components, and decided that the pros of namespaced arguments and separation of concerns outweighed the cons. The explicitness may feel verbose, but the extra safety it provides is generally worth it. This is similar to our current stance on Mixins.
That said, we also intentionally left room for future solutions here, such as a separate defaultArgs export. We realize this is a common enough use case that the verbosity is not ideal, and personally I really do want to see something better here. I also want that solution to have the benefits I discussed above:
Separate from both the template and the class definitions, so neither is required
Isolated from component state and incoming args state, so that defaulting doesn't become a magnet for business logic (as it can be currently)
Easier to automate documentation for
Ideally, a better typing strategy, especially assuming we do get template typing at some point. While I agree classic components were somewhat easier to type, I actually feel like there is some type smell there, especially around having to use ! to specify required values.
I realize that not having this ability right now is frustrating, but I think this is also part of the philosophy Ember is taking on right now - iterative steps and improvements, day over day and year over year, rather than complete rewrites that attempt to solve every problem all at once. There are plenty more features, ideas, and improvements we have in the wings for the post-Octane world, and we'll keep on shipping them one at a time 😄
Discussions like this help us prioritize and gather ideas for how to solve the problems better, so we absolutely appreciate you bringing this up, I just want to reiterate, we get where you're coming from, and we're taking it seriously. We just need to do the design work, and iterate toward a solid solution.
Reacted by Kevin Pfefferle, Chris Santero, Chris Krycho, Ryan Panning, Scott Ames-Messinger, Sam Van Campenhout, Eli Dupuis, Avery Bloom and akashdsouza
I'm late to the party. I found this discussion somewhere left it open in a tab and now I had the time to read through it, totally worth it.
I can say, I totally had the same experience as @lolmaus with this. With sparkles rising, I created the @arg to quickly serve my direct needs. I wouldn't recommend using it today anymore, though it was a good learning experiment for me all along the way with sparkles (now glimmer) components and decorators. This project basically funnels into ember-decorators/argument#95
However, all along the way, you need to do a mindshift - that is particularly not easy when trying to bake old patterns into a new system (which easily become anti-patterns as in this case). So some things I learned, that I hope that help you (since not everything is finalized, those things may change):
Glimmer is template only, means you need to shift your priority (from code to template); it is: template (declarative) first, programming second. That also goes for didUpdate() => {{did-update}} (here I also had a missunderstanding of what it can do, it is here to make explicit calls if one of the args is changing - very helpful). That said doing {{or}} or {{optional}} plenty of times in a template is tedious and it is in our craft/nature as developers to seek for a smarter solution that stimulates our brain 😁
Set your initial state in the respective initializer (init() for classic, constructor() for glimmer):
If this.foo needs to be a dynamic value (depending on the passed in @foo) then use a getter (and praise auto-tracking)
this.foo and @foo are two separate and explicit things. Do not try to use them for the same thing (as with args on classic components). Just don't. (Re)think your architecture for that component, it will thank you (most of the times).
That said, not everything has landed in ember/glimmer/octane yet, things that will make our lives easier (e.g. not attributes to glimmer components yet, when using the {{component}} helper).
The best tip here (and I gonna repeat myself for this): It's a mindshift, embrace it. Throw your "old habited" balast over board and come into a clean, explicit and more structured ember-world :)
//cc @locks@jenweber -> is that something for the guides, too? Regarding the migration part?
Reacted by Sam Van Campenhout, Jeff Hertzler, Luis Vegerano, Andrey Mikhaylov (lolmaus), Falk Pauser and akashdsouza
Reacted by Andrey Mikhaylov (lolmaus) and Falk Pauser
I've been thinking about this more, along with the ability to specify some sort of ArgTypes similar to React's PropTypes, and one thing I'm coming back to and realizing is that in the future, we won't necessarily be guaranteed to have one component per-file. This is still something that needs to be figured out, what our SFC/template import syntax looks like, but if we do land on something that allows multiple components per file, my suggestion of export const defaultArgs won't work.
I think it may actually make sense to use static class properties instead. It could look something like:
And the same could be done for some sort of ArgTypes system. The other option would be to have a different syntax specifically for TO-components, but I don't think that would be ideal. It'd make converting from a TO-component to a class-component much harder, and would make it harder to statically analyze.
Reacted by Josemar Luedke, Scott Ames-Messinger, Jeff, Arthur Goldsmith and Daniel J
I found reading through the comments for this quite frustrating because it seems I hold very different things important than most of the people here. That's going to make the rest of this post feel somewhat rant-y, which I apologize in advance for.
The named arguments RFC broke an extremely basic and commonly feature of components, which are the modern building block of front-end UI. The fact that it was accepted and implemented without any support for default values (all the current solutions are extremely bug-prone because anyone using @foo syntax won't get the default value) represents, in my mind, not some strength of Ember's new iterative approach but a massive disconnect with users and a huge threat to the framework's viability for new users relative to other popular options.
The two main considerations being expressed in this thread preventing a solution being agreed upon (many of the decorator-based solutions have seemed great to me) are things that I utterly disagree with. They are:
That it is important to know whether a value comes from a caller or the default value. Why? Much like in JS functions, I don't see why it would. It's a value, you use it. If you care, then default values are not the feature you wanted in the first place. Many members of the Ember community seem to value "not needing to look at the JS to know the value", but from my perspective coming from other frameworks like Vue that's probably a relic of Ember's long history of failing to co-locate the template and the JS in any way. Putting the two closer together is the solution (as evidenced by other frameworks gravitating that way), not breaking things like default argument values so you don't need to pull up the JS file that's 6 directories up and 5 directories down...
That it is worth prioritizing compatibility with template-only components over getting a solution to this issue using JS in place. Multiple people have brought this up as a concern but nobody has offered up a solution. That's because this feature is almost certainly not compatible with template-only components, nor should it need to be. I agree that it's cool to not need a class if you don't have anything besides the template to define for a component, but I think it's extremely dangerous to the future of the framework to start requiring new features to somehow support them.
It's hard to participate in a debate like this where the disagreements seem so fundamental, so thanks for bearing with me.
Reacted by Avery Bloom, Evan Butler, Daniel J, Philip Bjorge, Selva, Sascha Mayr, Andrey Mikhaylov (lolmaus), Cristina and Chris Hut
@maxbogue I understand the frustration. The core team has been in a holding pattern recently as we put the final touches on Octane, so we haven't been actively working on or reviewing RFCs, which is why progress has been slow.
I don't think we're as philosophically disconnected as it may seem though. For your first point, it's important to realize that you do get a clear distinction in a function definition as to what is an argument, and what is a local value.
Personally I think this is much more difficult to reason about, and I think this is why a lot of folks both in the community and on the core team want to make sure we don't reintroduce semantics like this.
This doesn't mean that everyone is against defaults in general. JS also didn't have default values for arguments for quite some time, but they have definitely been valuable since they were introduced, and I think that we can probably find a solution that works for all of our use cases, and still maintains the semantics we worked for with named arguments.
Re: prioritizing template-only components (something I've mentioned a few times in this thread), IMO it's is not prioritizing them over class based components, but making sure we don't design ourselves into a corner. If we were to come up with a design for defaults that, for instance, limited our options for designing template-imports, that would not be ideal. If we were to come up with a design that had no way of translating into TO components, that would also not be ideal. It's mostly about doing our due-diligence, and making sure we've considered as much as possible in the design phase.
I'm hopeful that we'll be able to make progress on this soon! And again, I understand the frustration and hope the explanation helps a bit. Thanks for your feedback!
@maxbogue I understand the frustration. The core team has been in a holding pattern recently as we put the final touches on Octane, so we haven't been actively working on or reviewing RFCs, which is why progress has been slow.
I don't think we're as philosophically disconnected as it may seem though. For your first point, it's important to realize that you do get a clear distinction in a function definition as to what is an argument, and what is a local value.
Personally I think this is much more difficult to reason about, and I think this is why a lot of folks both in the community and on the core team want to make sure we don't reintroduce semantics like this.
I like @foo syntax as a way to differentiate what is an argument, I just don't see the problem in wrapping the default value up into that (same as JS). Not against the syntax wholesale, just that it was implemented without solving this use-case.
Re: prioritizing template-only components (something I've mentioned a few times in this thread), IMO it's is not prioritizing them over class based components, but making sure we don't design ourselves into a corner. If we were to come up with a design for defaults that, for instance, limited our options for designing template-imports, that would not be ideal. If we were to come up with a design that had no way of translating into TO components, that would also not be ideal. It's mostly about doing our due-diligence, and making sure we've considered as much as possible in the design phase.
I guess it's pretty unclear to me how you would ever do something like this with TO components, because templates are just templates and the JS is everything else about a component. This is pretty clearly part of the everything else. Trying to make every component feature going forward TO-compatible seems pretty problematic to me.
I guess it's pretty unclear to me how you would ever do something like this with TO components, because templates are just templates and the JS is everything else about a component
So, for some extra context here, one thing that is being discussed a lot is making TO components much more viable for use in the majority of component situations. Today they're fairly limited in what they can do, but with template imports it may be possible to make them much more powerful.
With this type of a setup, it would be possible to use TO components for many more use cases, especially when no root state exists on the component. Helper functions can take the place of computed properties/getters, and you could define modifiers inline as well potentially. Today, those would all have to be global, and the cost to setting them up is pretty prohibitive because of that.
This isn't all set in stone at all, template imports could also end up being much more minimal (e.g. only allow import syntax), but I do think it demonstrates the possibilities, and why we would like to make sure we think about them in general when designing new component APIs.
Got to be honest, I'm not a fan of putting JavaScript in frontmatter, that belongs in the .js file. I'd rather work out the single file components solution. Unless this is the SFC solution, in that case, I still don't like it. 😛
There are a few different general directions that are being discussed, some that are even more minimal (no JS except import statements allowed). I posted two of the options mainly to demonstrate why some folks think TO components are worth investing into, myself included, and because I don't want to give the impression that anything has been decided - still plenty of RFCs to go before we finalize the syntax!
@pzuraq This is a bit of a side tangent, but what’s the main driving force behind having that TO component you showed (which wasn’t just a template, but verbiage aside) over a standard/glimmer/single file/class component? I get the explicit import, but the rest just seems like needless confusion, and could be easily done with a template and a class rather then template and functions.
Is it performance? Memory usage? “Ergonomics”? If there’s a better place for this question, point me there and i’ll read ask their.
I'm closing this due to inactivity. This doesn't mean that the idea presented here is invalid, but that, unfortunately, nobody has taken the effort to spearhead it and bring it to completion. Please feel free to advocate for it if you believe that this is still worth pursuing. Thanks!
Activity
lolmaus commentedon Apr 19, 2019
To be clear, I do NOT want to be using
{{@foo}}for arguments without default values and{{this.foo}}for arguments with default values. Such approach creates a lot of confusion.I want a way to provide default values for named args and keep using them as
{{@foo}}, otherwise the named args RFC makes no sense to me.This is true both for Classic and Glimmer components.
NullVoxPopuli commentedon Apr 19, 2019
Update (2021-05-23) (and after having spent a lot more time with Octane, and teaching it to folks)
I no longer think the opposite of @rwjblue
I am more... 🤷
fwiw, I think the opposite.
we know that
{{@foo}}comes from the caller.we know that
{{this.foo}}is a properties in the current context/component.(just for clarity, and thoroughness), at present, the only way to get the same behavior as in classic components is via:
and since
argsare readonly, we can't modify them -- we can't do any sort of dynamic default value setting (like what is possible in classic components).The only option I really see for still using
{{@foo}}and having a default value, is if we set static default values.however, this introduces an extra layer of complexity, and the merge of args with defaultArgs happens behind the scenes, and may just add 'one more thing' to a beginners set of things to learn / have to deal with.
my preference for a default value pattern would be something like this:
and then still accessed via
{{this.foo}}, because it's then a thing defined on the component.However, this still uses a string to reference an arg, which I don't like, so the getter is my go-to for now.
(I avoid strings / any dynamic code via strings at all costs)
chriskrycho commentedon Apr 19, 2019
One thing to consider for a design around this is that one of the points of arguments is that they're immutable; so you'd want an approach—whether using decorators or something else—that lets that guarantee still be upheld for the idea of defaults.
Personally, I strongly prefer the explicitness of using a getter and saying "This is my own derived state," however it is derived. That makes it clear in the template that when I see
@fooI'm looking at something that was passed in without any kind of transformation or alteration. A thing that can be passed optionally or can be given a different value has different semantics.That said, optional arguments with defaults in JS are a thing, so I'm not opposed to coming up with a comparable solution here; I just want it to be the case that we don't lose the other benefits of named args.
lolmaus commentedon Apr 19, 2019
I do not want to access some of my arguments via
{{@foo}}and others via{{this.foo}}. It's hard to remember which is which. When you have a dozen of arguments, it's a huge pain.Also, default values for arguments are a very common use case. It's not some caprice that developers, be they quirky enough to desire it, should figure out on their own.
The worst is that when providing a default value with a getter but not passing any value from the parent template,
{{@foo}}and{{this.foo}}will have different values. And if I confuse one for another, I'll have unexpected results, with the code looking completely legit and leaving me clueless.I don't want this gotcha to be scattered all over my code like rakes (gif inside).
How is this ☝️ better than these 👇 ?
Either way you still need to revert to
{{this.foo}}in templates, which defeats the purpose of @-named args.But arguments passed externally do get written into the
thiscontext of the component! That's the only thing I can be 100% certain about.And I want to avoid mistakes and confusion, my only option is to always use
{{this.foo}}. It will work predictably and will never cause any issues.But I don't want to be a black sheep. And I do want to see a distinction between arguments and properties in my templates. It's just arguments with default values are still arguments! They've always been.
lolmaus commentedon Apr 19, 2019
Note that there's no linter (and can hardly imagine one to exist) for warning you that you're using
{{@foo}}whereas a default value is available.NullVoxPopuli commentedon Apr 19, 2019
I think maybe we have something else to talk about now <3
Can you give an example of something where'd you have this many args?
fwiw, the point is not to remember, but to be able to discover. Apps are too big to keep in your head, ya know?
imo, the getter is better, because there is no string access on args.
that defeats the purpose of read-only args. ya don't always need a default. :-\
lolmaus commentedon Apr 19, 2019
Oh wow, doing
get foo() {}and passing@foofrom a parent template crashes Ember! 🤦♂️lolmaus commentedon Apr 19, 2019
That's what I said in the top post, and this is exactly the reason to request an RFC for default values.
NullVoxPopuli commentedon Apr 19, 2019
that's probably because we have to support classic accessing for those not fully on the named args train.
once we get rid of classic arg accessing, that won't be an issue
lolmaus commentedon Apr 19, 2019
I can agree with that for Glimmer components. They are more strict and are a new entity, so a shift of a mental model is acceptable.
But everyone's mental model for Classic components is that an argument can be accessed in a template the same way, regardless of whether it has a default value or not. Adding (or removing, for that matter!) a default value to/from an existing argument did not force me to update my template. It just worked, and that's how it should be.
The named arguments RFC changed that. 😒 But note how adding a default value now requires rewriting a template from
{{@foo}}to{{this.foo}}, but removing a default value does not require switching back to{{@foo}}.It is an inconsistency and a source of confusion. I've been always subconsciously uncomfortable using
@&thisin Classic component templates and now I understand why.Panman82 commentedon Apr 19, 2019
This was discussed elsewhere (apparently not on the RFC) and I seem to recall the answer (for the time being) was to be
{{or @argName "static value"}}or{{or @argName this.argDefault}}. The only issue with that is what if the@argNameis falsy and that's what you'd want. Maybe we need a{{default}}helper to only pass the second/default value when the arg isundefined. Related: #388NullVoxPopuli commentedon Apr 19, 2019
you could still use classic / ambiguous access:
{{foo}}where,
lolmaus commentedon Apr 19, 2019
There are two huge issues with this approach:
lolmaus commentedon Apr 19, 2019
Which equally defeats the purpose of named arguments RFC.
BTW, you don't need to use
.extend()for that. Class property works.Panman82 commentedon Apr 19, 2019
{{or @argName this.argDefault}}as another example as a way to show the option of putting the default value in one place to reference. Another template-only solution would be to{{let "some default" as |argDefault|}}and then{{or @argName argDefault}}.21 remaining items
lolmaus commentedon May 2, 2019
I've been reiterating on my complaints in a hope to achieve agreement that the issue is legit.
I've been thinking of employing decorators:
Not sure how to do it for Glimmer components, though. :( Maybe a class decorator?
pzuraq commentedon May 2, 2019
@lolmaus we absolutely think your concerns are legit.
For context, when originally designing namespaced args and Glimmer components, this issue did come up, and we discussed it in depth quite a few times. In all those discussions, we kept coming back to the many issues with the way that arguments work in classic components, and decided that the pros of namespaced arguments and separation of concerns outweighed the cons. The explicitness may feel verbose, but the extra safety it provides is generally worth it. This is similar to our current stance on Mixins.
That said, we also intentionally left room for future solutions here, such as a separate
defaultArgsexport. We realize this is a common enough use case that the verbosity is not ideal, and personally I really do want to see something better here. I also want that solution to have the benefits I discussed above:!to specify required values.I realize that not having this ability right now is frustrating, but I think this is also part of the philosophy Ember is taking on right now - iterative steps and improvements, day over day and year over year, rather than complete rewrites that attempt to solve every problem all at once. There are plenty more features, ideas, and improvements we have in the wings for the post-Octane world, and we'll keep on shipping them one at a time 😄
Discussions like this help us prioritize and gather ideas for how to solve the problems better, so we absolutely appreciate you bringing this up, I just want to reiterate, we get where you're coming from, and we're taking it seriously. We just need to do the design work, and iterate toward a solid solution.
gossi commentedon May 5, 2019
I'm late to the party. I found this discussion somewhere left it open in a tab and now I had the time to read through it, totally worth it.
I can say, I totally had the same experience as @lolmaus with this. With sparkles rising, I created the
@argto quickly serve my direct needs. I wouldn't recommend using it today anymore, though it was a good learning experiment for me all along the way with sparkles (now glimmer) components and decorators. This project basically funnels into ember-decorators/argument#95However, all along the way, you need to do a mindshift - that is particularly not easy when trying to bake old patterns into a new system (which easily become anti-patterns as in this case). So some things I learned, that I hope that help you (since not everything is finalized, those things may change):
Glimmer is template only, means you need to shift your priority (from code to template); it is: template (declarative) first, programming second. That also goes for
didUpdate()=>{{did-update}}(here I also had a missunderstanding of what it can do, it is here to make explicit calls if one of the args is changing - very helpful). That said doing{{or}}or{{optional}}plenty of times in a template is tedious and it is in our craft/nature as developers to seek for a smarter solution that stimulates our brain 😁Set your initial state in the respective initializer (
init()for classic,constructor()for glimmer):If
this.fooneeds to be a dynamic value (depending on the passed in@foo) then use a getter (and praise auto-tracking)this.fooand@fooare two separate and explicit things. Do not try to use them for the same thing (as with args on classic components). Just don't. (Re)think your architecture for that component, it will thank you (most of the times).That said, not everything has landed in ember/glimmer/octane yet, things that will make our lives easier (e.g. not attributes to glimmer components yet, when using the
{{component}}helper).The best tip here (and I gonna repeat myself for this): It's a mindshift, embrace it. Throw your "old habited" balast over board and come into a clean, explicit and more structured ember-world :)
//cc @locks @jenweber -> is that something for the guides, too? Regarding the migration part?
pzuraq commentedon Jul 15, 2019
I've been thinking about this more, along with the ability to specify some sort of
ArgTypessimilar to React'sPropTypes, and one thing I'm coming back to and realizing is that in the future, we won't necessarily be guaranteed to have one component per-file. This is still something that needs to be figured out, what our SFC/template import syntax looks like, but if we do land on something that allows multiple components per file, my suggestion ofexport const defaultArgswon't work.I think it may actually make sense to use static class properties instead. It could look something like:
And the same could be done for some sort of ArgTypes system. The other option would be to have a different syntax specifically for TO-components, but I don't think that would be ideal. It'd make converting from a TO-component to a class-component much harder, and would make it harder to statically analyze.
maxbogue commentedon Nov 22, 2019
I found reading through the comments for this quite frustrating because it seems I hold very different things important than most of the people here. That's going to make the rest of this post feel somewhat rant-y, which I apologize in advance for.
The named arguments RFC broke an extremely basic and commonly feature of components, which are the modern building block of front-end UI. The fact that it was accepted and implemented without any support for default values (all the current solutions are extremely bug-prone because anyone using
@foosyntax won't get the default value) represents, in my mind, not some strength of Ember's new iterative approach but a massive disconnect with users and a huge threat to the framework's viability for new users relative to other popular options.The two main considerations being expressed in this thread preventing a solution being agreed upon (many of the decorator-based solutions have seemed great to me) are things that I utterly disagree with. They are:
That it is important to know whether a value comes from a caller or the default value. Why? Much like in JS functions, I don't see why it would. It's a value, you use it. If you care, then default values are not the feature you wanted in the first place. Many members of the Ember community seem to value "not needing to look at the JS to know the value", but from my perspective coming from other frameworks like Vue that's probably a relic of Ember's long history of failing to co-locate the template and the JS in any way. Putting the two closer together is the solution (as evidenced by other frameworks gravitating that way), not breaking things like default argument values so you don't need to pull up the JS file that's 6 directories up and 5 directories down...
That it is worth prioritizing compatibility with template-only components over getting a solution to this issue using JS in place. Multiple people have brought this up as a concern but nobody has offered up a solution. That's because this feature is almost certainly not compatible with template-only components, nor should it need to be. I agree that it's cool to not need a class if you don't have anything besides the template to define for a component, but I think it's extremely dangerous to the future of the framework to start requiring new features to somehow support them.
It's hard to participate in a debate like this where the disagreements seem so fundamental, so thanks for bearing with me.
pzuraq commentedon Nov 22, 2019
@maxbogue I understand the frustration. The core team has been in a holding pattern recently as we put the final touches on Octane, so we haven't been actively working on or reviewing RFCs, which is why progress has been slow.
I don't think we're as philosophically disconnected as it may seem though. For your first point, it's important to realize that you do get a clear distinction in a function definition as to what is an argument, and what is a local value.
What Ember did historically would be, more or less, translated to JS (not in a literal way, but in a semantically similar hypothetical) like this:
Personally I think this is much more difficult to reason about, and I think this is why a lot of folks both in the community and on the core team want to make sure we don't reintroduce semantics like this.
This doesn't mean that everyone is against defaults in general. JS also didn't have default values for arguments for quite some time, but they have definitely been valuable since they were introduced, and I think that we can probably find a solution that works for all of our use cases, and still maintains the semantics we worked for with named arguments.
Re: prioritizing template-only components (something I've mentioned a few times in this thread), IMO it's is not prioritizing them over class based components, but making sure we don't design ourselves into a corner. If we were to come up with a design for defaults that, for instance, limited our options for designing template-imports, that would not be ideal. If we were to come up with a design that had no way of translating into TO components, that would also not be ideal. It's mostly about doing our due-diligence, and making sure we've considered as much as possible in the design phase.
I'm hopeful that we'll be able to make progress on this soon! And again, I understand the frustration and hope the explanation helps a bit. Thanks for your feedback!
maxbogue commentedon Nov 22, 2019
I like
@foosyntax as a way to differentiate what is an argument, I just don't see the problem in wrapping the default value up into that (same as JS). Not against the syntax wholesale, just that it was implemented without solving this use-case.I guess it's pretty unclear to me how you would ever do something like this with TO components, because templates are just templates and the JS is everything else about a component. This is pretty clearly part of the everything else. Trying to make every component feature going forward TO-compatible seems pretty problematic to me.
pzuraq commentedon Nov 22, 2019
So, for some extra context here, one thing that is being discussed a lot is making TO components much more viable for use in the majority of component situations. Today they're fairly limited in what they can do, but with template imports it may be possible to make them much more powerful.
Or a frontmatter version:
With this type of a setup, it would be possible to use TO components for many more use cases, especially when no root state exists on the component. Helper functions can take the place of computed properties/getters, and you could define modifiers inline as well potentially. Today, those would all have to be global, and the cost to setting them up is pretty prohibitive because of that.
This isn't all set in stone at all, template imports could also end up being much more minimal (e.g. only allow
importsyntax), but I do think it demonstrates the possibilities, and why we would like to make sure we think about them in general when designing new component APIs.Panman82 commentedon Nov 22, 2019
Got to be honest, I'm not a fan of putting JavaScript in frontmatter, that belongs in the
.jsfile. I'd rather work out the single file components solution. Unless this is the SFC solution, in that case, I still don't like it. 😛pzuraq commentedon Nov 23, 2019
There are a few different general directions that are being discussed, some that are even more minimal (no JS except import statements allowed). I posted two of the options mainly to demonstrate why some folks think TO components are worth investing into, myself included, and because I don't want to give the impression that anything has been decided - still plenty of RFCs to go before we finalize the syntax!
webark commentedon Nov 23, 2019
@pzuraq This is a bit of a side tangent, but what’s the main driving force behind having that TO component you showed (which wasn’t just a template, but verbiage aside) over a standard/glimmer/single file/class component? I get the explicit import, but the rest just seems like needless confusion, and could be easily done with a template and a class rather then template and functions.
Is it performance? Memory usage? “Ergonomics”? If there’s a better place for this question, point me there and i’ll read ask their.
pzuraq commentedon Nov 24, 2019
@webark let's continue this discussion on Discuss, started a thread over there
wagenet commentedon Jul 23, 2022
I'm closing this due to inactivity. This doesn't mean that the idea presented here is invalid, but that, unfortunately, nobody has taken the effort to spearhead it and bring it to completion. Please feel free to advocate for it if you believe that this is still worth pursuing. Thanks!
NullVoxPopuli commentedon Jul 23, 2022
I think someone had an idea that layered on top of: #779 (and #800 / #748 )
and was something like:
tho, maybe
??=would have better semantics? idk?