In my angular application, I have created custom component for accordion, which have accordion-item and accrodion-item-content as its directive.
I am trying to access templateRef to get all required input elements passed within accordionContent directive. but console.log(this.items.get(0)?.content.templateRef.elementRef.nativeElement) gives null value as I have placed console statement inside accordion.component.ts.
Please suggest alternative way if available for such case.
accordion-content.directive.ts
import { Directive, TemplateRef } from "@angular/core";
@Directive({
selector: "[accordionContent]"
})
export class AccordionContent {
constructor(public templateRef: TemplateRef<any>) {
console.log(templateRef.elementRef.nativeElement);
}
}
accordion-item.directive.ts
import { ContentChild, Directive, Input } from "@angular/core";
import { AccordionContent } from "./accordion-content.directive";
@Directive({
selector: "accordion-item"
})
export class AccordionItem {
@Input() title = "";
@Input() disabled = false;
@Input() expanded = false;
@Input() headerTitle = "";
@Input() enableProgressStatus = false;
@Input() progressStatus = "";
@Input() isProgressComplete = false;
@ContentChild(AccordionContent) content!: AccordionContent;
}
accordion.component.ts
@Component({
selector: 'accordion',
templateUrl: './accordion.component.html',
styleUrls: ['./accordion.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
animations: [
trigger('contentExpansion', [
state('expanded', style({ height: '*', opacity: 1, visibility: 'visible' })),
state('collapsed', style({ height: '0px', opacity: 0, visibility: 'hidden' })),
transition('expanded <=> collapsed', animate('200ms cubic-bezier(.37,1.04,.68,.98)')),
]),
],
})
export class AccordionComponent implements AfterViewInit {
expanded = new Set<number>();
@Input() collapsing = true;
@ContentChildren(AccordionItem, { read: AccordionItem, descendants: true }) items!: QueryList<AccordionItem>;
constructor(private readonly cdr: ChangeDetectorRef) {
}
ngAfterViewInit() {
console.log(this.items.get(0)?.content.templateRef.elementRef.nativeElement)
merge(this.items.changes, of(this.items))
.pipe(map(() => this.items.toArray()))
.subscribe((items) => {
items.forEach((item, index) => {
if (item.expanded) {
this.expanded.add(index);
}
});
this.cdr.detectChanges();
});
}
toggleState = (index: number) => {
if (this.expanded.has(index)) {
this.expanded.delete(index);
} else {
if (this.collapsing) {
this.expanded.clear();
}
this.expanded.add(index);
}
};
}
accordion.component.html
<section class="accordion">
<div *ngFor="let item of items;index as i"
class="accordion__item" [class.disabled]="item.disabled" [class.active]="expanded.has(i)">
<div class="accordion__header" (click)="item.disabled ? {} :toggleState(i)">
<mat-icon class="icon">
<span class="material-symbols-outlined">
{{ expanded.has(i) ? "expand_more" : "chevron_right" }}
</span>
</mat-icon>
<p class="title">{{item?.headerTitle}}</p>
<div class="line"></div>
<div class="progress-status" *ngIf="item?.enableProgressStatus==true">{{item?.progressStatus}}</div>
<mat-icon class="icon" [ngClass]="item?.isProgressComplete ? 'completed-color' : 'pending-color'" *ngIf="item?.enableProgressStatus==true">
<span class="material-symbols-outlined">
{{ item?.isProgressComplete ? "check_circle" : "error" }}
</span>
</mat-icon>
</div>
<div class="accordion__content" [class.expanded]="expanded.has(i)" [@contentExpansion]="expanded.has(i) ? 'expanded':'collapsed'">
<ng-container *ngTemplateOutlet="$any(item?.content?.templateRef)"></ng-container>
</div>
</div>
</section>
Here is how I am using my custom component accordion-demo.component.html
<accordion [collapsing]="false">
<accordion-item [expanded]="true" [headerTitle]="'Title 1'"
[enableProgressStatus]="true" [progressStatus]="'0/3 Completed'"
[isProgressComplete]="false">
<ng-template accordionContent>
<div>
This is a <strong>complete custom header</strong> implementation. The whole header section can be customized
to you liking. Toggle method will be exposed which can be used to open/close the section.
<br/>
<br/>
<input matInput
[id]="'action-input'"
[type]="'text'"
[required]="true"></input>
</div>
</ng-template>
</accordion-item>
</accordion>