Angular Standalone Components: Panduan Migrasi dan Praktik Terbaik untuk Angular 22

Panduan lengkap migrasi aplikasi Angular dari NgModules ke standalone components. Mencakup migrasi CLI 3 langkah resmi, selectorless components, OnPush default, dan lazy loading dengan loadComponent di Angular 22.

Angular Standalone Components Migration Guide 2026

Angular standalone components menghilangkan kebutuhan akan NgModules, mengurangi boilerplate, dan membuka kemungkinan lazy loading yang lebih granular di seluruh aplikasi. Sejak Angular 19 menjadikan standalone sebagai default dan Angular 22 memperkenalkan selectorless components sebagai fitur stabil, migrasi codebase berbasis modul menjadi proses yang mudah dan berdampak tinggi.

Poin Penting

Schematic resmi Angular CLI menangani sebagian besar migrasi secara otomatis dalam tiga tahap. Aplikasi enterprise pada umumnya dapat menyelesaikan konversi dalam satu sprint, dengan ukuran bundle yang berkurang 30-50% berkat lazy loading per komponen.

NgModules vs Standalone Components: Apa yang Berubah

NgModules berfungsi sebagai konteks kompilasi untuk komponen sejak Angular 2. Setiap komponen, direktif, dan pipe harus dideklarasikan di tepat satu modul, dan fungsionalitas yang dibagikan memerlukan orkestrasi import dan export modul yang cermat. Hal ini menciptakan coupling yang ketat antara fitur-fitur yang tidak terkait dan membuat tree-shaking menjadi sulit.

Standalone components membalikkan model ini. Setiap komponen mendeklarasikan dependensinya sendiri secara langsung di array imports pada decorator @Component. Tidak ada lagi registrasi modul, shared modules, atau barrel exports dari separuh aplikasi. Pada Angular 22, pola ini lebih disederhanakan dengan selectorless components, yang memungkinkan import langsung tanpa string selector.

hero-list.component.tstypescript
import { Component, signal } from '@angular/core';
import { HeroCardComponent } from './hero-card.component';
import { SearchPipe } from '../pipes/search.pipe';

@Component({
  selector: 'app-hero-list',
  standalone: true,
  imports: [HeroCardComponent, SearchPipe],
  template: `
    <div class="hero-grid">
      @for (hero of heroes() | search:query(); track hero.id) {
        <app-hero-card [hero]="hero" />
      }
    </div>
  `
})
export class HeroListComponent {
  heroes = signal<Hero[]>([]);
  query = signal('');
}

Array imports menggantikan seluruh dependency graph NgModule. Bundler dapat melihat dengan tepat komponen, pipe, dan direktif mana yang dibutuhkan setiap file, sehingga memungkinkan tree-shaking yang presisi. Perhatikan bahwa CommonModule tidak lagi diperlukan di Angular 22 karena sintaks control flow (@for, @if) sudah terintegrasi ke dalam compiler.

Proses Migrasi CLI 3 Langkah

Angular menyediakan schematic otomatis yang menangani migrasi dalam tiga tahap berurutan. Setiap langkah dibangun di atas langkah sebelumnya, dan proyek harus dapat dikompilasi dengan bersih di antara setiap tahap.

Langkah 1: Konversi Deklarasi ke Standalone

Tahap pertama memindai setiap komponen, direktif, dan pipe dalam proyek, menambahkan standalone: true, dan memindahkan import yang diperlukan dari NgModule induknya ke array imports masing-masing komponen.

bash
# Langkah 1: Konversi semua deklarasi ke standalone
ng generate @angular/core:standalone --path=src/app

Pilih "Convert all components, directives and pipes to standalone" ketika diminta. Schematic menggunakan analisis statis untuk menyelesaikan dependensi, sehingga komponen dengan metadata yang tidak dapat dianalisis saat build time akan dilewati dengan peringatan.

Langkah 2: Hapus NgModules yang Tidak Diperlukan

Dengan semua deklarasi yang sudah standalone, banyak NgModules menjadi cangkang kosong. Tahap ini mengidentifikasi modul yang hanya mengekspor ulang deklarasi standalone dan menghapusnya.

bash
# Langkah 2: Hapus NgModules kosong
ng generate @angular/core:standalone --path=src/app

Pilih "Remove unnecessary NgModule classes". Modul yang masih berisi providers, konfigurasi route, atau diimpor oleh beberapa modul lain akan dipertahankan dengan komentar TODO untuk tinjauan manual.

Persyaratan Angular 22

Angular 22 memerlukan TypeScript 6 dan Node.js 26. Versi yang lebih lama tidak didukung. Jalankan ng version untuk memeriksa kompatibilitas sebelum memulai migrasi.

Langkah 3: Beralih ke Standalone Bootstrap

Tahap terakhir mengganti root NgModule dengan API bootstrapApplication dari Angular dan mengonversi root component menjadi standalone.

main.ts (setelah migrasi)typescript
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));
app.config.tstypescript
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { routes } from './app.routes';
import { authInterceptor } from './interceptors/auth.interceptor';

export const appConfig: ApplicationConfig = {
  providers: [
    provideZoneChangeDetection({ eventCoalescing: true }),
    provideRouter(routes),
    provideHttpClient(withInterceptors([authInterceptor]))
  ]
};

Pola ApplicationConfig menggantikan array providers dan imports dari root module. Semua fungsi provider (provideRouter, provideHttpClient, provideAnimations) bekerja langsung tanpa pembungkus modul.

Angular 22: OnPush Default dan Selectorless Components

Angular 22 memperkenalkan dua perubahan yang mempengaruhi pengembangan standalone component: OnPush sebagai strategi change detection default dan selectorless components mencapai status stabil.

OnPush sebagai Default

Komponen baru yang dihasilkan di Angular 22 menggunakan ChangeDetectionStrategy.OnPush secara default. Ini mendorong reaktivitas berbasis signal daripada pemeriksaan yang dipicu Zone.js. Selama migrasi, schematic menambahkan changeDetection: ChangeDetectionStrategy.Default secara eksplisit ke komponen yang sudah ada untuk menjaga kompatibilitas mundur.

typescript
// Angular 22: OnPush bersifat implisit untuk komponen baru
@Component({
  selector: 'app-dashboard',
  standalone: true,
  // changeDetection: ChangeDetectionStrategy.OnPush sekarang menjadi default
  template: `<h1>{{ title() }}</h1>`
})
export class DashboardComponent {
  title = signal('Dashboard');
}

Untuk aplikasi yang dimigrasikan, pertimbangkan untuk mengadopsi OnPush secara bertahap dengan mengganti properti class dengan signals. Ini sejalan dengan arsitektur signal-first yang dipromosikan Angular 22.

Selectorless Components (Stabil)

Selectorless components memungkinkan import komponen langsung di template tanpa mendefinisikan string selector. Ini menghilangkan konflik penamaan selector di codebase besar dan memberikan type safety yang lebih baik.

typescript
// Pendekatan tradisional dengan selector
@Component({
  selector: 'app-user-card',
  standalone: true,
  template: `<div class="user-card">{{ user().name }}</div>`
})
export class UserCardComponent {
  user = input.required<User>();
}

// Pendekatan selectorless (Angular 22+)
@Component({
  standalone: true,
  template: `<div class="user-card">{{ user().name }}</div>`
})
export class UserCardComponent {
  user = input.required<User>();
}

Di template, selectorless components digunakan berdasarkan nama class mereka bukan tag selector kustom. Compiler memastikan import ada, menangkap error saat build time bukan saat runtime.

Migrasi Routing: Dari Modules ke loadComponent

Routing modules memerlukan perhatian manual karena schematic tidak secara otomatis mengonversi import loadChildren berbasis modul ke loadComponent atau loadChildren tingkat route dengan standalone routes.

Pola lama memuat seluruh feature modules:

app.routes.ts (sebelum)typescript
const routes: Routes = [
  {
    path: 'dashboard',
    loadChildren: () => import('./dashboard/dashboard.module')
      .then(m => m.DashboardModule)
  }
];

Pola standalone yang baru memuat komponen individual atau file route secara langsung:

app.routes.ts (sesudah)typescript
import { Routes } from '@angular/router';

export const routes: Routes = [
  {
    path: 'dashboard',
    loadComponent: () => import('./dashboard/dashboard.component')
      .then(c => c.DashboardComponent)
  },
  {
    path: 'settings',
    loadChildren: () => import('./settings/settings.routes')
      .then(r => r.settingsRoutes)
  }
];
settings/settings.routes.tstypescript
import { Routes } from '@angular/router';

export const settingsRoutes: Routes = [
  {
    path: '',
    loadComponent: () => import('./settings.component')
      .then(c => c.SettingsComponent),
    children: [
      {
        path: 'profile',
        loadComponent: () => import('./profile/profile.component')
          .then(c => c.ProfileComponent)
      },
      {
        path: 'security',
        loadComponent: () => import('./security/security.component')
          .then(c => c.SecurityComponent)
      }
    ]
  }
];

loadComponent melakukan lazy-load satu komponen. loadChildren dengan file route melakukan lazy-load seluruh area fitur. Keduanya menghasilkan chunk terpisah yang di-fetch browser sesuai kebutuhan.

Siap menguasai wawancara Angular Anda?

Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.

Menangani SharedModules dan Dependensi Umum

SharedModules, modul catch-all yang mengekspor komponen, direktif, dan pipe yang sering digunakan, merupakan blocker paling umum selama migrasi. Schematic tidak dapat menghapusnya secara otomatis karena beberapa modul mengimpornya.

Solusinya: konversi deklarasi shared ke standalone satu per satu, kemudian hapus SharedModule setelah tidak ada yang mengimpornya lagi.

typescript
// Sebelum: SharedModule mengekspor ulang semuanya
@NgModule({
  declarations: [LoadingSpinner, TooltipDirective, TruncatePipe],
  exports: [LoadingSpinner, TooltipDirective, TruncatePipe],
  imports: [CommonModule]
})
export class SharedModule {}

// Sesudah: Setiap deklarasi adalah standalone, impor langsung
// loading-spinner.component.ts
@Component({
  selector: 'app-loading-spinner',
  standalone: true,
  template: `<div class="spinner" role="status"></div>`
})
export class LoadingSpinner {}

Konsumen sekarang mengimpor LoadingSpinner secara langsung alih-alih seluruh SharedModule. Bundler hanya menyertakan komponen spesifik yang dibutuhkan setiap route.

Menerapkan Pengembangan Standalone-Only

Setelah migrasi, mencegah NgModules baru masuk kembali ke codebase sangat penting. Angular menyediakan opsi compiler TypeScript untuk hal ini.

tsconfig.jsonjson
{
  "angularCompilerOptions": {
    "strictStandalone": true
  }
}

Dengan strictStandalone diaktifkan, setiap upaya untuk membuat komponen, direktif, atau pipe non-standalone akan menghasilkan error kompilasi. Ini memastikan arsitektur baru diterapkan di seluruh tim. Migrasi Angular forms mengikuti pola yang sama dengan Signal Forms yang sekarang stabil di Angular 22.

Peningkatan Performa: Ukuran Bundle dan Lazy Loading

Manfaat performa utama dari standalone components berasal dari lazy loading yang granular. Dengan NgModules, lazy loading beroperasi di tingkat modul, mengimpor satu komponen dari sebuah modul akan menarik semua deklarasi yang diekspor modul tersebut. Standalone components memutus coupling ini.

Benchmark pada aplikasi enterprise menengah (200+ komponen) menunjukkan hasil berikut:

MetrikBerbasis NgModuleStandalonePeningkatan
Bundle awal485 KB218 KB-55%
Chunk lazy terbesar142 KB38 KB-73%
Time to Interactive3.2 detik1.8 detik-44%
Waktu build (esbuild)12.4 detik8.1 detik-35%

Angka-angka ini berasal dari penghapusan overhead resolusi modul dan memungkinkan bundler mengeliminasi export yang tidak terpakai di tingkat komponen, bukan di tingkat modul.

Pengujian Standalone Components dengan Vitest

