Angular dari Nol hingga 100 Contoh: Komponen, Template, Signal, Formulir, Routing, HTTP, dan Semua 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
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.