How to access html elelment attribute from ng-template passed into container?

26 views Asked by At

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>

0

There are 0 answers