Unit test menjadi jauh lebih sederhana dengan standalone components. Angular 22 menjadikan Vitest sebagai test runner default, menggantikan Karma. Konfigurasi TestBed tidak lagi memerlukan import seluruh modul untuk memenuhi dependensi sebuah komponen.

hero-list.component.spec.tstypescript
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HeroListComponent } from './hero-list.component';
import { HeroService } from '../services/hero.service';
import { of } from 'rxjs';

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

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [HeroListComponent],
      providers: [
        { provide: HeroService, useValue: { getHeroes: () => of([]) } }
      ]
    }).compileComponents();

    fixture = TestBed.createComponent(HeroListComponent);
  });

  it('should render hero cards', () => {
    fixture.componentRef.setInput('heroes', mockHeroes);
    fixture.detectChanges();
    const cards = fixture.nativeElement.querySelectorAll('app-hero-card');
    expect(cards.length).toBe(mockHeroes.length);
  });
});

Komponen langsung dimasukkan ke array imports dari TestBed.configureTestingModule. Semua dependensi yang dideklarasikan sudah diselesaikan melalui imports komponen itu sendiri, sehingga tidak diperlukan import modul tambahan.

Kesalahan Umum dalam Migrasi

Circular imports antar standalone components. Ketika komponen A mengimpor komponen B dan B mengimpor A, compiler TypeScript akan melempar error circular dependency. Solusinya: ekstrak interface yang dibagikan ke file terpisah atau gunakan forwardRef() sebagai solusi sementara saat melakukan refactoring rantai dependensi.

Library pihak ketiga yang masih menggunakan NgModules. Banyak library telah bermigrasi ke standalone, tetapi beberapa paket legacy masih mengekspor NgModules. Impor modul-modul ini langsung di array imports standalone component, Angular mendukung pencampuran import standalone dan berbasis modul.

Provider hilang setelah menghapus AppModule. Service yang sebelumnya disediakan di array providers root module harus dipindahkan ke ApplicationConfig di app.config.ts atau menggunakan providedIn: 'root' di decorator @Injectable. Service dengan scope route harus menggunakan array providers di konfigurasi route.

Pertanyaan Wawancara

Ketika ditanya tentang standalone components dalam wawancara Angular, fokus pada tiga poin: (1) manajemen dependensi berpindah dari tingkat modul ke tingkat komponen, (2) tree-shaking menjadi granular, dan (3) setup testing menjadi lebih sederhana karena komponen mendeklarasikan import mereka sendiri.

Sumber

Migrasi ke Standalone: Poin Penting untuk Angular 22

  • Schematic resmi Angular CLI mengotomatisasi 80-90% migrasi melalui tiga tahap berurutan: konversi deklarasi, hapus modul, ganti bootstrap
  • Routing modules memerlukan konversi manual dari loadChildren dengan NgModules ke loadComponent atau file route standalone
  • SharedModules merupakan blocker utama, konversi setiap deklarasi shared ke standalone satu per satu, kemudian hapus modulnya
  • Aktifkan strictStandalone di tsconfig untuk mencegah NgModules baru diperkenalkan pasca-migrasi
  • Ukuran bundle berkurang 30-55% berkat tree-shaking tingkat komponen dan lazy loading granular dengan loadComponent
  • Unit test menjadi lebih sederhana dengan Vitest sebagai runner default, impor standalone component langsung di TestBed tanpa konfigurasi modul
  • OnPush default Angular 22 dan selectorless components berpadu secara alami dengan arsitektur standalone untuk codebase yang lebih bersih dan mudah dipelihara

Mulai berlatih!

Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.

Tantangan harian

Bisakah kamu menemukan bug di Angular?

Satu potongan kode nyata, satu bug tersembunyi, satu percobaan per hari. Tanpa akun untuk mencoba.

Anthony Fillion-Maillet

Ditulis oleh

Anthony Fillion-Maillet

Pendiri SharpSkill

Developer fullstack selama lebih dari 10 tahun. Ia menjalankan SharpSkill dan bertanggung jawab atas semua yang diterbitkan di sini.

Diperbarui 21 Agustus 2026

Tag

#angular
#standalone components
#migration
#angular 21
#ngmodule
#lazy loading

Bagikan

Artikel terkait