emberjs / rfcs

RFCs for changes to Ember
https://rfcs.emberjs.com/
793 stars 408 forks source link

Syntax/Feature Need: Passable Blocks (as args / other?) #791

Open NullVoxPopuli opened 2 years ago

NullVoxPopuli commented 2 years ago

context: https://github.com/emberjs/rfcs/pull/460#issuecomment-902961346

The lack of this capability is a HUGE growing pain for the projects I'm working on and causes a lot of cruft and awkward APIs, and having this feature would simplify a ton of things.

For library / ui / design-system authors, having passable blocks would be an IMMENSE lift / relief -- and it feels very surprising that this wasn't talked about more during the original RFC (or maybe it was planned to be added later, and just no one has gotten around to it)

Related thoughts:

<SubComponent @myBlock={{:myBlock}}>
<:@myBlock>

</:@myBlock>

Example:

the first place this came up was our big table component wrapped with all the bells and whistles. Here is a more generic, yet very related, example.

A table component

A wrapper component cannot

Like,

<WrappedTable>
  <:header>
     <th></th>
  </:header>
</WrappedTable>
and
<WrappedTable />

are not possible APIs.

You can hack around this, by, in the implementation of WrappedTable, put an if block around the entirety of Table, but that's 1. terrible, and 2. does not scale past 1 named block.

I have 7 named blocks in Table component

In order to provide a wrapper table (with nice defaults, optional blocks, etc), I cannot fathom (other than actually doing the math) the amount of code it would take to if-stamement my way through implementing that.

wagenet commented 2 years ago

@locks status?

NullVoxPopuli commented 1 year ago

Another potential syntax:

<SubComponent @:myBlock={{:myBlock}} />

This implies:

Alternatively (or maybe in addition to, because this is about to be way more typing than the above, we could allow control-flow syntaxes outisde named-block space.

For example:

{{! the contents of "MyDemo" component }}
<ListItem @list={{@list}}>
  {{#if (has-block "media")}}
    <:media>
      {{yield to="media"}}
    </:media>
  {{/if}}
</ListItem>

e.g.: if <MyDemo> is rendered with a <:media> block, pass those contents to <ListItem>, otherwise render <ListItem> as if no :media block were used.

sukima commented 1 week ago

I just ran into this today. I needed a way to use Ember/Glimmer components to render dynamic content that is generated from a nested data structure. By nature of nested data structures we needed recursion which is supported but the parent's block(s) were not transferable to the recursive children and so they became no-ops making the system only able to handle one level of nesting in the data.

Example

data = [
  { type: ‘text’, value: ‘This is a ‘ },
  {
    type: ‘link’,
    children: [
      { type: ‘text’, value: ‘fancy link ‘ },
      { type: ‘icon’ },
    ],
  },
  { type: ‘text’, value: ‘ to render.’ },
];

With something like this:

<MyFancyComponent @nodes={{this.data}} as |type Content|>
  {{#if (eq type "link")}}
    <MyLink><Content /></MyLink>
  {{else if (eq type "icon")}}
    <MyIcon />
  {{/if}}
</MyFancyComponent>

But if you notice the data structure has nested nodes under children so when attempting to recurse you would normally have this:

{{yield
  currentNode.type
  (component MyFancyComponent nodes=currentNode.children)
}}

Which is unable to pass along the parent's yielded block thus rendering any child nodes rendering useless.