Search code examples
htmlangulartypescriptng-container

Dynamic content creation


I need your help. I have two different components Stage i Executor. Inside the Stage component, I try to create new elements, the number of which depends on the input parameters, and in the Executor component, I set these parameters and output that component. Also, I'm trying, with the help of directives, to create dynamic content for each block created so as to simply enter the text for the finished element. The fact is that all text elements are added to me only at the end, to the last created step, and when the value of the number variable changes, it all disappears. Please help to create separate content for each separate block. Thank you very much

stage.component.ts

export class StageComponent {

  @Input() numberOfSteps: number;
  @Input() number: number;

  get stepsArray(): number[] {
    return [...Array(this.numberOfSteps).keys()].map(i => i + 1);
  }
}

stage.component.html

<div class="step" *ngFor="let step of stepsArray; let i = index;">
<ng-container *ngIf="step > number; else done">
    <svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
        <rect [class.completed]="step <= number" width="36" height="36" rx="18" fill="#F3F3F3"/>
        <text class="body-3" x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" font-size="16" fill="black">{{ step }}</text>
    </svg>
    <ng-content select="[stepDescription]"></ng-content>
</ng-container>
</div>

<ng-template #done>
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
    <rect x="0.5" y="0.5" width="35" height="35" rx="17.5" stroke="#929292"/>
    <path d="M24.7373 10L14.5466 20.3769L11.2415 17.0114L8 20.3122L11.3051 23.6777L14.5678 27L17.8093 23.6992L28 13.3224L24.7373 10Z"
          fill="#28B446"/>
</svg>
</ng-template>

executor.component.html

<stage [numberOfSteps]="6" [number]="number">
   <div stepDescription> content for one </div>
   <div stepDescription> content for two </div>
   <div stepDescription> content for three </div>
   <div stepDescription> content for four </div>
   <div stepDescription> content for five </div>
   <div stepDescription> content for six </div>
</stage>

Solution

  • ng-content can't be used inside a structural directive like ngFor. You have to do the following steps to accomplish what you want.

    Create a Directive

    step-description.directive.ts

    @Directive({
      selector: '[StepDescription]'
    })
    export class StepDescriptionDirective{
    
      @Input() StepDescription: number = 0;
    
      constructor(public templateRef: TemplateRef<number>) { }
    }
    

    executor.component.html

    <stage [numberOfSteps]="6" [(number)]="currentExecutorStep">
        <div *StepDescription="1">content for one</div>
        <div *StepDescription="2">content for two</div>
        <div *StepDescription="3">content for three</div>
        <div *StepDescription="4">content for four</div>
        <div *StepDescription="5">content for five</div>
        <div *StepDescription="6">content for six</div>
    </stage>
    

    executor.component.ts

    export class ExecutorComponent{
    
      currentExecutorStep: number = 1;
      constructor() { }
    }
    

    stage.component.ts

    <div class="step" *ngFor="let step of stepsArray; let i = index;">
        <div *ngIf="step > number; else done">
            <svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
                <rect [class.completed]="step <= number" width="36" height="36" rx="18" fill="#F3F3F3"/>
                <text class="body-3" x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" font-size="16" fill="black">{{ step }}</text>
            </svg>
            <ng-container *ngFor="let description of stepDescriptions">
                <ng-template *ngIf="description.StepDescription === step" [ngTemplateOutlet]="description.templateRef" ></ng-template>
            </ng-container>
        </div>
        <ng-template #done>
            <svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
                <rect x="0.5" y="0.5" width="35" height="35" rx="17.5" stroke="#929292"/>
                <path d="M24.7373 10L14.5466 20.3769L11.2415 17.0114L8 20.3122L11.3051 23.6777L14.5678 27L17.8093 23.6992L28 13.3224L24.7373 10Z"
                      fill="#28B446"/>
            </svg>
        </ng-template>
    </div>
    

    stage.component.ts

    @Input() numberOfSteps ?: number;
      @Input() number!: number;
      @Output() numberChange: EventEmitter<number> = new EventEmitter<number>();
    
      @ContentChildren(StepDescriptionDirective) stepDescriptionDirs !: QueryList<StepDescriptionDirective>;
      stepDescriptions: StepDescriptionDirective[] = [];
      
      get stepsArray(): number[] {
        return [...Array(this.numberOfSteps).keys()].map(i => i + 1);
      }
      constructor() { }
    
      ngAfterContentInit(): void {
        this.stepDescriptions = this.stepDescriptionDirs.toArray();
      }