This episode covers directives and pipes: built-in directives such as *ngIf, *ngFor, ngClass, and ngStyle along with the modern @if and @for control flow, how to create custom directives and custom pipes, and using built-in pipes to format display data.

Components give structure, but most of the "life" in a template comes from directives and pipes. Directives change the appearance or behavior of elements, while pipes format data before it's displayed.
Episode 5 covers built-in directives such as *ngIf, *ngFor, ngClass, and ngStyle, the modern @if and @for control flow, creating custom directives and custom pipes, and the built-in pipes you'll use most often. After this, your templates will be far more expressive.
Classically, conditionals and loops use structural directives with an asterisk:
<li *ngFor="let produk of daftarProduk; trackBy: trackById">
{{ produk.nama }}
</li>
<div *ngIf="sedangMuat; else kosong">Memuat data...</div>
<ng-template #kosong>Belum ada data</ng-template>*ngFor loops over a list, while *ngIf displays an element only when its condition is true. trackBy helps Angular identify the same items so re-rendering is more efficient.
Since Angular 17, there's a new control flow syntax that's more concise, performs better, and doesn't require imports:
@for (produk of daftarProduk; track produk.id) {
<p>{{ produk.nama }} - Rp {{ produk.harga }}</p>
} @empty {
<p>Keranjang masih kosong</p>
}
@if (sedangMuat) {
<p>Memuat data...</p>
} @else {
<button (click)="muatData()">Muat Data</button>
}@for ... track produk.id replaces *ngFor with trackBy, and the @empty block handles the empty-list case without an extra <ng-template>. This is the recommended way for all new projects.
*ngIf and *ngFor. Marked with an asterisk.ngClass and ngStyle.<div [ngClass]="{ aktif: terpilih, error: gagal }">Status</div>
<div [ngStyle]="{ backgroundColor: terpilih ? 'green' : 'gray' }">Warna</div>[ngClass] accepts a class object with conditions, while [ngStyle] binds style values directly. Both remain useful even now that modern control flow is available.
A directive is created with @Directive. Here's an attribute directive that adds a highlight when an element is hovered:
import { Directive, ElementRef, HostListener, input } from '@angular/core';
@Directive({
selector: '[appHighlight]',
standalone: true,
})
export class HighlightDirective {
readonly appHighlight = input<string>('yellow');
constructor(private el: ElementRef) {}
@HostListener('mouseenter') onMouseEnter(): void {
this.el.nativeElement.style.backgroundColor = this.appHighlight();
}
@HostListener('mouseleave') onMouseLeave(): void {
this.el.nativeElement.style.backgroundColor = '';
}
}The selector [appHighlight] means the directive is used as an attribute: <p appHighlight="lightblue">teks</p>. @HostListener listens for events from the host element. A standalone directive can be used directly without an NgModule.
Pipes format data in templates with the | operator. The built-in pipes you'll use most often:
<p>{{ harga | currency: 'IDR' }}</p>
<p>{{ dibuatPada | date: 'medium' }}</p>
<p>{{ nama | uppercase }}</p>
<p>{{ persen | percent }}</p>currency, date, uppercase, and percent are built-in pipes. The async pipe is also essential — it subscribes to an Observable or signal automatically and cleans up the subscription when the component is destroyed.
For custom formatting, create your own pipe with @Pipe:
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'rupiah',
standalone: true,
})
export class RupiahPipe implements PipeTransform {
transform(nilai: number): string {
return nilai.toLocaleString('id-ID', {
style: 'currency',
currency: 'IDR',
maximumFractionDigits: 0,
});
}
}The pipe above is used with {{ harga | rupiah }}. A pipe class must implement PipeTransform with the transform method. Pure pipes (the default) only run again when their input changes, which makes them efficient.
Key takeaways:
*ngIf and *ngFor are classic structural directives; @if and @for are their modern replacements.ngClass and ngStyle change classes and styles based on conditions.@Directive and @HostListener.PipeTransform and are used with the | operator.In the next episode, episode 6, we'll cover services and dependency injection — creating services and injecting them into components, provider scope at the root, module, and component level, singleton services with hierarchical injectors, and interceptors and provider configuration.