[TwigComponent] Easier composability of components.
We're building a large set of twig components. Currently, it is possible to pass the attributes from one component into another one. This works, as long as the attributes in the parent component are no real properties inside the twig component PHP class:
Use-case:
#[AsTwigComponent('button')]
class Button {
public string $type = 'submit';
}
#[AsTwigComponent('primary-button')]
class PrimaryButton extends Button {
}
Now the template of the Primary button could look like this:
{% set children = block('children') %}
{% component 'button' with attributes.defaults({
type: type,
class: 'primary',
}).all() %}
{% block children %}{{ children|raw }}{% endblock %}
{% endcomponent %}
And button:
<button type="{{type}}" {{attributes.defaults({class: 'button'})}}>
{% block children %}{% endblock %}
</button>
This works, but we had to do some strange things in order to make it work:
- Primary-button needs to pass down all public properties manually. It would be nice if there was a way to just pass all attributes - even if they are placed in properties.
- See twig of primary button above:
type: type,
- See twig of primary button above:
- Primary-button needs to serialize to all(). It would be nice if the component took both an array and the Twig Attributes model. For the example above, this is still managable, but you might have 10 props or more in some places.
- passing down blocks from parent into children is a bit cumbersome as well. Not sure if this is fixable
- See twig component of primary button above:
{% set children = block('children') %}
- See twig component of primary button above:
It would be nice if we could improve DX a bit here.
I would really love to make this "just work" and work awesomely :). Do you have any ideas on how you would like the final code to look? To give a better impression, I'd need to do some research into composition on existing systems - components are not new, so build on the ideas of giants :).
Well ... I do love the way react handles this ... :)
interface ButtonProps { /* Not to mention extends ButtonHTMLAttributes<HTMLButtonElement> */
readonly type : string;
readonly chlildren: ReactNode;
}
const PrimaryButton = (props : ButtonProps) => (
<Button className="primary" {...props} />
)
const Button = (props : ButtonProps) => (
<button {...props} />
)
But twig sure ain't no react.
A few possibilities which are more in line with twig maybe:
all() problem
This should be the easiest one to fix: support both array and the twig attributes class as parameter.
Passing down props and blocks
Here are a few ideas
A dedicated syntax
{% component 'button' from this %}
{# this would give the possibility to override blocks. If you do not override a block, it falls back to the one in the child #}
{% block children %}This one is overridden but can {{ parent() }}{% endblock children %}
{% endcomponent %}
I'm not sure about the this in this syntax. it feels natural and might be possible to compose components from other components. E.g.:
{% set peerComponent = component('peer', peerAttrs) %}
{% component 'button' from peerComponent %}{% endcomponent %}
a macro or twig function
{{ composeComponent('button', this) }}
I'm not sure how far you can go with this. Passing down the props sure is possible, but I am not sure at this moment how twig blocks work internally and if you can pass them down like this.
This would pretty much work like a Higher order component in react that reconstructs this (the parent component) back into attributes and passes them down to the child button component
These are just brainfarts at the moment. I can try to think of a few other possibilities if you like. I'm a bit busier the coming week, but maybe I can also try to create a POC out of this if I can find some time.
WDYT?
all() problem
This should be the easiest one to fix: support both array and the twig attributes class as parameter.
That seems reasonable. To repeat what you're referring to for later clarity, you would want the following to be allowed:
{% component 'button' with attributes.defaults({
type: type,
class: 'primary',
}) %}
Currently, you would need to add a .all() on the end to convert the ComponentAttributes into an array.
Passing down props and blocks
This is super helpful. But it'll require some time to bend my mind around it a bit and think what might be possible @kbond might have some thoughts about what might be possible - he did the work for the current {% component syntax, which deals with blocks.
Is this a bit related to #542?
While I did work on the {% component syntax, I don't have a great knowledge of twig internals. I copied most of the code from Twig's {% embed tag and https://github.com/giorgiopogliani/twig-components.
Hello @kbond
I wouldnt say those 2 are very related. Only this would be easier with the PR linked to that issue:
{% set mountedComponent = mount_component('mount_for_change') %}
{% do mountedComponent.setName('Mr. Smith') %}
{% component 'button' from mountedComponent %}{% endcomponent %}
I think the use-case for mount_component is more as in the example of that specific issue. Eg you have a table and want to tell how table cells should be rendered, but want to pass in the data on the moment you render that component.
This should be the easiest one to fix: support both array and the twig attributes class as parameter.
Agreed (but I'll have to dig into twig internals), couple questions:
- If passing attributes directly, what should the parameter name be in the child component? It will already have it's own
attributesparameter, right? Or are you thinking this should override it? - Would you want to pass additional parameters down in addition to attributes?
Related to this issue it would be super cool if we could avoid using {% block %} without multiple slots :) Because its cumbersome to write {% component Super %} {% block ... %} {% endblock %} {% endcomponent %}
I know... I'd love this as well but it's pretty advanced stuff. https://github.com/giorgiopogliani/twig-components has this but I couldn't figure out how to port that code into this library.
Yup, I would LOVE that syntax, in general, with twig components... 😇
1. If passing attributes directly, what should the parameter name be in the child component? It will already have it's own `attributes` parameter, right? Or are you thinking this should override it?
Good question. I don't know... I'dd say: merge(attributes, this.publicProps) and pass that down to the attributes of the child component?
2. Would you want to pass additional parameters down _in addition_ to attributes?
If you choose between component with and component from, then I'dd say that if you want to explicitely specify attributes, you'dd use the with form. If you want to copy it all you use from? I don't see a lot of value in adding other attributes atm. You can always use parent's attributes.with().without() as well.
Yeah, maybe blade's slots principle is better in this specific situation than the block system... I don't mind the twig syntax that much, but the HTML-ish version would indeed be super nice indeed! However, it might complicate things like passing down multiple attributes at once etc.
The idea of an default block was proposed for twig's embed tag - looks like it is complex!
Thank you for this suggestion. There has not been a lot of activity here for a while. Would you still like to see this feature?
Friendly ping? Should this still be open? I will close if I don't hear anything.
Hey,
I didn't hear anything so I'm going to close it. Feel free to comment if this is still relevant, I can always reopen!
Thank you for this suggestion. There has not been a lot of activity here for a while. Would you still like to see this feature? Every feature is developed by the community. Perhaps someone would like to try? You can read how to contribute to get started.
Could I get an answer? If I do not hear anything I will assume this issue is resolved or abandoned. Please get back to me <3
Hey,
I didn't hear anything so I'm going to close it. Feel free to comment if this is still relevant, I can always reopen!
Thank you for this suggestion. There has not been a lot of activity here for a while. Would you still like to see this feature? Every feature is developed by the community. Perhaps someone would like to try? You can read how to contribute to get started.