Angular dari Nol hingga 100 Contoh: Komponen, Template, Signal, Formulir, Routing, HTTP, dan Semua Jenis Properti

Angular dari Nol hingga 100 Contoh: Komponen, Template, Signals, Formulir, Routing, HTTP, dan Setiap Jenis Properti

Angular adalah framework dari Google untuk membangun aplikasi web yang besar dan terstruktur dengan TypeScript. Artikel ini dibangun dari membuat proyek sampai 100 contoh yang bisa dijalankan. Anda akan mempelajari komponen dan template, sintaks control-flow, pipe, input dan output, setiap jenis properti yang bisa dimiliki komponen, lifecycle hook, dependency injection, routing, formulir reaktif dan berbasis template, HTTP dengan RxJS, directive, signal, pengujian, dan deployment. Setiap contoh adalah cuplikan pendek. Cuplikan TypeScript diperiksa sintaksnya dengan parser TypeScript 5.6; template dan perintah build ditampilkan seperti yang ditulis dalam proyek Angular 19.

Yang Anda butuhkan

Node.js 20 atau yang lebih baru beserta npm, editor kode seperti Visual Studio Code, dan browser. Pasang Angular CLI dengan npm install -g @angular/cli. Anda sebaiknya memahami HTML, CSS, dan JavaScript dasar. Mengetahui apa itu class dan fungsi akan membantu contoh TypeScript.

Cara memakai sebuah contoh

Contoh TypeScript dimasukkan ke dalam proyek Angular mandiri, pada berkas komponen atau layanan yang disebutkan. Contoh perintah dijalankan di terminal proyek, dan contoh HTML adalah template untuk berkas HTML komponen.

Topik 1: Pengaturan proyek dan komponen pertama

Aplikasi Angular adalah pohon komponen. CLI membuat proyek, dan setiap komponen adalah class dengan decorator yang memberinya selector dan template.

Membuat proyek

ng new membuat proyek dengan berkas untuk aplikasi standalone, yang berarti komponen tidak memerlukan NgModule. ng serve menjalankan server pengembangan dengan live reload, dan ng generate membuat berkas untuk komponen, layanan, atau pipe baru.

Bootstrap dan konfigurasi

main.ts memanggil bootstrapApplication dengan komponen akar dan konfigurasi aplikasi. Konfigurasi itu mendaftar provider, seperti router dan HTTP client, di satu tempat.

Komponen, selector, dan gaya

Decorator @Component menetapkan selector, yaitu tag HTML, template, dan gayanya. Gaya hanya berlaku untuk komponen itu karena Angular menambahkan atribut unik pada elemennya, sehingga dua komponen bisa sama-sama memakai class bernama card dengan aman.

Ilustrasi

Cara komponen Angular ditampilkan di halaman: template dan class dikompilasi bersama, Angular menyimpan state komponen, dan signal serta change detection hanya memperbarui bagian tampilan yang berubah
Cara komponen Angular ditampilkan di halaman: template dan class dikompilasi bersama, Angular menyimpan state komponen, dan signal serta change detection hanya memperbarui bagian tampilan yang berubah

Contoh 1: Membuat proyek dengan CLI

Pasang Angular CLI dan buat aplikasi standalone.

npm install -g @angular/cli
ng new blog --standalone --routing --style=css
cd blog
ng serve

Contoh 2: Membuat komponen

CLI membuat berkas untuk komponen baru.

ng generate component hello
# short form
ng g c hello --standalone

Contoh 3: Bootstrap aplikasi

Jalankan komponen akar dengan konfigurasi aplikasi.

import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { appConfig } from './app/app.config';

bootstrapApplication(AppComponent, appConfig)
  .catch((err) => console.error(err));

Contoh 4: Komponen standalone

Komponen yang tidak memerlukan NgModule.

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  standalone: true,
  template: '<h1>Hello, Angular!</h1>',
})
export class AppComponent {}

Contoh 5: Template inline multibaris

Tulis template di beberapa baris dengan backtick.

import { Component } from '@angular/core';

@Component({
  selector: 'app-card',
  standalone: true,
  template: `
    <section>
      <h2>Welcome</h2>
      <p>This template spans several lines.</p>
    </section>
  `,
})
export class CardComponent {}

Contoh 6: Gaya komponen

Gaya yang hanya berlaku untuk komponen ini.

import { Component } from '@angular/core';

@Component({
  selector: 'app-badge',
  standalone: true,
  template: '<span class="badge">New</span>',
  styles: [`.badge { background: #3366ff; color: white; padding: 4px 8px; border-radius: 6px; }`],
})
export class BadgeComponent {}

Contoh 7: Konfigurasi aplikasi dengan provider

Daftarkan router dan layanan lain di satu tempat.

import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [provideRouter(routes)],
};

Contoh 8: Elemen akar di index.html

app-root adalah tempat Angular menggambar aplikasi.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Blog</title>
  <base href="/">
</head>
<body>
  <app-root></app-root>
</body>
</html>

Topik 2: Template dan data binding

Template adalah HTML dengan sintaks tambahan. Template menampilkan nilai dari komponen, mengirim aksi pengguna kembali, dan menjaga input serta nilai tetap sinkron.

Interpolasi dan ekspresi

Kurung kurawal ganda menampilkan nilai di dalam teks, dan ekspresinya bisa menghitung, memakai operator ternary, atau memakai safe navigation operator ?. sehingga objek yang tidak ada tidak menyebabkan error.

Binding property, atribut, dan class

Kurung siku mengikat property DOM, seperti [disabled]. Binding atribut dengan attr. diperlukan untuk atribut HTML yang tidak punya property yang sesuai, seperti label aria. Binding class dan style menambahkan class atau style inline dari sebuah nilai.

Event binding dan two-way binding

Kurung biasa mendengarkan event, seperti (click). Two-way binding dengan [(ngModel)] menjaga agar field formulir dan nilai komponen tetap sama, dan ini memerlukan FormsModule. Variabel referensi template seperti #box memberi nama pada elemen untuk seluruh sisa template.

Contoh 9: Interpolasi

Tampilkan nilai di dalam teks dengan kurung kurawal ganda.

import { Component } from '@angular/core';

@Component({
  selector: 'app-greeting',
  standalone: true,
  template: '<p>Hello, {{ name }}! You have {{ count + 1 }} message(s).</p>',
})
export class GreetingComponent {
  name = 'Ada';
  count = 2;
}

Contoh 10: Ekspresi template

Hitung nilai, gunakan ternary, dan safe navigation di dalam template.

<p>{{ price * quantity }}</p>
<p>{{ isLoggedIn ? 'Welcome back' : 'Please sign in' }}</p>
<p>{{ user?.name ?? 'Guest' }}</p>

Contoh 11: Property binding

Atur property elemen dari nilai komponen.

<img [src]="photoUrl" [alt]="photoAlt" width="120">
<button [disabled]="saving">Save</button>

Contoh 12: Attribute binding

Atur atribut HTML yang tidak punya property yang sesuai.

<button [attr.aria-label]="closeLabel">X</button>
<td [attr.colspan]="columns">Total</td>

Contoh 13: Event binding

Jalankan metode komponen ketika pengguna mengklik.

import { Component } from '@angular/core';

@Component({
  selector: 'app-counter',
  standalone: true,
  template: `
    <p>Clicked {{ clicks }} times</p>
    <button (click)="increment()">Click me</button>
  `,
})
export class CounterComponent {
  clicks = 0;
  increment() {
    this.clicks++;
  }
}

Contoh 14: Two-way binding dengan ngModel

Jaga agar input dan nilai komponen tetap sinkron.

import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';

@Component({
  selector: 'app-name-field',
  standalone: true,
  imports: [FormsModule],
  template: `
    <input [(ngModel)]="name">
    <p>You typed: {{ name }}</p>
  `,
})
export class NameFieldComponent {
  name = '';
}

Contoh 15: Class binding

Tambahkan atau hapus class CSS dari sebuah nilai.

<div [class.active]="isActive" [class.error]="hasError">Panel</div>
<div [ngClass]="{ 'big': size === 'lg', 'hidden': !visible }">Box</div>

Contoh 16: Style binding

Atur style inline dari nilai komponen.

<p [style.color]="textColor" [style.font-size.px]="fontSize">Styled text</p>

Contoh 17: Variabel referensi template

Beri nama pada elemen agar bagian lain template bisa memakainya.

<input #nameBox type="text">
<button (click)="greet(nameBox.value)">Greet</button>

Topik 3: Control flow: @if, @for, @switch, dan @defer

Sejak Angular 17, template memiliki blok bawaan untuk kondisi dan perulangan. Blok ini menggantikan direktif struktural yang lama dan lebih mudah dibaca.

@if dan @else

@if hanya menampilkan blok ketika kondisinya benar. @else if dan @else memilih salah satu dari beberapa blok. Blok bisa bersarang, jadi if bisa berada di dalam for.

@for dengan track dan @empty

@for mengulang sebuah blok untuk setiap item. track memberi tahu Angular item mana yang mana, sehingga hanya baris yang berubah yang digambar ulang. @empty menampilkan pesan ketika daftarnya kosong. track wajib diisi.

@switch dan @defer

@switch memilih blok berdasarkan nilai sebuah ekspresi, dengan @default sebagai cadangan. @defer memuat bagian halaman nanti, misalnya ketika bagian itu terlihat atau ketika pengguna berinteraksi dengannya, dan menampilkan @placeholder atau @loading sampai saat itu.

Contoh 18: @if

Tampilkan blok hanya ketika kondisinya benar.

@if (items.length > 0) {
  <p>You have {{ items.length }} items.</p>
}

Contoh 19: @if dengan @else

Pilih salah satu dari beberapa blok.

@if (isLoggedIn) {
  <p>Welcome back!</p>
} @else if (isGuest) {
  <p>Browsing as a guest.</p>
} @else {
  <p>Please sign in.</p>
}

Contoh 20: @for dengan track

Ulangi blok untuk setiap item; track memberi setiap item identitas yang stabil.

<ul>
  @for (fruit of fruits; track fruit.id) {
    <li>{{ fruit.name }}</li>
  }
</ul>

Contoh 21: @for dengan @empty

Tampilkan pesan ketika daftar tidak memiliki item.

@for (post of posts; track post.id) {
  <article>{{ post.title }}</article>
} @empty {
  <p>No posts yet.</p>
}

Contoh 22: @switch

Pilih blok berdasarkan nilai sebuah ekspresi.

@switch (status) {
  @case ('draft') { <span>Draft</span> }
  @case ('published') { <span>Live</span> }
  @default { <span>Unknown</span> }
}

Contoh 23: @defer dengan placeholder

Muat bagian halaman nanti, dengan menampilkan placeholder lebih dulu.

@defer (on viewport) {
  <app-heavy-chart />
} @placeholder {
  <p>Chart will load when visible.</p>
} @loading {
  <p>Loading chart...</p>
}

Contoh 24: @let untuk nilai lokal

Beri nama sebuah nilai sekali dan pakai beberapa kali dalam template.

@let total = price * quantity;
<p>Total: {{ total }}</p>

Topik 4: Pipe

Pipe mengubah sebuah nilai di dalam template, misalnya untuk memformat tanggal atau uang. Nilai di dalam komponen tetap sama.

Pipe format bawaan

date, currency, number, uppercase, lowercase, titlecase, dan slice memformat nilai di dalam template. Berikan argumen setelah titik dua, seperti date: 'longDate' atau currency: 'USD'.

json, keyvalue, dan async

json menampilkan objek sebagai teks, yang berguna ketika Anda men-debug. keyvalue melakukan perulangan pada kunci dan nilai sebuah objek. async berlangganan ke Observable dan menampilkan nilai terbarunya, lalu berhenti berlangganan untuk Anda ketika komponen dihancurkan.

Pipe kustom

Sebuah class yang diberi decorator @Pipe dan mengimplementasikan transform mendefinisikan pipe baru. Tulis pipe ketika Anda memformat jenis nilai yang sama di banyak tempat, dan jaga agar tidak memiliki efek samping sehingga Angular bisa memakai ulang hasilnya.

Contoh 25: Pipe date

Format tanggal untuk ditampilkan.

<p>{{ today | date: 'longDate' }}</p>
<p>{{ today | date: 'shortTime' }}</p>

Contoh 26: Pipe currency dan number

Format uang dan angka desimal.

<p>{{ 1234.5 | currency: 'USD' }}</p>
<p>{{ 3.14159 | number: '1.2-2' }}</p>

Contoh 27: Pipe teks

Ubah huruf besar-kecil atau potong sebagian string di dalam template.

<p>{{ 'angular' | uppercase }}</p>
<p>{{ 'HELLO' | lowercase }}</p>
<p>{{ 'hello world' | titlecase }}</p>
<p>{{ 'Angular' | slice: 0 : 3 }}</p>

Contoh 28: Pipe json dan keyvalue

Tampilkan objek untuk debugging, atau lakukan perulangan pada kunci dan nilainya.

<pre>{{ user | json }}</pre>
<ul>
  @for (item of settings | keyvalue; track item.key) {
    <li>{{ item.key }}: {{ item.value }}</li>
  }
</ul>

Contoh 29: Pipe async

Berlangganan ke Observable dan tampilkan nilai terbarunya.

import { Component } from '@angular/core';
import { AsyncPipe } from '@angular/common';
import { Observable, of } from 'rxjs';

@Component({
  selector: 'app-clock',
  standalone: true,
  imports: [AsyncPipe],
  template: '<p>Value: {{ value$ | async }}</p>',
})
export class ClockComponent {
  value$: Observable<number> = of(42);
}

Contoh 30: Pipe kustom

Tulis pipe Anda sendiri yang memperpendek teks.

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({ name: 'shorten', standalone: true })
export class ShortenPipe implements PipeTransform {
  transform(value: string, max = 20): string {
    return value.length > max ? value.slice(0, max) + '...' : value;
  }
}

Topik 5: Komponen yang saling berkomunikasi

Data mengalir ke bawah dari induk ke anak melalui input, dan event mengalir ke atas melalui output. Komponen yang bukan induk dan anak bisa berbagi layanan.

Input

input() mendeklarasikan nilai yang diatur oleh induk. Beri nilai default, gunakan input.required ketika induk wajib mengatur nilai itu, atau tambahkan alias ketika nama atributnya harus berbeda. Decorator @Input yang lama masih berfungsi.

Output dan @Output

output() mendeklarasikan event yang dipancarkan anak dengan .emit(). Induk mendengarkannya dengan event binding, seperti (liked)='save()'. @Output dengan EventEmitter yang lama berperilaku sama.

Proyeksi, query, dan layanan bersama

ng-content memungkinkan induk menempatkan kontennya sendiri di dalam anak. viewChild dan contentChild menemukan elemen atau template berdasarkan nama. Layanan bersama dengan Subject atau signal memungkinkan komponen yang berjauhan saling berkomunikasi.

Contoh 31: Signal input()

Menerima nilai dari induk dengan nilai default.

import { Component, input } from '@angular/core';

@Component({
  selector: 'app-user-card',
  standalone: true,
  template: '<p>Hello, {{ name() }}</p>',
})
export class UserCardComponent {
  name = input<string>('Guest');
}

Contoh 32: Input wajib

Memaksa induk memberikan nilainya.

import { Component, input } from '@angular/core';

@Component({
  selector: 'app-price',
  standalone: true,
  template: '<p>{{ amount() }} THB</p>',
})
export class PriceComponent {
  amount = input.required<number>();
}

Contoh 33: Event output()

Mengirim event dari anak ke induk.

import { Component, output } from '@angular/core';

@Component({
  selector: 'app-like-button',
  standalone: true,
  template: '<button (click)="liked.emit()">Like</button>',
})
export class LikeButtonComponent {
  liked = output<void>();
}

Contoh 34: EventEmitter dengan @Output

Cara lama untuk memancarkan event dari komponen anak.

import { Component, EventEmitter, Output } from '@angular/core';

@Component({
  selector: 'app-search-box',
  standalone: true,
  template: '<input (keyup.enter)="submit($event)">',
})
export class SearchBoxComponent {
  @Output() searched = new EventEmitter<string>();

  submit(event: Event) {
    this.searched.emit((event.target as HTMLInputElement).value);
  }
}

Contoh 35: Query signal viewChild

Mendapatkan referensi ke elemen di dalam template komponen.

import { Component, ElementRef, viewChild } from '@angular/core';

@Component({
  selector: 'app-focus',
  standalone: true,
  template: '<input #box type="text">',
})
export class FocusComponent {
  box = viewChild<ElementRef<HTMLInputElement>>('box');

  focusBox() {
    this.box()?.nativeElement.focus();
  }
}

Contoh 36: Proyeksi konten dengan ng-content

Biarkan induk menempatkan kontennya sendiri di dalam anak.

import { Component } from '@angular/core';

@Component({
  selector: 'app-panel',
  standalone: true,
  template: `
    <section class="panel">
      <ng-content></ng-content>
    </section>
  `,
})
export class PanelComponent {}

Contoh 37: contentChild dan ng-template

Terima template dari induk dan render untuk setiap item.

import { Component, contentChild, TemplateRef } from '@angular/core';

@Component({
  selector: 'app-list',
  standalone: true,
  imports: [],
  template: `
    @for (item of items; track item) {
      <ng-container [ngTemplateOutlet]="itemTpl()" [ngTemplateOutletContext]="{ $implicit: item }"></ng-container>
    }
  `,
})
export class ListComponent {
  items = ['a', 'b'];
  itemTpl = contentChild<TemplateRef<unknown>>('itemTpl');
}

Contoh 38: Host binding dan host listener

Terikat ke elemen host dan mendengarkan event-nya.

import { Component, HostBinding, HostListener } from '@angular/core';

@Component({
  selector: 'app-hover-card',
  standalone: true,
  template: '<p>Hover me</p>',
})
export class HoverCardComponent {
  @HostBinding('class.highlighted') highlighted = false;

  @HostListener('mouseenter') onEnter() {
    this.highlighted = true;
  }

  @HostListener('mouseleave') onLeave() {
    this.highlighted = false;
  }
}

Contoh 39: Metadata host

Atur class dan atribut host di dalam decorator.

import { Component } from '@angular/core';

@Component({
  selector: 'app-tag',
  standalone: true,
  template: '<ng-content></ng-content>',
  host: {
    'role': 'note',
    '[class.small]': 'small',
  },
})
export class TagComponent {
  small = true;
}

Contoh 40: Layanan bersama untuk pesan

Kirim pesan antarkomponen yang bukan induk dan anak.

import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class MessageBus {
  private messages = new Subject<string>();
  messages$ = this.messages.asObservable();

  send(text: string) {
    this.messages.next(text);
  }
}

Topik 6: Properti dari setiap jenis

Sebuah komponen memiliki beberapa jenis property. Setiap jenis cocok untuk kebutuhan yang berbeda: nilai biasa, nilai hanya-baca, nilai turunan, signal reaktif, dan nilai dua arah.

Property biasa, readonly, dan opsional

Property biasa adalah field dengan nilai, dan nilainya bisa berubah. readonly mencegah penetapan ulang setelah field diatur. Tanda tanya pada property, seperti email?, berarti nilainya mungkin tidak ada, sehingga kode harus menangani undefined.

Getter, setter, dan property statis

Getter menjalankan kode ketika property dibaca, dan setter menjalankan kode ketika property ditulis. Property statis milik class itu sendiri, bukan milik setiap instance, dan cocok untuk nilai default bersama seperti nama tema.

Signal: signal, computed, dan effect

signal() menyimpan nilai dan memberi tahu Angular ketika nilainya berubah; bacanya sebagai fungsi, seperti count(). computed() menurunkan nilai dari signal dan diperbarui ketika signal-nya berubah. effect() menjalankan kode setiap kali signal yang dibacanya berubah.

model(), alias input, dan input.required

model() adalah input dua arah: anak bisa mengatur nilainya, dan nilai induk mengikutinya, mirip ngModel untuk komponen kustom. Alias memberi input nama atribut yang berbeda. input.required memaksa induk memberikan nilai.

Property fungsi panah

Property yang berisi fungsi panah mempertahankan this milik komponen ketika fungsi itu diteruskan sebagai callback, karena fungsi panah menangkap this pada saat dibuat.

Contoh 41: Property class dengan nilai default

Property biasa dengan nilai awal.

import { Component } from '@angular/core';

@Component({
  selector: 'app-profile',
  standalone: true,
  template: '<p>{{ username }} ({{ role }})</p>',
})
export class ProfileComponent {
  username = 'mali';
  role: string = 'editor';
}

