Content Projection and Custom Structural Directives
Content Projection with ng-content
Content projection lets a parent component inject HTML into a child component's template at designated slots. The child uses <ng-content> as a placeholder. This is the Angular equivalent of React's children prop and is essential for reusable layout components like cards, modals, and tabs.
// card.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-card',
standalone: true,
template: `
<div class="card">
<div class="card-header">
<ng-content select="[slot=header]" />
</div>
<div class="card-body">
<ng-content /> <!-- default slot -->
</div>
<div class="card-footer">
<ng-content select="[slot=footer]" />
</div>
</div>
`
})
export class CardComponent {}// parent usage
import { Component } from '@angular/core';
import { CardComponent } from './card.component';
@Component({
selector: 'app-profile-page',
standalone: true,
imports: [CardComponent],
template: `
<app-card>
<h2 slot="header">Jane Doe</h2>
<p>Full-stack developer from Lagos.</p>
<img src="/jane.jpg" alt="Jane" />
<div slot="footer">
<button>Follow</button>
<button>Message</button>
</div>
</app-card>
`
})
export class ProfilePageComponent {}Custom Structural Directive with TemplateRef
Structural directives manipulate the DOM by adding or removing <ng-template> views. Writing your own requires TemplateRef (the template to stamp) and ViewContainerRef (where to stamp it).
// repeat.directive.ts
import {
Directive, Input, OnChanges,
TemplateRef, ViewContainerRef
} from '@angular/core';
@Directive({
selector: '[appRepeat]',
standalone: true
})
export class RepeatDirective implements OnChanges {
@Input('appRepeat') count = 0;
constructor(
private templateRef: TemplateRef<{ $implicit: number }>,
private viewContainer: ViewContainerRef
) {}
ngOnChanges() {
this.viewContainer.clear();
for (let i = 0; i < this.count; i++) {
this.viewContainer.createEmbeddedView(this.templateRef, { $implicit: i });
}
}
}
// Usage in a template:
// <p *appRepeat="3; let i">Item {{ i + 1 }}</p>The $implicit context key maps to the let variable in the microsyntax (let i). You can expose multiple context properties ({ $implicit: i, last: i === count - 1 }) and bind them with let i; let isLast = last.
Change Detection and OnPush
By default Angular uses Default change detection: it checks every component in the tree on every event. OnPush tells Angular to only check a component when its @Input() references change, an event originates inside it, or an Observable it subscribes to via async pipe emits. This can dramatically reduce unnecessary checks in large trees.
import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
@Component({
selector: 'app-user-card',
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="card">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
</div>
`
})
export class UserCardComponent {
@Input({ required: true }) user!: { name: string; email: string };
}With OnPush, mutating an object or array passed as @Input() will NOT trigger change detection because the reference stays the same. Always pass new object/array references (immutable update pattern) or use signals, which bypass this limitation entirely.