-
-
Notifications
You must be signed in to change notification settings - Fork 406
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
Component helper in JS #434
Comments
I have also wanted the ability to do I'd like to add that it would be awesome if instances of |
Maybe import { componentRenderer } from '@ember/component'; I'm not really sure where it would be appropriate to export such a thing. I also think it needs some sort of rendering context to work...? |
As I mentioned in the other thread, the difficulty here is passing bound arguments. Something like |
@chancancode I wouldn't expect Most of the time these curried component definitions are created within computed property bodies or custom template helpers, so a change in any of the upstream values causes a new curried component definition to be emitted. This hopefully is handled efficiently by glimmer (e.g. if a new definition object is emitted but it has the same shape as the previous definition object then the old component shouldn't be completely torn down). |
ehh.. i'd expect it to work the same way as the component helper in the template, and since that's bound, I think this would be too. That would be a pretty huge ergonomics issue if they didn't behave the same. |
I think you are describing bound arguments 😉
Nope.
I agree, but it would need some kind of different api/syntax, otherwise there is no way to track the property/changes. |
@mehulkar That just doesn't seem possible with the syntax you are proposing. But again I really don't see this as limiting at all. I would simply rewrite your example as: // app/controllers/index.js
export default Controller.extend({
mappedModels: computed('model.@each.{name,type}', function() {
return this.model.map(item => {
let component;
switch (item.type) {
case 'foo':
component = createComponent('special-component', { arg1: item.name, arg2: item.type });
break;
default:
component = createComponent('default-component', { otherArg: item.name });
}
return Object.assign({}, item, { component });
});
});
});
@chancancode That's unfortunate. How is this handled today with the |
@Herriau so long as the first argument to the helper doesn't change, the definition object is stable https://github.com/glimmerjs/glimmer-vm/blob/master/packages/@glimmer/runtime/lib/references/curry-component.ts#L40-L42 This works because the args are curried as references https://github.com/glimmerjs/glimmer-vm/blob/master/guides/04-references.md |
@chancancode Thank you for the exhaustive list of links. In that case, and if glimmer is to remain as such, it does indeed look like we would want a way for developers to pass values that are bound to some context. In the few cases where we've needed this, we have resorted to const component = createComponent('special-component', {
item,
arg1: reads('item.name'),
arg2: reads('item.type'),
}); This isn't to say that computed properties should be supported at all, but syntactically-speaking I feel like this makes sense. |
@Herriau I'd recommend creating it as an addon that people can use and find caveats and API limitations through that and then feeding it back into the RFC. |
@chancancode @pzuraq does |
I do think tracked properties make it possible to revisit this. As we move toward components as really first class values (meaning you can import a component into JS, pass it around, and eventually invoke it), I think the gap of not being able to curry in JS becomes more glaring. |
I believe this here is very much the same as #563, am I right? |
@boris-petrov Not quite, this is discussing allowing you to curry component definitions, e.g. provide arguments to them. The end result is still a component definition, not a rendered component. |
Now that components can be passed around as values and then rendered via |
I banged on this for a while, it did not turn out to be easy. I think it’s
equivalent to being able to splat component args and block args, because
with those features you can implement currying as deriving a wrapper
component.
The other problem with currying in JS is reactivity. You can’t make a
tracked local variable, so it’s easy to accidentally curry values that
don’t update.
…On Fri, Jul 9, 2021 at 6:57 AM NullVoxPopuli ***@***.***> wrote:
Now that components can be passed around as values and then rendered via
<this.myComponent>, what would it take to curry args in js? The component
helper can't be 'that' magical, yeah?
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#434 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AACN6MWGPVJCQ65PNJYTDHTTW3IZDANCNFSM4GRDLNMA>
.
|
🤔 the same problems need to be solved for currying args to modifiers, then ya? I wanted to do this earlier today: <div {{@something.modifier}} /> where |
Right, this is similar to the problems we dealt with for invoking helpers in JS. I think the solution would be similar: let curried = curry(MyComponent, () => {
return {
named: {
someArg: this.args.someArg,
}
};
}); Then the process of getting the arguments can necessarily be tracked, allowing us to react whenever they change.
Yes, in fact the currying infrastructure has recently been unified in the VM so all curried modifiers, helpers, and components end up being the same thing internally. I think we could introduce a single keyword in both JS and templates that can be used with all of them in order to simplify things. |
That solves a different problem than I was thinking of. It makes it possible to detect which tracked state is consumed by the arguments. But it doesn't prevent people from currying locals: let someArg = 1;
let curried = curry(MyComponent, () => {
return {
named: {
someArg
}
};
});
function doesNotReact() {
someArg = 2;
} Maybe that's fine, I mean you do need to learn to put |
Is it also fine that this would we want wrapper utilities to help out with that: function component(Klass, thunk) {
return curry(Klass, () => {
let named = thunk();
return { named };
});
}
let curried = component(MyComponent, () => ({ fruit: this.pepper })) |
@ef4 yeah I think that's a more general problem, you can also use locals in getters too for instance. I think we just need to double down on describing how tracked state works in general here. @NullVoxPopuli yes, we would definitely want to be able to pass positionals, even components can receive positionals. I'm not sure about making any short hands/utilities for the initial version of it, it'd probably be best for it to cover just the basics and then we can figure out a better shorthand in the future, but I'd definitely be open to thinking about it. |
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! |
This hadn't really found a resolution, here is my attempt, given we can "use anything as values" since ember-source 3.25. The intent in the original post is to dynamically use some components based on some value in JS. import DefaultComponent from './default';
import FooComponent from './foo';
export default class Demo extends Component {
get items() {
return this.args.someArray.map(item => {
let component = DefaultComponent;
if (item.type === 'foo') {
component = FooComponent;
}
return { ...item, component };
});
}
} {{#each this.items as |item|}}
{{#if (eq item.type 'foo') }}
<item.component @arg1={{item.name}} @arg2={{item.type}} />
{{else}}
<item.component @otherArgs={{item.name}} />
{{/if}}
{{/each}} |
I've implemented a The implementation is only a few lines long, and follows the same patterns Ember core uses to curry components in JS. It's using only 'public' APIs on Open to suggestions for API improvements! If people find it useful, perhaps we can reboot the discussions about including something like this in Ember itself. |
It's possible to use the
{{component}}
helper in templates to specify a dynamic component name. This allows all kinds of polymorphic approaches to rendering data, which is awesome. However, as of now, it is not easily possible to bind a different set of arguments to dynamic components, which means that your components need to have the exact same API.It's debatable whether or not this is a good idea, but if there was an equivalent to the component helper in JS, we would be able to construct a component with a different set of arguments.
The goal is to be able to do something like this:
This lets us use separate components based on
item.type
, but allows for flexibility in their API.The text was updated successfully, but these errors were encountered: