Angular 16+ conditionally render one of several templates using ngTemplateOutlet

242 views Asked by At

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.

2

There are 2 answers

1
Naren Murali On BEST ANSWER

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

0
PVG On

Naren Murali's solution worked like a charm in stackblitz and in my local test app. However, for it to work on my main app, I had to add change detection inside ngAfterViewInit.

  cd = inject(ChangeDetectorRef);

  ngAfterViewInit() {
    this.selectTemplate();
    this.cd.detectChanges();
  }

stackblitz