angularng-templateng-containerngtemplateoutlet

Angular 16+ conditionally render one of several templates using ngTemplateOutlet


I have 3 templates inside tempaltes.component. Inside item.component I need to pick one of the templates conditionally, and pass it down to the header.component to render, but somethingnot right with my code, please help.

Here is the complete code stackblitz

tempaltes.component.html

<ng-template #templateA let-item>
  <p>{{ item.name }} Template A content here...</p>
</ng-template>

<ng-template #templateB let-item>
  <p>{{ item.name }} Template B content here...</p>
</ng-template>

<ng-template #templateC let-item>
  <p>{{ item.name }} Template C content here...</p>
</ng-template>

tempaltes.component.ts

@Component({
  selector: 'app-templates',
  templateUrl: './templates.component.html',
  standalone: true,
  imports: [],
})
export class TemplatesComponent {
  @ViewChild('templateA') templateA!: TemplateRef<any>;
  @ViewChild('templateB') templateB!: TemplateRef<any>;
  @ViewChild('templateC') templateC!: TemplateRef<any>;
}

item.component.html

<app-header [headerTemplate]="selectedTemplate" [item]="item" />

item.component.ts

@Component({
  selector: 'app-item',
  templateUrl: './item.component.html',
  standalone: true,
  imports: [CommonModule, HeaderComponent],
  providers: [TemplatesComponent],
})
export class ItemComponent {
  templatesComponent = inject(TemplatesComponent);

  @Input() item!: Item;
  selectedTemplate: TemplateRef<any> | null = null;

  ngOnInit() {
    if (this.item.type) {
      this.selectTemplate();
    }
  }

  selectTemplate(): void {
    if (this.item.type === 'type-a') {
      this.selectedTemplate = this.templatesComponent.templateA;
    } else if (this.item.type === 'type-b') {
      this.selectedTemplate = this.templatesComponent.templateB;
    } else if (this.item.type === 'type-c') {
      this.selectedTemplate = this.templatesComponent.templateC;
    }
  }
}

header.component.html

<ng-container *ngTemplateOutlet="headerTemplate; context: { $implicit: item }"></ng-container>

header.component.ts


@Component({
  selector: 'app-header',
  templateUrl: './header.component.html',
  standalone: true,
  imports: [CommonModule],
})
export class HeaderComponent {
  @Input() headerTemplate: TemplateRef<any> | null = null;
  @Input() item!: Item;
}

I believe the issue is within tempaltes.component.ts and/or item.component.ts.


Solution

  • You cant inject a component and access the view like that (in your code the template component is empty), you need to have it in the html of the component and access with view child, please find below a working example, I also changed to ngAfterViewInit since template will always be present, if you do in ngOnInit the view will be un-initialized and will give you null.

    html

    <p>{{ item.id }} - {{ item.name }} - {{ item.email }}</p>
    <app-header [headerTemplate]="selectedTemplate" [item]="item" />
    <app-templates #templates></app-templates>
    

    ts

    import { CommonModule } from '@angular/common';
    import {
      Component,
      Input,
      TemplateRef,
      inject,
      ViewChild,
    } from '@angular/core';
    import { Item } from '../services/data.service';
    import { HeaderComponent } from '../header/header.component';
    import { TemplatesComponent } from '../templates/templates.component';
    
    @Component({
      selector: 'app-item',
      templateUrl: './item.component.html',
      standalone: true,
      imports: [CommonModule, HeaderComponent, TemplatesComponent],
      providers: [TemplatesComponent],
    })
    export class ItemComponent {
      @ViewChild(TemplatesComponent) templatesComponent: TemplatesComponent;
    
      @Input() item!: Item;
      selectedTemplate: TemplateRef<any> | null = null;
    
      ngAfterViewInit() {
        if (this.item.type) {
          this.selectTemplate();
        }
      }
    
      selectTemplate(): void {
        console.log(this.templatesComponent);
        if (this.item.type === 'type-a') {
          this.selectedTemplate = this.templatesComponent.templateA;
        } else if (this.item.type === 'type-b') {
          this.selectedTemplate = this.templatesComponent.templateB;
        } else if (this.item.type === 'type-c') {
          this.selectedTemplate = this.templatesComponent.templateC;
        }
      }
    }
    

    stackblitz