Angular conditional content project

49 Views Asked by At

I'm using Angular 16.

I have to create a component to render the multi-stepper components and the component usage will be like

<!- app.component.html -->

<app-multi-step>
  <app-multi-step-panel title="Basic">
    <ng-template panelContent>
       <div>Panel content</div>
    </ng-template>
    <ng-template panelFooter>
      <button (click)="handleNext()">Next</button>
    </ng-template>
  </app-multi-step-panel>

  <app-multi-step-panel title="Settings">
    <ng-template panelContent>
       <div>Settings content</div>
    </ng-template>
    <ng-template panelFooter>
      <button (click)="handleNext()">Next</button>
    </ng-template>
  </app-multi-step-panel>
</app-multi-step>

Where the app-multi-step can have n-number of app-multi-step-panel which can have two directives panelContent to render contents and panelFooter to render footer content.

To achieve this, I created a component multi-step

# multi-step.component.ts

@Component({
  selector: 'app-multi-step',
  templateUrl: './multi-step.component.html',
  styleUrls: ['./multi-step.component.scss']
})
export class MultiStepComponent implements AfterContentInit {

  @ContentChildren(MultiStepPanelComponent) panels: QueryList<MultiStepPanelComponent>;

  panelList: Array<string> = [];

  projectedIndex = 0;

  ngAfterContentInit() {
    this.panelList = this.panels.map(p => p.title);
  }

  setProjection(index) {
    this.projectedIndex = index;
  }
}

and the HTML content

<!-- multi-step.component.html -->

<div class="card">
  <div class="card-body">
    <div class="multisteps-form__progress">
      <button [disabled]="i > projectedIndex" (click)="setProjection(i)" class="multisteps-form__progress-btn" [class.js-active]="i <= projectedIndex" type="button" title="{{panelTitle}}" *ngFor="let panelTitle of panelList; let i=index">
        {{panelTitle}}
      </button>
    </div>
  </div>
</div>

<div class="row">
  <div class="col-12 col-lg-8 m-auto">
    <ng-content select="app-multi-step-panel"></ng-content>
  </div>
</div>

This is working as expected, except I want to render one app-multi-select-panel at once and change the component based on the projectIndex value change.

How can I achieve it using the above setup? Any other approach suggestion is welcome as well.

2

There are 2 best solutions below

0
Skeptic Monk On

Have a flag called "selected" in "app-multi-step-panel" component, the component will be displayed only if the selected is true. In the "app-multi-step" component, make the selected flag of "app-multi-step-panel" in first index to be true. Likewise, have a UI in "app-multi-step" to change the index

0
Eliseo On

You can simply use a variable "selectedIndex", then each button you change the value of the variable

<button *ngFor="let bt of panelList;let i=index" 
       (click)="select(i)">pannels</button>

@ViewChild(MultiStepComponent) pannelList:QueryList<MultiStepComponent>

select(index:number)
{
     this.selectedIndex=index
}

Then you can use style.display none or null depending the variable

<app-multi-step-panel title="Basic" 
     [style.display]="selectedIndex!=0?'none':null">
<app-multi-step-panel title="Settings" 
     [style.display]="selectedIndex!=1?'none':null">
...

But really, force to write style.display is an "ugly patchwork". Why not make that all was more scalable?

For this, first we are going to inject in constructor of the app-multi-step-panel the app-multi-step using Host and add a property index to our app-multi-step-panel

index:number=-1;
constructor(@Host() private parent:MultiStepComponent ){}

Then we can use HostBinding to give "style.display:none"

@HostBinding('style.display') get display() { 
   return !this.parent || this.parent.selectedIndex!=this.index ? 'none': null; }

The last piece of the jigsaw if give value of the "index" to our app-multi-step-panel from the app-multi-step

ngAfterViewInit()
{
    this.panelList.forEach((x:any,index:number)=>x.index=index)
}

a stackblitz