In Angular, how to do Content Projection over the list of children?
Asked Answered
I

2

6

I have a component that should accept arbitrary amount of child components of known type and render them with additional wrapper in an *ngFor. Alternatively, can I pass and render a list of <ng-template>?

Container will be used like this:

<app-custom-container>
   <app-custom-child name="1">...</app-custom-child>
   <app-custom-child name="2">...</app-custom-child>
   <app-custom-child name="3">...</app-custom-child>
</app-custom-container>

Inside container template I'd like to do something like this:

template: '<div>
    <ng-container *ngFor="let child of ...?">
        <app-custom-child-internal-wrapper [id]="child.id">
             <ng-content/> <!--somehow only render this specific child-->
        </app-custom-child-internal-wrapper>
    </ng-content>
</div>'

Can this or something similar be done?

Ingratitude answered 9/3, 2022 at 15:13 Comment(0)
S
6

this is a use case for ContentChildren and ngTemplateOutlet... like so

first you need a directive to mark and get a template ref of any children you want rendered:

@Directive({ selector: '[childMarker]'})
export class ChildMarkerDirective {
  constructor(public templateRef: TemplateRef<any>) { }
}

then add it to the children:

<app-custom-container>
   <app-custom-child *childMarker name="1">...</app-custom-child>
   <app-custom-child *childMarker name="2">...</app-custom-child>
   <app-custom-child *childMarker name="3">...</app-custom-child>
</app-custom-container>

in your custom container controller, you need something like this to query the marker in the content:

@ContentChildren(ChildMarkerDirective)
children

then in your template, you can iterate and project:

<div>
    <ng-container *ngFor="let child of children">
        <app-custom-child-internal-wrapper>
             <ng-container *ngTemplateOutlet="child.templateRef"></ng-container>
        </app-custom-child-internal-wrapper>
    </ng-container>
</div>
Scour answered 11/3, 2022 at 19:32 Comment(2)
I like the idea.. but can't make it working, I got always Error: NG0201: No provider for TemplateRefLao
Needs to use the * syntax on the directive to inject template refScour
I
3

Solved it. You can use CustomChildComponent for template only and get a list if them with @ContentChildren(CustomChildComponent)

Usage:

<app-custom-container>
   <app-custom-child name="1">
       <ng-template #innertemplate>
          ...
       </ng-temlate>
   </app-custom-child>
   <app-custom-child name="2">
       <ng-template #innertemplate>
            ...
       </ng-temlate>
   </app-custom-child>
</app-custom-container>

Child component:

@Component({
  selector:    'app-custom-child',
  template: '', // template empty by design
})
export class CustomChildComponent {
    @Input() public name!: string;
   
    @ContentChild('innertemplate', { read: TemplateRef }) 
    public innerTemplateReference!: TemplateRef<any>; 
}

Parent component:

@Component({
  selector:    'app-custom-container',
  template: '<app-some-internal-container>
       <ng-container *ngFor="let child of contentChildren">
           <app-some-internal-wrapper>
               <ng-container ngTemplateOutlet="child.innerTemplateReference">
                    
               </ng-container>
           </app-some-internal-wrapper>
       </ng-container>
  </app-some-internal-container>', 
})
export class CustomContainerComponent {   
    @ContentChildren(CustomChildComponent) 
    public contentChildren: QueryList<CustomChildComponent>;
}
Ingratitude answered 11/3, 2022 at 19:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.