Multiple ng-content
Asked Answered
M

6

204

I am trying to build a custom component using multiple ng-content in Angular 6, but this is not working and I have no idea why.

This is my component code:

<div class="header-css-class">
    <ng-content select="#header"></ng-content>
</div>
<div class="body-css-class">
    <ng-content select="#body"></ng-content>
</div>

I am trying to use this component in another place and render two different HTML code inside body and header select of ng-content, something like this:

<div #header>This should be rendered in header selection of ng-content</div>
<div #body>This should be rendered in body selection of ng-content</div>

But the component is rendering blank.

Do you guys know what I could be doing wrong or what is the best way to render two different sections in same component?

Thanks!

Manifestation answered 4/10, 2018 at 3:49 Comment(1)
Sorry, stackoverflow did not save my second code snippet: The code that I am using in component is something like this: <div #header>This is header content</div> <div #body>This is body content</div>Manifestation
G
371
  1. You could add dummy attributes header and body as opposed to template references (#header, #body).
  2. And transclude using ng-content with select attribute like select="[header]".

app.comp.html

<app-child>
    <div header >This should be rendered in header selection of ng-content</div>
    <div body >This should be rendered in body selection of ng-content</div>
</app-child>

child.comp.html

<div class="header-css-class">
    <ng-content select="[header]"></ng-content>
</div>
<div class="body-css-class">
    <ng-content select="[body]"></ng-content>
</div>

DEMO

Gena answered 4/10, 2018 at 11:30 Comment(5)
If you don't want to render additional div or any other tag you should use <ng-container>Aintab
@AmitChigadani I believe @Aintab meant that you can replace <div header> with <ng-container header>.Behan
I confirm replacing <div header> with <ng-container header> works too.Tel
For anyone wanting more details, this is called multi-slot content project (as opposed to single-slot). Angular docs have some good examples: angular.io/guide/content-projectionRadcliff
and how can we get the @ContentChild nativeElement ref from header or body?Molton
A
111

To fit the Web Component specs. Even if that's Angular. It's about avoiding attributes for selector like Angular directives or reserved attributes with another use. So, we just use the "slot" attribute. We'll see <ng-content select="[slot=foobar]"> as <slot name="foobar">.

Example:

hello-world.component.html

<ng-content select="[slot=start]"></ng-content>
<span>Hello World</span>
<ng-content select="[slot=end]"></ng-content>

app.component.html

<app-hello-world>
  <span slot="start">This is a </span>
  <span slot="end"> demo.</span>
</app-hello-world>

Result

This is a Hello World demo.

Stackblitz Example

You can use any name you want like "banana" or "fish". But "start" and "end" are a good convention to place elements before and after.

Antihelix answered 23/1, 2020 at 14:11 Comment(6)
How can I query those elements ? with name slot.Desk
It depends on your Angular and component settings and what you exactly want. You can use ViewChild in TS or :host and ::ng-deep in SCSS. But this is just an example. See Stackblitz Maybe ::slotted / ::content will also work. But not sure. The web will offer more about this topic. Generally you should only style the component itself. And avoid styling stuff outside (global). Otherwise you'll have unwanted side effects.Antihelix
A good practise is to wrap it. See the updated Stackblitz example in my last comment. See the html and css file of the component. You should prefer this over ng-deep. E.g. <div class="end"><ng-content></ng-content></div> Because this element is accessable in the component. The ng-content is just a pseudo element which is replaced by the docked element outside. So you have to use ng-deep selector.Antihelix
@Dominik Let's say I need to know if the "start" slot has content, or has been defined. Is that doable?Pile
@Pile This is not related to ng-content etc. So you can use any solution for your issue. I'm not sure what you want to achieve. Check the DOM (dev tools). ng-content is a pseudo element (not available on runtime). It's a placeholder, replaced by the element who uses the select attr. So, if you don't provide an element to this slot, the element is not set in your component. Means e.g. if you want to check if "start" slot has content, you just need to check if an element with "slot=start" exists in you component. Or wrap the ng-content in a div and count the childs (e.g. directive).Antihelix
@Pile See example. It's a directive to check if element has childs. ... But I would suggest thinking again about whether you really need it or whether there is another solution. :)Antihelix
O
18

alternatively you can use:

app.comp.html

<app-child>
    <div role="header">This should be rendered in header selection of ng-content</div>
    <div role="body">This should be rendered in body selection of ng-content</div>
</app-child>

child.comp.html

<div class="header-css-class">
    <ng-content select="div[role=header]"></ng-content>
</div>
<div class="body-css-class">
    <ng-content select="div[role=body]"></ng-content>
</div>
Organogenesis answered 26/6, 2019 at 8:14 Comment(0)
C
14

Complementing the other answers:

You can also do it with custom tags (like <ion-card>, <ion-card-header>, and <ion-card-content>).

app.comp.html

<app-child>
    <app-child-header>This should be rendered in header selection of ng-content</app-child-header>
    <app-child-content>This should be rendered in content selection of ng-content</app-child-content>
</app-child>

child.comp.html

<div class="header-css-class">
    <ng-content select="app-child-header"></ng-content>
</div>
<div class="content-css-class">
    <ng-content select="app-child-content"></ng-content>
</div>

You'll get a warning message, but it will work. You can suppress the warning messages or use known tags such as header or footer. However, if you don't like any of these methods, you should go with one of the other solutions.

Carce answered 24/6, 2020 at 23:2 Comment(2)
Do app-child-header and app-child-content have to be defined angular components. Or is it enough to just reference those names in the ng-content select attribute?Heartstrings
@KenHadden The select attribute will accept any CSS selector of the element that will be projected into the ng-content element. So, you can use it as @Dominik mentioned in this answer, as I mentioned above or with any native HTML elements such as div or spam, for example. You can actually use any CSS selector of the element that will be nested to app-child element as mentioned in the ng-content docsTeshatesla
C
12

as another option you can pass templates to the child component, and then you would have the benefit of being able to bind values to the content / templates

parent component html

<app-child
    [templateHeader]="header"
    [templateContent]="content">
</app-child>

<ng-template #header
     let-data="data"> < -- how you get dynamic data
     what ever you would like the header to say
     {{data}}
</ng-template>

<ng-template #content>
    what ever you would like the content to say or any other component
</ng-template>

child component ts

export class ChildComponent {
    @Input() templateHeader: TemplateRef<any>;
    @Input() templateContent: TemplateRef<any>;
}

child component html

<div class="header-css-class">
    <ng-container
        *ngTemplateOutlet="
        templateHeader;
        context: {   , < -- if you want to pass data to your template
            data: data
        }">
    </ng-container>
</div>
<div class="content-css-class">
    <ng-container *ngTemplateOutlet="templateContent">
    </ng-container>
</div>

for a more complete explanations of templates see this great article https://indepth.dev/posts/1405/ngtemplateoutlet

Comparative answered 27/1, 2021 at 19:0 Comment(1)
This is great info, I was looking into how to instantiate multiple times the same <ng-content> and found this answer, so instead I'm making a template and passing it as a TemplateRef. Thanks.Lamplighter
S
5

if you just want to "accept" more than one component, you can use:

<ng-content select="custom-component,a"></ng-content>

This accepts elements of custom-component as well as anchor (a) elements and does not change the sequence.

Shushan answered 16/4, 2021 at 10:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.