It is not rendering the @ContentChild in another standalone component.
Added stackblitz
HTML
<div class="card">
<h6 class="card-header">
<ng-container [ngTemplateOutlet]="cardHeader.tpl"></ng-container>
@if (hasActions()) {
<!-- <ng-content select="app-card-header-actions"> </ng-content> -->
}
</h6>
<div class="card-body">
@defer(when cardMainContent.tpl) {
<ng-container [ngTemplateOutlet]="cardMainContent.tpl"></ng-container>
} @loading(minimum 750ms) {
<div class="spinner-border" role="status">
</div>
} @placeholder() {
<div class="my-5">
<div class="alert alert-primary text-center" role="alert">
No Data Found!
</div>
</div>
}
</div>
</div>
TS
@Directive({
selector: '[appCardHeader]',
standalone: true,
})
export class CardHeaderDirective {
constructor(public tpl: TemplateRef<any>) {}
}
@Directive({
selector: 'app-card-header-actions',
standalone: true,
})
export class CardHeaderActionsDirective {}
@Directive({
selector: '[appCardMainContent]',
standalone: true,
})
export class CardContentDirective {
constructor(public tpl: TemplateRef<any>) {}
}
@Component({
selector: 'app-card',
standalone: true,
imports: [CommonModule, CardContentDirective, CardHeaderDirective],
templateUrl: './card.component.html',
styleUrl: '',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CardComponent {
hasActions = input<boolean>(false);
@ContentChild(CardContentDirective) cardMainContent!: CardContentDirective;
@ContentChild(CardHeaderDirective) cardHeader!: CardHeaderDirective;
}
ERROR TypeError: Cannot read properties of undefined (reading 'tpl') at CardComponent_Template (card.component.html:3:19)
Content projection should work and it should display the content from different cards.
We cannot use a directive on ng-template
since it does not fire, ng-template is a virtual element and is not rendered in the DOM, so the better option, is to just create template reference variables like #cardHeader
and #cardMainContent
and access these through ContentChild
and directly render them on the HTML.
@ContentChild('cardMainContent') cardMainContent!: TemplateRef<any>;
@ContentChild('cardHeader') cardHeader!: TemplateRef<any>;
import { CommonModule } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
ContentChild,
Directive,
TemplateRef,
inject,
input,
} from '@angular/core';
@Directive({
selector: 'app-card-header-actions',
standalone: true,
})
export class CardHeaderActionsDirective {}
@Component({
selector: 'app-card',
standalone: true,
imports: [CommonModule],
templateUrl: './card.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CardComponent {
hasActions = input<boolean>(false);
@ContentChild('cardMainContent') cardMainContent!: TemplateRef<any>;
@ContentChild('cardHeader') cardHeader!: TemplateRef<any>;
barChartOptions = {};
}
<div class="card">
<h6 class="card-header">
<ng-container [ngTemplateOutlet]="cardHeader"></ng-container>
@if (hasActions()) {
<ng-content select="app-card-header-actions"> </ng-content>
}
</h6>
<div class="card-body">
@defer(when cardMainContent) {
<ng-container [ngTemplateOutlet]="cardMainContent"></ng-container>
} @loading(minimum 750ms) {
<div class="spinner-border" role="status"></div>
} @placeholder() {
<div class="my-5">
<div class="alert alert-primary text-center" role="alert">
No Data Found!
</div>
</div>
}
</div>
</div>
<mat-grid-list
cols="4"
gutterSize="15px"
rowHeight="375px"
id="dashboard-tiles"
>
<mat-grid-tile [colspan]="1" [rowspan]="1" class="card shadow">
<app-card>
<ng-template #cardHeader>LOREM</ng-template>
<ng-template #cardMainContent>
<ul class="list-group">
<li class="list-group-item btn-link">Item 1</li>
<li class="list-group-item btn-link">Item 2</li>
<li class="list-group-item btn-link">Item 3</li>
</ul>
</ng-template>
</app-card>
</mat-grid-tile>
<mat-grid-tile [colspan]="2" [rowspan]="1" class="card shadow">
<app-card [hasActions]="true">
<ng-template #cardHeader>IPSUM</ng-template>
<ng-template #cardMainContent>
<ng-template
[ngTemplateOutlet]="cardTitle"
[ngTemplateOutletContext]="{ data: resData }"
></ng-template>
<div class="card-body mt-0 p-0" [style.height.px]="400">
<canvas
baseChart
[datasets]="[]"
[labels]="[]"
[options]="barChartOptions"
[plugins]="[]"
[legend]="true"
[type]="'bar'"
>
</canvas>
</div>
</ng-template>
</app-card>
</mat-grid-tile>
</mat-grid-list>
<ng-template #cardTitle let-data="data">
<div class="card-title">
@if (data.length) {
<!-- display-->
} @else {
<!-- some other display-->
}
</div>
</ng-template>