Contoh 42: Property readonly

Property yang hanya bisa diatur sekali, saat dideklarasikan.

import { Component } from '@angular/core';

@Component({
  selector: 'app-version',
  standalone: true,
  template: '<p>Version {{ version }}</p>',
})
export class VersionComponent {
  readonly version = '1.4.0';
}

Contoh 43: Property getter dan setter

Property yang menjalankan kode ketika dibaca atau ditulis.

import { Component } from '@angular/core';

@Component({
  selector: 'app-temperature',
  standalone: true,
  template: '<p>{{ fahrenheit }} F</p>',
})
export class TemperatureComponent {
  private celsius = 0;

  get fahrenheit(): number {
    return this.celsius * 9 / 5 + 32;
  }

  set fahrenheit(value: number) {
    this.celsius = (value - 32) * 5 / 9;
  }
}

Contoh 44: Property signal dengan set dan update

Nilai reaktif yang Anda ubah dengan set atau update.

import { Component, signal } from '@angular/core';

@Component({
  selector: 'app-counter-signal',
  standalone: true,
  template: `
    <p>{{ count() }}</p>
    <button (click)="count.update(n => n + 1)">+1</button>
    <button (click)="count.set(0)">Reset</button>
  `,
})
export class CounterSignalComponent {
  count = signal(0);
}

Contoh 45: Property computed

Nilai yang diturunkan dari signal dan diperbarui dengan sendirinya.

import { Component, computed, signal } from '@angular/core';

@Component({
  selector: 'app-cart',
  standalone: true,
  template: '<p>Total: {{ total() }}</p>',
})
export class CartComponent {
  prices = signal([120, 80, 50]);
  total = computed(() => this.prices().reduce((sum, p) => sum + p, 0));
}

Contoh 46: effect

Jalankan kode setiap kali signal yang dibacanya berubah.

import { Component, effect, signal } from '@angular/core';

@Component({
  selector: 'app-theme',
  standalone: true,
  template: '<p>Theme: {{ theme() }}</p>',
})
export class ThemeComponent {
  theme = signal('light');

  constructor() {
    effect(() => {
      console.log('Theme changed to', this.theme());
    });
  }
}

Contoh 47: Input dua arah model()

Input yang juga bisa diperbarui oleh anak, seperti ngModel.

import { Component, model } from '@angular/core';

@Component({
  selector: 'app-toggle',
  standalone: true,
  template: '<button (click)="on.set(!on())">{{ on() ? "On" : "Off" }}</button>',
})
export class ToggleComponent {
  on = model(false);
}

Contoh 48: Input dengan alias

Gunakan nama atribut yang berbeda di dalam template.

import { Component, input } from '@angular/core';

@Component({
  selector: 'app-avatar',
  standalone: true,
  template: '<img [alt]="label()">',
})
export class AvatarComponent {
  label = input('Profile picture', { alias: 'caption' });
}

Contoh 49: Property opsional

Property yang mungkin tidak ada, ditandai dengan tanda tanya.

interface Customer {
  id: number;
  name: string;
  email?: string;
}

const shopper: Customer = { id: 1, name: 'Ploy' };
console.log(shopper.email ?? 'no email');

Contoh 50: Property statis dan fungsi panah

Nilai untuk seluruh class, dan fungsi yang mempertahankan this-nya.

import { Component } from '@angular/core';

@Component({
  selector: 'app-config',
  standalone: true,
  template: '<p>{{ appName }}</p>',
})
export class ConfigComponent {
  static readonly defaultTheme = 'light';
  appName = 'Blog';
  logName = () => console.log(this.appName);
}

Topik 7: Lifecycle hook

Angular memanggil metode khusus pada titik-titik tetap dalam kehidupan komponen: ketika dibuat, ketika inputnya berubah, setelah tampilannya digambar, dan sebelum dihapus.

ngOnInit dan ngOnChanges

ngOnInit berjalan sekali setelah Angular mengatur input pertama, sehingga menjadi tempat yang tepat untuk memuat data. ngOnChanges berjalan setiap kali sebuah input berubah, dan menerima nilai lama serta nilai baru dalam SimpleChanges.

ngOnDestroy

ngOnDestroy berjalan tepat sebelum komponen dihapus. Berhenti berlangganan dari stream yang berjalan lama dan bersihkan timer di sana untuk mencegah kebocoran memori.

ngAfterViewInit dan ngDoCheck

ngAfterViewInit berjalan setelah tampilan komponen dan tampilan anaknya digambar, sehingga elemen tampilan sudah tersedia. ngDoCheck berjalan pada setiap putaran change detection, jadi buatlah tetap ringan.

Contoh 51: ngOnInit

Jalankan kode pengaturan sekali, setelah Angular mengatur input.

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-todo-list',
  standalone: true,
  template: '<p>{{ items.length }} tasks loaded</p>',
})
export class TodoListComponent implements OnInit {
  items: string[] = [];

  ngOnInit() {
    this.items = ['Write', 'Test', 'Ship'];
  }
}

Contoh 52: ngOnChanges

Bereaksi ketika input dari induk berubah.

import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';

@Component({
  selector: 'app-badge-count',
  standalone: true,
  template: '<span>{{ count }}</span>',
})
export class BadgeCountComponent implements OnChanges {
  @Input() count = 0;

  ngOnChanges(changes: SimpleChanges) {
    if (changes['count']) {
      console.log('Count changed to', changes['count'].currentValue);
    }
  }
}

Contoh 53: ngOnDestroy

Bersihkan langganan dan timer ketika komponen dihapus.

import { Component, OnDestroy, OnInit } from '@angular/core';
import { interval, Subscription } from 'rxjs';

@Component({
  selector: 'app-timer',
  standalone: true,
  template: '<p>Ticks: {{ ticks }}</p>',
})
export class TimerComponent implements OnInit, OnDestroy {
  ticks = 0;
  private sub?: Subscription;

  ngOnInit() {
    this.sub = interval(1000).subscribe(() => this.ticks++);
  }

  ngOnDestroy() {
    this.sub?.unsubscribe();
  }
}

Contoh 54: ngAfterViewInit

Gunakan elemen tampilan setelah template digambar.

import { AfterViewInit, Component, ElementRef, ViewChild } from '@angular/core';

@Component({
  selector: 'app-autofocus',
  standalone: true,
  template: '<input #field>',
})
export class AutofocusComponent implements AfterViewInit {
  @ViewChild('field') field!: ElementRef<HTMLInputElement>;

  ngAfterViewInit() {
    this.field.nativeElement.focus();
  }
}

Contoh 55: ngDoCheck

Jalankan pemeriksaan pada setiap putaran change detection.

import { Component, DoCheck } from '@angular/core';

@Component({
  selector: 'app-watcher',
  standalone: true,
  template: '<p>Checks run: {{ checks }}</p>',
})
export class WatcherComponent implements DoCheck {
  checks = 0;

  ngDoCheck() {
    this.checks++;
  }
}

