Using {{yield}} for multiple nested components

If a component has two or three levels of descendant components, it can get a little annoying when having to pass down a parameter that is only needed by the last descendant all the way through the chain.

Using the {{yield}} helper can eliminate some of the cruft by allowing me to declare the nested component within the scope of the parent container. The only problem I am having now is that I cannot have multiple yield ‘outlets’ … If I wanted to nest multiple components in different parts of the containing component’s template I would be unable to (AFAIK).

The new contextual component feature in 2.3 seems like it might make things easier with the (hash and (component helpers but I am still limited by only having one {{yield}} “outlet” in a template. Does anyone have advice for this predicament and or a better way of seeing things? Thanks in advance.

Well, you can do 2 with {{else}} and {{yield inverse=true}} :smile:

Probably want to take a closer look at how Ember work with {{else if ...}}

Apologies for resurrecting this thread but things have changed significantly since.

It is possible now to yield different sections in a single component. Your wrapping component will yield a specially crafted hash which references a dummy component.

For example the dummy component foo-content:

{{yield}}

And an example wrapper component foo-wrapper:

<div class="foo-wrapper">
  <div class="foo-wrapper__foo-a">
    {{yield (hash a=(component "foo-content"))}}
  </div>
  <div class="foo-wrapper__foo-b">
    {{yield (hash b=(component "foo-content"))}}
  </div>
  <div class="foo-wrapper__foo-c">
    {{yield (hash c=(component "foo-content"))}}
  </div>
</div>

Would provide you (the consumer) with this syntax:

{{#foo-wrapper as |foo|}}
  {{#foo.a}}This is the Foo A component{{/foo.a}}
  {{#foo.b}}This is the Foo B component{{/foo.b}}
  {{#foo.c}}This is the Foo C component{{/foo.c}}
{{/foo-wrapper}}

Here is a working demo.

6 Likes

Has this been documented somewhere? The multiple yield values get merged into one object? I guess there is no way to know when one of the sub components received content?

Has this been documented somewhere?

I don’t think the guides discuss this but based on how contextual components work this is allowed by modern Ember. There have been blog posts and talks describing contextual components and this is just another (although unique) way of using them.

The multiple yield values get merged into one object?

I don’t understand what you mean here nor how the code above resulted in that conclusion.

I guess there is no way to know when one of the sub components received content?

Again I am not understanding what your asking here. Perhaps I don’t know your mental model of the contextual components to extrapolate meaning in this case. Sorry.

For clarity, The code sample I gave basically has three yields. In normal components this would result in the content being yielded to render three times:

{{! my-component/template.hbs }}
{{yield}}
{{yield}}
{{yield}}

{{! my-controller/template.hbs }}
{{#my-component}}
  <p>foo</p>
{{/my-component}}

{{!  Would result in: }}
  <p>foo</p>
  <p>foo</p>
  <p>foo</p>

In the example the content is wrapped in components provided by the contextual component. In each instance of a yield a hash is provided with a unique key. In truth all three yields render the whole context except that if the key is undefined Ember renders nothing. Which means each yield will result in only one set of content rendered.

One could argue this is a hack. But it works and does not stray from the intent of contextual components. For contrast given the following:

{{#my-component as |foo|}}
  <p>{{#foo.b}}foo{{/foo.b}}</p>
{{/my-component}}

would result in this output:

<div>
  <p><!----></p>
  <p>foo</p>
  <p><!----></p>
</div>

Hopefully that clears up some confusion.

2 Likes

This question has also be asked in Stackoverflow.

Now, there is an accepted RFC for this problem; the Named Templates Block API will support passing multiple blocks to a component.

I also end up using Contextual components and created a similar working demo that allows using the default subcomponent content or overwrites it.

{{my-component}}



{{#my-component as |f|}}
  {{f.header}}
  {{#f.content}}
    Custom content
  {{/f.content}}
  {{f.footer}}
{{/my-component}}


{{#my-component as |f|}}
  {{#f.header}}
     Custom header
  {{/f.header}}
  {{#f.content}}
     Custom content
  {{/f.content}}
  {{#f.footer}}
     Custom footer
  {{/f.footer}}
{{/my-component}}