Search code examples
angularangular-directiveangular-componentsangular-pipeangular-renderer2

Showing Title string/templateRef Angular


I am trying to improve my code rather than having conditions, so I decided to create a directive or maybe a pipe if possible that could help me print the title of a tab depending on its type (string or templateRef), my code is the following, this code is used in my Tabs/Tab component, but also in my Stepper/step component, so I believe it would be great to create something reusable. I've tried doing it with ElementRef, Renderer2, ViewContainerRef, TemplateRef... but I was unsuccessful.

<ng-container *ngIf="tab.isLabelTemplate">
     <ng-container *ngTemplateOutlet="tab.title">
     </ng-container>
</ng-container>
<ng-container *ngIf="!tab.isLabelTemplate">{{ tab.title }}</ng-container>

The isLabelTemplate looks like this:

get isLabelTemplate(): boolean {
  return this.title instanceof TemplateRef;
}

Thanks a lot :)


Solution

  • You can leverage Angular low-level API to dynamically manipulate DOM's structure:

    Here's an example of how that directive can look like:

    title-template.directive.ts

    import { Directive, TemplateRef, Renderer2, ViewContainerRef, Input } from '@angular/core';
    
    @Directive({
      selector: '[titleTemplate]',
    })
    export class TitleTemplateDirective {
      @Input()
      set titleTemplate(value: string | TemplateRef<any>) {
        this.updateView(value);
      }
    
      textNode: Text;
    
      constructor(private vcRef: ViewContainerRef, private renderer: Renderer2) {}
    
      private updateView(value: string | TemplateRef<any>) {
        this.clear();
    
        if (!value) {
          return;
        }
    
        if (value instanceof TemplateRef) {
          this.vcRef.createEmbeddedView(value);
        } else {
          this.textNode = this.renderer.createText(value);
          const elem = this.vcRef.element.nativeElement;
    
          this.renderer.insertBefore(elem.parentNode, this.textNode, elem);
        }
      }
    
      private clear() {
        this.vcRef.clear();
        if (this.textNode) {
          this.renderer.removeChild(this.textNode.parentNode, this.textNode);
        }
      }
    
      ngOnDestroy() {
        this.clear();
      }
    }
    

    Usage:

    <ng-container [titleTemplate]="title"></ng-container>  
    

    Forked Stackblitz