Topik 8: Dependency injection dan layanan

Layanan menyimpan logika dan data yang dibutuhkan beberapa komponen. Angular membuatnya dan memberikannya melalui dependency injection, sehingga komponen tidak membuat salinannya sendiri.

@Injectable dan providedIn root

Class dengan @Injectable({ providedIn: 'root' }) dibuat sekali untuk seluruh aplikasi. Komponen mendapatkannya dengan inject() atau dengan parameter konstruktor. Gunakan signal di dalam layanan untuk menyimpan state yang bisa dibaca template.

InjectionToken dan value provider

Token mengidentifikasi nilai yang bukan class, seperti objek pengaturan. Anda mendaftarkannya dengan provider, menggunakan useValue untuk nilai tetap, useClass untuk sebuah class, atau useFactory untuk membangun nilainya.

Provider tingkat komponen

Array providers pada sebuah komponen memberi komponen itu dan anak-anaknya instance layanan sendiri. Ini berguna untuk state yang seharusnya direset setiap kali komponen dibuat.

Contoh 56: Fungsi inject()

Dapatkan layanan tanpa parameter konstruktor.

import { Component, inject } from '@angular/core';
import { DataService } from './data.service';

@Component({
  selector: 'app-items',
  standalone: true,
  template: '<p>{{ data.count() }} items</p>',
})
export class ItemsComponent {
  data = inject(DataService);
}

Contoh 57: Layanan yang bisa di-inject

Class bersama yang bisa diterima komponen mana pun.

import { Injectable, signal } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class CartService {
  private itemsSignal = signal<string[]>([]);
  readonly items = this.itemsSignal.asReadonly();

  add(item: string) {
    this.itemsSignal.update((list) => [...list, item]);
  }
}

Contoh 58: InjectionToken

Berikan nilai yang bukan class, seperti pengaturan.

import { InjectionToken } from '@angular/core';

export interface AppSettings {
  apiUrl: string;
  pageSize: number;
}

export const APP_SETTINGS = new InjectionToken<AppSettings>('app.settings');

Contoh 59: Provider dengan useValue

Berikan objek tetap ke injector.

import { ApplicationConfig } from '@angular/core';
import { APP_SETTINGS } from './app-settings.token';

export const appConfig: ApplicationConfig = {
  providers: [
    {
      provide: APP_SETTINGS,
      useValue: { apiUrl: 'https://api.example.com', pageSize: 20 },
    },
  ],
};

Contoh 60: useClass dan useFactory

Pilih sebuah class atau bangun nilai ketika dibutuhkan.

import { ApplicationConfig, inject } from '@angular/core';
import { LoggerService } from './logger.service';
import { ConsoleLogger } from './console-logger';

export const appConfig: ApplicationConfig = {
  providers: [
    { provide: LoggerService, useClass: ConsoleLogger },
    {
      provide: 'clock',
      useFactory: () => () => new Date().getFullYear(),
    },
  ],
};

Contoh 61: Provider tingkat komponen

Berikan layanan instance sendiri untuk satu pohon komponen.

import { Component, inject } from '@angular/core';
import { CounterStore } from './counter.store';

@Component({
  selector: 'app-scoped-counter',
  standalone: true,
  template: '<p>{{ store.value() }}</p>',
  providers: [CounterStore],
})
export class ScopedCounterComponent {
  store = inject(CounterStore);
}

Topik 9: Routing

Router memetakan path URL ke komponen, menampilkan halaman yang cocok di router-outlet, dan bisa memuat halaman hanya ketika dibutuhkan.

Rute dan tautan navigasi

Array Routes memetakan path seperti '' dan 'about' ke komponen. routerLink membuat tautan tanpa memuat ulang halaman, dan routerLinkActive menambahkan class pada tautan untuk halaman saat ini. Rute wildcard ** menangani URL yang tidak dikenal.

Parameter dan lazy loading

Rute seperti 'post/:id' meneruskan id, yang Anda baca dari ActivatedRoute. loadComponent dengan dynamic import memuat kode halaman hanya ketika pengguna membukanya, yang menjaga unduhan awal tetap kecil.

Guard, resolver, dan navigasi lewat kode

Guard CanActivate mengembalikan true atau UrlTree untuk mengalihkan. Resolver memuat data sebelum halaman dibuka. Router.navigate memindahkan pengguna dari kode, dan bisa meneruskan query parameter.

Contoh 62: Routes

Petakan path URL ke komponen.

import { Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { AboutComponent } from './about/about.component';

export const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'about', component: AboutComponent },
  { path: '**', redirectTo: '' },
];

Contoh 63: routerLink dan router-outlet

Hubungkan antarhalaman dan tampilkan halaman yang cocok.

<nav>
  <a routerLink="/" routerLinkActive="active">Home</a>
  <a routerLink="/about" routerLinkActive="active">About</a>
</nav>
<router-outlet></router-outlet>

Contoh 64: Parameter rute

Baca nilai seperti id dari URL.

import { Component, inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-post',
  standalone: true,
  template: '<p>Post id: {{ id }}</p>',
})
export class PostComponent {
  private route = inject(ActivatedRoute);
  id = this.route.snapshot.paramMap.get('id');
}

Contoh 65: Rute lazy-loaded

Muat kode halaman hanya ketika pengguna membukanya.

import { Routes } from '@angular/router';

export const routes: Routes = [
  {
    path: 'admin',
    loadComponent: () =>
      import('./admin/admin.component').then((m) => m.AdminComponent),
  },
];

Contoh 66: Guard CanActivate

Izinkan atau blokir sebuah rute berdasarkan pemeriksaan.

import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { AuthService } from './auth.service';

export const authGuard: CanActivateFn = () => {
  const auth = inject(AuthService);
  return auth.isLoggedIn() ? true : inject(Router).createUrlTree(['/login']);
};

Contoh 67: Resolver

Muat data sebelum halaman dibuka.

import { inject } from '@angular/core';
import { ResolveFn } from '@angular/router';
import { PostService } from './post.service';

export const postResolver: ResolveFn<string> = (route) => {
  return inject(PostService).titleFor(route.paramMap.get('id') ?? '');
};

Contoh 68: Navigasi terprogram

Pindah ke halaman lain dari kode, dengan query parameter.

import { Component, inject } from '@angular/core';
import { Router } from '@angular/router';

@Component({
  selector: 'app-checkout',
  standalone: true,
  template: '<button (click)="finish()">Finish</button>',
})
export class CheckoutComponent {
  private router = inject(Router);

  finish() {
    this.router.navigate(['/thanks'], { queryParams: { ref: 'cart' } });
  }
}

Topik 10: Formulir

Angular memiliki dua gaya formulir. Formulir reaktif dibangun di dalam kode dan menjadi pilihan umum untuk formulir yang besar. Formulir berbasis template memakai ngModel di HTML dan cocok untuk formulir kecil.

FormControl, FormGroup, dan FormBuilder

