4

I'm building a stepper and am using "transclusion" with ng-content to dynamically grab section elements within the stepper tag. The stepper.component view works like this:

<ng-content select=".one"></ng-content>
<ng-content select=".two"></ng-content>
<ng-content select=".three"></ng-content>

Component usage looks like this:

<stepper>
    <section class="one">content here<section>
    <section class="two">content here<section>
    <section class="three">content here<section>
</stepper>

However, I'd like to make it dynamic by recognizing the section elements automatically:

<ng-content *ngFor="let section of sections; index as i;" select="['section:nth-child(' + i + ')']"></ng-content>

How can I:

  1. get a nodeList of the section elements available to transclude?
  2. use ng-content's select to target them incrementally?
Ben Racicot
  • 5,332
  • 12
  • 66
  • 130

1 Answers1

5

I would create a directive like:

@Directive({
  selector: '[stepper-section]'
})
export class StepperSectionDirective {}

then add stepper-section attribute to each of sections:

<stepper>
    <section stepper-section>content here<section>
    <section stepper-section>content here<section>
    <section stepper-section>content here<section>
</stepper>

and finally make use of @ContentChildren decorator to query all the sections:

@ContentChildren(StepperSectionDirective) sections: QueryList<StepperSectionDirective>;

Ng-run Example

If you want to loop through content and render it dynamically you can wrap your children with ng-template and use ngTemplateOutlet directive to render them in StepperComponent:

html

<app-stepper>
  <ng-template stepper-section>Content 1</ng-template>
  <ng-template stepper-section>Content 2</ng-template>
  <ng-template stepper-section>Content 3</ng-template>
</app-stepper>

stepper-section.directive.ts

@Directive({
  selector: '[stepper-section]'
})
export class StepperSectionDirective {
  hidden = false;

  constructor(public templateRef: TemplateRef<any>) {}
}

stepper.component.ts

@ContentChildren(StepperSectionDirective) sectionDirs: QueryList<StepperSectionDirective>;

stepper.component.html

<button *ngFor="let section of sections; index as i;"
       [class.enabled]="activeSection === i" (click)="goTo(i)">
  Step {{ i + 1 }}
</button>
<div class="content">
  <ng-container *ngFor="let section of sections">
    <ng-template *ngIf="!section.hidden" 
         [ngTemplateOutlet]="section.templateRef"></ng-template>
  </ng-container>
</div>

Ng-run Example

The difference between these two approaches is that in the first case all child content gets rendered and we only manipulate display: none to those steps we want to hide.

In the second approach we have more control on what we want to render and we render only one step at specific time.

yurzui
  • 205,937
  • 32
  • 433
  • 399
  • 1
    Hey @yurzui! It already works that way actually! `@ViewChild('container', { static: false }) container: ElementRef;` but I'm trying to get away from naming each class incrementally. I just want to dynamically pull them in and display them. Is this possible? – Ben Racicot Dec 17 '19 at 19:58
  • You do not need to name each class – yurzui Dec 17 '19 at 19:59
  • Hmm I think I follow. Directive's are required so that ContentChildren can access them and pull them into a QueryList? Without the directive it cannot? (can I make stepper-section components instead?) – Ben Racicot Dec 17 '19 at 20:04
  • 1
    Yes, you can use component – yurzui Dec 17 '19 at 20:09
  • Thanks Yurzui. The issue is looping through them with ng-content. Would you be able to update your answer with how that looks within the stepper component? – Ben Racicot Dec 17 '19 at 20:14
  • Yes! It has completely cleared up how to approach this. With one ng-content. The `@ContentChildren` concept is working out great as well for access. Thank you. – Ben Racicot Dec 17 '19 at 21:53
  • Hey @yurzui I think you may have changed the answer to use ng-template and placed the stepper-section on each one? I'm using one ng-content to get the content within `...` but ContentChildren has no results. Do you know why? – Ben Racicot Dec 18 '19 at 00:41
  • Can you reproduce it somewhere? – yurzui Dec 18 '19 at 02:41