Customizable and re-usable embedded views using NgTemplateOutlet in Angular (Declarative way)
sabrymuhamad
Posted on July 12, 2024
There are different ways to customize the view of angular component, one of the most famous ways is using content projection "ng-content"
but actually I find "ng-content" won't be very flexible for the upcoming example and I really doubt if it would work at all.
Before we jump into a real life example I want to emphasis that we could use the "imperative" way to solve this example as well but I'm afraid that it would be more complicated
than using NgTemplateOutlet
directive "declarative"...
and now to our first example:
- Using Angular material stepper I need to make a customizable stepper header in some cases so I want to conditionally show and hide the default header and this an easy part using
some CSS so I created a class called
hide-stepper-header
.hide-stepper-header {
.mat-horizontal-stepper-header-container {
display: none;
}
}
<div class="tw-mt-10 tw-w-1/2 tw-mx-auto" [ngClass]="{'hide-stepper-header': stepperHeader()}">
<div class="tw-flex tw-mb-4" *ngIf="stepperHeader()">
<ng-container [ngTemplateOutlet]="stepperHeader()!"></ng-container>
</div>
<mat-stepper #stepper [(selectedIndex)]="selectedIndex">
<mat-step>
<ng-template matStepLabel>Fill out your name</ng-template>
<p>Step 1 body</p>
<div>
<button mat-button matStepperNext>Next</button>
</div>
</mat-step>
<mat-step>
<ng-template matStepLabel>Done</ng-template>
<p>You are now done.</p>
<div>
<button mat-button matStepperPrevious>Back</button>
<button mat-button (click)="stepper.reset()">Reset</button>
</div>
</mat-step>
</mat-stepper>
</div>
stepperHeader = input<TemplateRef<any>>();
selectedIndex = model(0);
this class would be triggered dynamically depends on if there is a custom header provided to the stepper or not.
in the above code snippet I created a stepper component which takes a model signal that works like an Input and Output to listen to the active step changes.
and this is the parent component re-using the stepper component in customizable view
export class EmbeddedViewsComponent {
steps = [
{stepTitle:'Step title 1', isActive:false},
{stepTitle:'Step title 2', isActive:true}
];
activeIndex = 0;
}
<app-stepper [stepperHeader]="stepperHeaderRef" [(selectedIndex)]="activeIndex"></app-stepper>
<ng-template #stepperHeaderRef>
<ng-container *ngFor="let step of steps;let i = index;let last = last">
<div class="step"
[ngClass]="{'active':activeIndex === i, 'completed':activeIndex > i, 'pending':activeIndex < i}">
<div class="bullet">
<mat-icon *ngIf="activeIndex > i">check</mat-icon>
<div [ngClass]="{'active':activeIndex === i}"></div>
</div>
<div class="number"><span class="tw-uppercase">{{step.stepTitle}}</span></div>
<div class="tw-w-max" [ngClass]="{'active':activeIndex === i}">
{{activeIndex === i ? 'In Progress' :
activeIndex < i ? 'Pending' : 'Completed' }} </div>
</div>
<div *ngIf="!last" class="step-link"></div>
</ng-container>
</ng-template>
.step-link {
@apply tw-h-[1px] tw-bg-neutral-300 tw-w-full tw-mt-4;
}
.step {
.bullet {
@apply tw-mb-4 tw-flex tw-items-center tw-justify-center tw-bg-green-100 tw-w-[32px] tw-h-[32px] tw-rounded-full;
mat-icon {
@apply tw-text-white;
}
.active {
@apply tw-bg-green-400 tw-w-[12px] tw-h-[12px] tw-rounded-full;
}
}
.number {
@apply tw-text-neutral-600 tw-tracking-widest tw-w-32;
}
&>.active {
@apply tw-text-green-500;
}
&.pending {
@apply tw-opacity-60;
}
&.completed {
.bullet {
@apply tw-bg-green-600;
}
}
}
- here in the parent component I provide number of steps for the header to show and actually I could also provide the steps content to show in our re-usable component
You could also check the src code in my github repo.
Posted on July 12, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 29, 2024