FormControl menyimpan satu nilai, dan FormGroup mengelompokkan beberapa kontrol. FormBuilder mengurangi jumlah kode, dan nonNullable menjaga nilainya tetap string, bukan null, setelah reset.

Validator dan pesan error

Validators.required, minLength, min, max, dan email sudah tersedia. Tampilkan error hanya ketika kontrol tidak valid dan sudah disentuh, sehingga pengguna tidak melihat error sebelum mengetik. Validator kustom adalah fungsi yang mengembalikan objek error atau null.

FormArray dan valueChanges

FormArray adalah daftar kontrol yang bisa Anda tambah atau hapus. valueChanges adalah Observable yang memancarkan nilai setiap kali nilainya berubah, yang berguna untuk pencarian langsung atau menyimpan draf.

Contoh 69: FormControl reaktif

Field formulir yang nilainya berada di dalam kode.

import { Component } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';

@Component({
  selector: 'app-email-input',
  standalone: true,
  imports: [ReactiveFormsModule],
  template: '<input [formControl]="email">',
})
export class EmailInputComponent {
  email = new FormControl('', { nonNullable: true });
}

Contoh 70: FormGroup dengan FormBuilder

Kelompokkan beberapa field menjadi satu formulir reaktif.

import { Component, inject } from '@angular/core';
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';

@Component({
  selector: 'app-signup',
  standalone: true,
  imports: [ReactiveFormsModule],
  template: '<form [formGroup]="form"></form>',
})
export class SignupComponent {
  private fb = inject(FormBuilder);
  form = this.fb.group({
    name: [''],
    email: [''],
  });
}

Contoh 71: Validator bawaan

Pemeriksaan required, minLength, min, max, dan email.

import { FormBuilder, Validators } from '@angular/forms';
import { inject } from '@angular/core';

const fb = inject(FormBuilder);
const form = fb.nonNullable.group({
  name: ['', [Validators.required, Validators.minLength(2)]],
  age: [18, [Validators.min(13), Validators.max(120)]],
  email: ['', Validators.email],
});

Contoh 72: Template formulir reaktif

Hubungkan FormGroup ke HTML dan tampilkan error.

<form [formGroup]="form" (ngSubmit)="save()">
  <input formControlName="name" placeholder="Name">
  @if (form.controls.name.invalid && form.controls.name.touched) {
    <p class="error">Name is required.</p>
  }
  <button [disabled]="form.invalid">Save</button>
</form>

Contoh 73: FormArray

Daftar kontrol formulir yang bisa bertambah atau berkurang.

import { FormArray, FormControl } from '@angular/forms';

const tags = new FormArray([
  new FormControl('angular'),
  new FormControl('typescript'),
]);

tags.push(new FormControl('rxjs'));
console.log(tags.length);

Contoh 74: Validator kustom

Tulis aturan Anda sendiri, seperti tidak boleh ada spasi.

import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';

export function noSpaces(): ValidatorFn {
  return (control: AbstractControl): ValidationErrors | null => {
    const value = String(control.value ?? '');
    return value.includes(' ') ? { noSpaces: true } : null;
  };
}

Contoh 75: Formulir berbasis template

Bangun formulir dengan ngModel langsung di template.

<form #form="ngForm" (ngSubmit)="submit(form.value)">
  <input name="title" ngModel required>
  <button [disabled]="form.invalid">Post</button>
</form>

Contoh 76: valueChanges

Bereaksi setiap kali nilai formulir berubah.

import { FormControl } from '@angular/forms';

const search = new FormControl('');

search.valueChanges.subscribe((text) => {
  console.log('Searching for', text);
});

Topik 11: HTTP, RxJS, dan data asinkron

HttpClient mengirim permintaan dan mengembalikan Observable. Operator RxJS mengubah stream tersebut, dan pipe async menampilkan hasilnya tanpa langganan manual.

Mengatur HttpClient

Panggil provideHttpClient() di konfigurasi aplikasi. HttpClient.get dan post mengembalikan Observable, yang tidak melakukan apa pun sampai ada yang berlangganan, jadi gunakan pipe async atau toSignal di template.

Operator: map, filter, dan switchMap

map mengubah setiap nilai dan filter hanya menyimpan nilai yang Anda inginkan. switchMap membatalkan permintaan sebelumnya ketika nilai baru datang, yang merupakan alat yang tepat untuk pencarian saat pengguna mengetik. Tambahkan debounceTime untuk menunggu sampai pengguna berhenti mengetik.

Error dan retry

catchError menangani permintaan yang gagal dan bisa mengembalikan nilai aman. retry mencoba permintaan lagi sebelum gagal. Selalu putuskan apa yang dilihat pengguna ketika terjadi error, karena layar kosong membingungkan.

Contoh 77: provideHttpClient

Aktifkan HttpClient di konfigurasi aplikasi.

import { ApplicationConfig } from '@angular/core';
import { provideHttpClient } from '@angular/common/http';

export const appConfig: ApplicationConfig = {
  providers: [provideHttpClient()],
};

Contoh 78: Mengambil data dengan pipe async

Tampilkan daftar dari HTTP tanpa langganan manual.

import { Component, inject } from '@angular/core';
import { AsyncPipe } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-users',
  standalone: true,
  imports: [AsyncPipe],
  template: `
    @for (user of users$ | async; track user.id) {
      <p>{{ user.name }}</p>
    }
  `,
})
export class UsersComponent {
  private http = inject(HttpClient);
  users$: Observable<{ id: number; name: string }[]> =
    this.http.get<{ id: number; name: string }[]>('/api/users');
}

Contoh 79: map dan filter RxJS

Ubah dan simpan hanya nilai yang Anda butuhkan dari sebuah stream.

import { map, filter } from 'rxjs';

const prices$ = of(120, 35, 80);
prices$
  .pipe(
    filter((p) => p > 50),
    map((p) => p * 1.07),
  )
  .subscribe((total) => console.log(total.toFixed(2)));

Contoh 80: switchMap untuk pencarian langsung

Tunggu sampai pengguna berhenti mengetik, lalu batalkan permintaan lama.

import { Component, inject } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { HttpClient } from '@angular/common/http';
import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs';

@Component({
  selector: 'app-live-search',
  standalone: true,
  imports: [ReactiveFormsModule],
  template: '<input [formControl]="query">',
})
export class LiveSearchComponent {
  private http = inject(HttpClient);
  query = new FormControl('', { nonNullable: true });

  results$ = this.query.valueChanges.pipe(
    debounceTime(300),
    distinctUntilChanged(),
    switchMap((q) => this.http.get<string[]>('/api/search', { params: { q } })),
  );
}

Contoh 81: catchError dan retry

Coba ulang permintaan yang gagal, lalu kembali ke nilai aman.

import { inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { catchError, of, retry } from 'rxjs';

const http = inject(HttpClient);
const safeList$ = http.get<string[]>('/api/tags').pipe(
  retry(2),
  catchError(() => of([] as string[])),
);

Contoh 82: HTTP POST

Kirim data ke server dengan body permintaan.

import { inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';

export class PostService {
  private http = inject(HttpClient);

  createPost(title: string) {
    return this.http.post<{ id: number }>('/api/posts', { title });
  }
}

Topik 12: Directive

Directive mengubah cara elemen berperilaku atau apakah elemen itu muncul. Attribute directive mengubah elemen; structural directive menambah atau menghapusnya.

Attribute directive

Directive dengan selector di dalam kurung siku, seperti [appHighlight], bisa memakai HostListener untuk bereaksi terhadap event mouse dan mengubah gaya elemen. Jaga logikanya tetap kecil dan letakkan perilaku besar di dalam komponen.

Structural directive dan TemplateRef

Structural directive menyuntikkan TemplateRef dan ViewContainerRef. Ia membuat view dengan createEmbeddedView ketika suatu kondisi benar, dan menghapusnya ketika salah. *ngIf dan @if bawaan Angular bekerja dengan cara yang sama.

Setter input di directive

Setter pada input dijalankan setiap kali induk memberikan nilai baru. Gunakan untuk bereaksi langsung terhadap sebuah nilai, misalnya untuk membangun ulang view, tanpa menulis ngOnChanges.

Contoh 83: Attribute directive

Directive yang mengubah tampilan sebuah elemen.

import { Directive, ElementRef, HostListener, inject, input } from '@angular/core';

@Directive({
  selector: '[appHighlight]',
  standalone: true,
})
export class HighlightDirective {
  color = input('yellow', { alias: 'appHighlight' });
  private el = inject<ElementRef<HTMLElement>>(ElementRef);

  @HostListener('mouseenter') onEnter() {
    this.el.nativeElement.style.backgroundColor = this.color();
  }

  @HostListener('mouseleave') onLeave() {
    this.el.nativeElement.style.backgroundColor = '';
  }
}

Contoh 84: Structural directive

Directive yang menambah atau menghapus elemen dari halaman.

import { Directive, inject, Input, TemplateRef, ViewContainerRef } from '@angular/core';

@Directive({
  selector: '[appUnless]',
  standalone: true,
})
export class UnlessDirective {
  private template = inject<TemplateRef<unknown>>(TemplateRef);
  private view = inject(ViewContainerRef);

  @Input() set appUnless(condition: boolean) {
    this.view.clear();
    if (!condition) {
      this.view.createEmbeddedView(this.template);
    }
  }
}

Contoh 85: Setter input

Jalankan kode setiap kali induk memberikan nilai baru.

import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-tag-list',
  standalone: true,
  template: '<p>{{ tags.join(", ") }}</p>',
})
export class TagListComponent {
  private _tags: string[] = [];

  @Input()
  set tags(value: string[]) {
    this._tags = value.map((t) => t.trim());
  }
  get tags(): string[] {
    return this._tags;
  }
}

Topik 13: Rendering, signal, dan change detection

Angular memutuskan apa yang digambar ulang melalui change detection. Signal dan OnPush memungkinkan Anda mengendalikannya, sehingga halaman besar tetap cepat.

Daftar dengan indeks dan nilai turunan

Di dalam @for Anda bisa memakai $index, $first, dan $last untuk memberi gaya pada setiap baris. Signal computed bisa memfilter daftar, sehingga hasil filter diperbarui setiap kali teks pencarian atau daftar berubah.

Change detection OnPush

Dengan ChangeDetectionStrategy.OnPush, Angular hanya memeriksa komponen ketika inputnya berubah, sebuah event terjadi di dalamnya, atau signal yang dibacanya berubah. Ini peningkatan kecepatan yang besar pada halaman dengan banyak komponen.

Zoneless dan toSignal

Aplikasi zoneless berjalan tanpa zone.js, sehingga view diperbarui karena signal dan event, bukan karena setiap event browser. toSignal mengubah Observable menjadi signal, yang bisa Anda baca di template tanpa berlangganan.

Contoh 86: @for dengan $index dan $first

Gunakan posisi setiap item dalam perulangan.

@for (step of steps; track step; let i = $index, first = $first, last = $last) {
  <li [class.first]="first" [class.last]="last">{{ i + 1 }}. {{ step }}</li>
}

Contoh 87: Pemfilteran dengan computed

Tampilkan daftar yang sudah difilter dan diperbarui saat pengguna mengetik.

import { Component, computed, signal } from '@angular/core';

@Component({
  selector: 'app-fruit-filter',
  standalone: true,
  template: `
    <input (input)="query.set($any($event.target).value)">
    @for (fruit of shown(); track fruit) {
      <p>{{ fruit }}</p>
    }
  `,
})
export class FruitFilterComponent {
  fruits = ['apple', 'banana', 'cherry'];
  query = signal('');
  shown = computed(() =>
    this.fruits.filter((f) => f.includes(this.query().toLowerCase())),
  );
}

Contoh 88: Change detection OnPush

Periksa komponen hanya ketika inputnya berubah.

import { ChangeDetectionStrategy, Component, Input } from '@angular/core';

@Component({
  selector: 'app-price-tag',
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: '<p>{{ price }} THB</p>',
})
export class PriceTagComponent {
  @Input() price = 0;
}

Contoh 89: Change detection zoneless

Jalankan Angular tanpa zone.js, dengan signal untuk memperbarui view.

import { ApplicationConfig, provideZonelessChangeDetection } from '@angular/core';

export const appConfig: ApplicationConfig = {
  providers: [provideZonelessChangeDetection()],
};

Contoh 90: toSignal

Ubah Observable menjadi signal yang bisa Anda baca di template.

import { Component, inject } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { interval, map } from 'rxjs';

@Component({
  selector: 'app-seconds',
  standalone: true,
  template: '<p>Seconds: {{ seconds() }}</p>',
})
export class SecondsComponent {
  seconds = toSignal(interval(1000).pipe(map((n) => n + 1)), { initialValue: 0 });
}

Topik 14: Pengujian dan animasi

Pengujian memeriksa komponen dan layanan secara terisolasi. Animasi mengubah elemen dengan mulus ketika state-nya berubah.

TestBed dan pengujian komponen

TestBed membuat komponen di dalam modul pengujian, detectChanges menjalankan change detection, dan pengujian memeriksa teks dari nativeElement. Jaga agar setiap pengujian hanya memeriksa satu perilaku sehingga kegagalan menunjuk ke satu penyebab.

HttpTestingController dan fakeAsync

provideHttpClientTesting mengganti jaringan sungguhan dengan palsu, sehingga expectOne dan flush memberi pengujian sebuah respons. fakeAsync dengan tick memajukan waktu, sehingga aksi yang tertunda bisa diuji tanpa menunggu.

Trigger animasi

Trigger animasi mendefinisikan state, seperti open dan closed, serta transisi di antaranya beserta waktunya. Ikat trigger ke sebuah nilai di template, dan Angular memainkan transisi ketika nilainya berubah.

Contoh 91: Pengujian komponen dengan TestBed

Buat komponen di dalam pengujian dan periksa teksnya.

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CounterComponent } from './counter.component';

describe('CounterComponent', () => {
  let fixture: ComponentFixture<CounterComponent>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({ imports: [CounterComponent] }).compileComponents();
    fixture = TestBed.createComponent(CounterComponent);
  });

  it('starts at zero', () => {
    fixture.detectChanges();
    expect(fixture.nativeElement.textContent).toContain('0');
  });
});

Contoh 92: HttpTestingController

Uji layanan HTTP tanpa server sungguhan.

import { TestBed } from '@angular/core/testing';
import { provideHttpClient } from '@angular/common/http';
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
import { UserService } from './user.service';

describe('UserService', () => {
  it('loads users', () => {
    TestBed.configureTestingModule({
      providers: [provideHttpClient(), provideHttpClientTesting()],
    });
    const service = TestBed.inject(UserService);
    const http = TestBed.inject(HttpTestingController);

    service.users().subscribe((list) => expect(list.length).toBe(2));
    http.expectOne('/api/users').flush([{ id: 1 }, { id: 2 }]);
    http.verify();
  });
});

Contoh 93: fakeAsync dan tick

Majukan waktu di dalam pengujian untuk memeriksa aksi yang tertunda.

import { fakeAsync, tick } from '@angular/core/testing';

it('runs a delayed action', fakeAsync(() => {
  let done = false;
  setTimeout(() => (done = true), 500);
  tick(500);
  expect(done).toBeTrue();
}));

Contoh 94: Trigger animasi

Animasikan elemen ketika state-nya berubah.

import { Component } from '@angular/core';
import { trigger, state, style, transition, animate } from '@angular/animations';

@Component({
  selector: 'app-drawer',
  standalone: true,
  animations: [
    trigger('slide', [
      state('closed', style({ height: '0px' })),
      state('open', style({ height: '200px' })),
      transition('closed <=> open', animate('300ms ease-in')),
    ]),
  ],
  template: '<div [@slide]="isOpen ? \'open\' : \'closed\'">Content</div>',
})
export class DrawerComponent {
  isOpen = false;
}

Topik 15: Konfigurasi, error, dan deployment

Aplikasi sungguhan memerlukan pengaturan untuk setiap lingkungan, rencana untuk error yang tidak terduga, dan build produksi yang siap dipublikasikan.

File environment dan initializer

Simpan pengaturan seperti alamat API di file environment, dengan nilai berbeda untuk development dan produksi. provideAppInitializer menjalankan kode, seperti memuat konfigurasi, sebelum halaman pertama digambar.

Error dan ErrorHandler

Angular mengirim error yang tidak ditangkap ke ErrorHandler. Sediakan class Anda sendiri untuk mencatatnya atau mengirimnya ke layanan pelaporan, sehingga satu tempat memutuskan apa yang terjadi pada error yang tidak terduga.

Build, test, dan deploy

ng build --configuration production membuat berkas yang dioptimalkan di dist/. ng test menjalankan pengujian unit sekali dengan --watch=false di pipeline build. Host berkas statis di server web atau layanan hosting statis apa pun yang mendukung aplikasi single-page.

Contoh 95: Konfigurasi environment

Simpan pengaturan seperti alamat API di satu berkas.

export const environment = {
  production: false,
  apiUrl: 'http://localhost:3000/api',
};

Contoh 96: ErrorHandler kustom

Laporkan error yang tidak terduga di satu tempat.

import { ErrorHandler, Injectable } from '@angular/core';

@Injectable()
export class ReportingErrorHandler implements ErrorHandler {
  handleError(error: unknown): void {
    console.error('Unexpected error:', error);
  }
}

Contoh 97: provideAppInitializer

Muat konfigurasi sebelum aplikasi dimulai.

import { ApplicationConfig, provideAppInitializer, inject } from '@angular/core';
import { ConfigService } from './config.service';

export const appConfig: ApplicationConfig = {
  providers: [
    provideAppInitializer(() => inject(ConfigService).load()),
  ],
};

Contoh 98: Import di komponen standalone

Daftarkan directive dan pipe yang dipakai komponen.

import { Component } from '@angular/core';
import { NgClass } from '@angular/common';
import { HighlightDirective } from './highlight.directive';
import { ShortenPipe } from './shorten.pipe';

@Component({
  selector: 'app-note',
  standalone: true,
  imports: [NgClass, HighlightDirective, ShortenPipe],
  template: '<p [ngClass]="{ bold: true }" appHighlight="pink">{{ text | shorten: 10 }}</p>',
})
export class NoteComponent {
  text = 'A long note about Angular standalone components';
}

Contoh 99: @defer saat interaksi

Muat bagian halaman hanya setelah pengguna mengklik.

<button #openBtn>Show comments</button>

@defer (on interaction(openBtn)) {
  <app-comments />
} @placeholder {
  <p>Comments will load after you click.</p>
}

Contoh 100: Perintah build dan test

Perintah CLI Angular utama untuk pekerjaan sehari-hari.

# development server
ng serve
# production build (outputs to dist/)
ng build --configuration production
# unit tests
ng test --watch=false

Proyek latihan: Daftar tugas

Bangun satu aplikasi kecil yang memakai gagasan dari setiap bagian. Tulis sendiri terlebih dahulu, lalu bandingkan dengan contohnya.

1. Buat proyek standalone, lalu komponen TaskList dengan signal yang menyimpan larik tugas.

2. Tambahkan formulir dengan field teks yang wajib diisi, dan tambahkan setiap tugas baru ke daftar dengan metode yang diikat ke (ngSubmit).

3. Tampilkan tugas dengan @for dan track, serta pesan @empty ketika daftarnya kosong.

4. Tambahkan signal computed yang menghitung tugas yang belum selesai, dan tampilkan di template.

5. Tambahkan rute untuk halaman detail tugas yang membaca id dari URL dengan ActivatedRoute.

6. Tulis satu pengujian komponen yang memeriksa pesan kosong, lalu jalankan ng test --watch=false.

Langkah berikutnya

Berikutnya, pelajari router secara mendalam, manajemen state dengan signal atau NgRx, server-side rendering dengan Angular SSR, dan library komponen seperti Angular Material. Dokumentasi resmi Angular di angular.dev adalah referensi terbaik, dan contoh di sini mengikuti sintaks yang direkomendasikannya untuk Angular 17 ke atas.

Ringkasan

Angular membangun aplikasi dari komponen. Template mengikat data dan event, blok control-flow menampilkan atau mengulang konten, pipe memformat nilai, dan input serta output menghubungkan induk dan anak. Komponen menyimpan state-nya dalam property biasa, nilai readonly, getter, dan signal. Layanan dibagikan melalui dependency injection, routing memetakan URL ke halaman, formulir memvalidasi input, dan HttpClient dengan RxJS memuat data. Pengujian, konfigurasi, dan build produksi melengkapi alurnya. Cara terbaik untuk mengingat ke-100 contoh ini adalah membangun proyek daftar tugas dan mengubah satu hal setiap kali.