スタンドアロン コンポーネント: NgModules を使用しない Angular
の導入により、 スタンドアロンコンポーネント Angular 14 とそのプログレッシブ Angular 15 から標準として採用されたこのフレームワークは、アーキテクチャ上の画期的な進歩をもたらしました。 基本:使用義務の撤廃 Ngモジュール 整理する コード。この進化により、開発が劇的に簡素化され、定型文が削減され、 新しい開発者にとって Angular がさらにアクセスしやすくなりました。
このシリーズの 4 番目の記事では、 モダンアンギュラー スタンドアロンを探索してみます
コンポーネントの詳細: コンポーネントが解決する問題から完全な移行まで、
ルーティング、ブートストラップ、依存関係の注入、および高度なパターン。これまでに苦労したことがあるなら
declarations, imports e exports Angularモジュールでは、
この記事では、それを永久に削除する方法を説明します。
何を学ぶか
- NgModule が問題となる理由と、スタンドアロン コンポーネントが問題を解決する方法
- スタンドアロンのコンポーネント、ディレクティブ、パイプを作成する方法
- 依存関係を管理する
importsコンポーネントに直接 - モジュールを使用せずにルーティングと遅延読み込みを構成する
- アプリケーションをブートストラップする
bootstrapApplication() - スタンドアロン環境での依存関係の注入
- 公式回路図を使用して既存のプロジェクトを移行する
- エンタープライズ アプリケーション向けの高度なパターンとベスト プラクティス
最新の Angular シリーズの概要
| # | アイテム | 集中 |
|---|---|---|
| 1 | 角度信号 | きめ細かい応答性 |
| 2 | ゾーンレスの変更検出 | Zone.jsを削除する |
| 3 | 新しいテンプレート @if、@for、@defer | 最新の制御フロー |
| 4 | 現在位置 - スタンドアロン コンポーネント | NgModule を使用しないアーキテクチャ |
| 5 | 信号形式 | シグナルを使用したレスポンシブフォーム |
| 6 | SSR と増分水分補給 | サーバーサイドレンダリング |
| 7 | Angular におけるコア Web バイタル | パフォーマンスと指標 |
| 8 | 角度のある PWA | プログレッシブ Web アプリ |
| 9 | 高度な依存関係の注入 | DIツリーシェイク可能 |
| 10 | Angular 17 から 21 への移行 | 移行ガイド |
1. NgModules の問題
バージョン 2 以降、Angular は i を使用しています。 Ngモジュール 基本単位として
コード構成の。すべてのコンポーネント、ディレクティブ、またはパイプを宣言する必要がありました
アレイを介して正確に 1 つのモジュール内で declarations、およびモジュール間の依存関係
を通じて管理されました imports e exports。このシステムですが、
明確な構造を提供するため、アプリケーションとして問題が増加しています
それらはより複雑になりました。
// app.module.ts - L'approccio tradizionale
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { ReactiveFormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { AppComponent } from './app.component';
import { HeaderComponent } from './header/header.component';
import { FooterComponent } from './footer/footer.component';
import { HomeComponent } from './home/home.component';
import { ProductListComponent } from './products/product-list.component';
import { ProductDetailComponent } from './products/product-detail.component';
import { CartComponent } from './cart/cart.component';
import { CheckoutComponent } from './checkout/checkout.component';
import { HighlightDirective } from './directives/highlight.directive';
import { CurrencyPipe } from './pipes/currency.pipe';
@NgModule({
declarations: [
AppComponent,
HeaderComponent,
FooterComponent,
HomeComponent,
ProductListComponent,
ProductDetailComponent,
CartComponent,
CheckoutComponent,
HighlightDirective,
CurrencyPipe
],
imports: [
BrowserModule,
HttpClientModule,
ReactiveFormsModule,
RouterModule.forRoot(routes)
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
NgModules に関する一般的な問題
- 過剰な定型文: 各コンポーネントはモジュール内でインポートして宣言する必要があります。大規模なプロジェクトでは、宣言だけで数百行に及ぶことになります。
- 循環依存関係: 2 つのモジュールが相互にインポートすると、診断が難しいエラーが発生します
- 宣言とインポートの混乱: 新しい開発者は、コンポーネント (宣言) を配置する場所とモジュール (インポート) を配置する場所を常に混同します。
- エラー「コンポーネントはどの NgModule にも含まれていません」: 初心者にとって最もイライラする間違いの 1 つは、コンポーネントの宣言を忘れることによって引き起こされます。
- カップリング: コンポーネントはそれを宣言するモジュールに結合されているため、再利用や単体テストが困難になります
- 複雑な遅延読み込み: 機能を遅延してロードするには、ルーティング構成を備えた専用モジュールを作成する必要があります
多くの Angular プロジェクトを悩ませていた循環依存関係の典型的な例を考えてみましょう。
// shared.module.ts
@NgModule({
declarations: [UserAvatarComponent],
imports: [ProductModule], // Importa ProductModule
exports: [UserAvatarComponent]
})
export class SharedModule { }
// product.module.ts
@NgModule({
declarations: [ProductCardComponent],
imports: [SharedModule], // Importa SharedModule → CIRCOLARE!
exports: [ProductCardComponent]
})
export class ProductModule { }
NgModules とスタンドアロン: 簡単な比較
| 待ってます | Ngモジュール | スタンドアロン |
|---|---|---|
| コンポーネント宣言 | In declarations 形の |
自己完結型 standalone: true |
| 依存関係の管理 | 中間モジュール経由 | コンポーネント内で直接 |
| 遅延読み込み | 専用の NgModule が必要 | loadComponent() 直接 |
| 定型文 | 高 (フォーム + 宣言) | 最小 |
| 単体テスト | テストモジュールを備えたTestBed | 最小構成 |
| 循環依存関係 | モジュール間で頻繁に発生する | ほぼ不可能 |
| 学習曲線 | 急勾配 (宣言、インポート、エクスポート、プロバイダー、ブートストラップ) | Sweet (スタンドアロン: true、インポート) |
2. スタンドアロンコンポーネントとは何ですか
Uno スタンドアロンコンポーネント 自身を次のように宣言する Angular コンポーネントです
旗で自給自足 standalone: true デコレータで
@Component。これは、コンポーネントを宣言する必要がないことを意味します
NgModule ではありません: 依存関係を配列内で直接管理します imports.
から始まる 角度 19、すべてのコンポーネント、ディレクティブ、パイプは
スタンドアロン デフォルトでは。指定する必要すらありません standalone: true
それがデフォルトの動作だからです。これは、Angular が
スタンドアロン コンポーネントはフレームワークの将来であると考えてください。
// Angular 14-18: standalone esplicito
@Component({
selector: 'app-user-card',
standalone: true,
template: `<div class="user-card">
<h3>{{ user().name }}</h3>
<p>{{ user().email }}</p>
</div>`,
styles: [`
.user-card {
padding: 1rem;
border: 1px solid #ccc;
border-radius: 8px;
}
`]
})
export class UserCardComponent {
user = input.required<User>();
}
// Angular 19+: standalone e il default, non serve specificarlo
@Component({
selector: 'app-user-card',
template: `<div class="user-card">
<h3>{{ user().name }}</h3>
<p>{{ user().email }}</p>
</div>`,
styles: [`
.user-card {
padding: 1rem;
border: 1px solid #ccc;
border-radius: 8px;
}
`]
})
export class UserCardComponent {
user = input.required<User>();
}
スタンドアロンに関する注意: Angular 19 以降では true
Angular 19 以降、プロパティを省略した場合 standalone、コンポーネントはスタンドアロンです
デフォルトでは。非スタンドアロン コンポーネントを明示的に作成する場合 (互換性のため)
レガシーコードの場合)、指定する必要があります standalone: false。この中で
いつも使うアイテム standalone: true 明確にするために言いますが、
Angular 21 ではこれはオプションであることに注意してください。
3. スタンドアロンコンポーネント、ディレクティブ、パイプを作成する
「スタンドアロン」の概念はコンポーネントだけに適用されるものではありません。さえも 指令 そして パイプ と同じ原則に従って、スタンドアロンにすることができます。 自給自足。
スタンドアロンコンポーネント
import { Component, input, output, signal } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-todo-item',
standalone: true,
imports: [CommonModule], // Dipendenze gestite qui
template: `
<div class="todo-item" [class.completed]="todo().completed">
<input type="checkbox"
[checked]="todo().completed"
(change)="onToggle()" />
<span>{{ todo().title }}</span>
<button (click)="onDelete()">Elimina</button>
</div>
`
})
export class TodoItemComponent {
todo = input.required<Todo>();
toggled = output<number>();
deleted = output<number>();
onToggle() {
this.toggled.emit(this.todo().id);
}
onDelete() {
this.deleted.emit(this.todo().id);
}
}
スタンドアロンディレクティブ
import { Directive, ElementRef, input, effect } from '@angular/core';
@Directive({
selector: '[appHighlight]',
standalone: true
})
export class HighlightDirective {
appHighlight = input<string>('yellow');
constructor(private el: ElementRef) {
effect(() => {
this.el.nativeElement.style.backgroundColor = this.appHighlight();
});
}
}
独立したパイプ
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'timeAgo',
standalone: true
})
export class TimeAgoPipe implements PipeTransform {
transform(value: Date): string {
const seconds = Math.floor((new Date().getTime() - value.getTime()) / 1000);
if (seconds < 60) return 'pochi secondi fa';
if (seconds < 3600) return Math.floor(seconds / 60) + ' minuti fa';
if (seconds < 86400) return Math.floor(seconds / 3600) + ' ore fa';
return Math.floor(seconds / 86400) + ' giorni fa';
}
}
4. インポートによる依存関係の管理
スタンドアロン コンポーネントの鍵となるのは配列です imports デコレータで
@Component。ここで彼らは自分自身を宣言します すべてのテンプレートの依存関係:
他のコンポーネント、ディレクティブ、パイプ、さらには整数の NgModule (ライブラリとの互換性のため)
レガシー)。各コンポーネントは、仲介者を介さずに、必要なものを正確に認識します。
import { Component } from '@angular/core';
import { RouterLink, RouterOutlet } from '@angular/router';
import { AsyncPipe, DatePipe, CurrencyPipe } from '@angular/common';
import { ReactiveFormsModule } from '@angular/forms';
// Importa componenti standalone direttamente
import { UserCardComponent } from './user-card.component';
import { LoadingSpinnerComponent } from '../shared/loading-spinner.component';
import { HighlightDirective } from '../directives/highlight.directive';
import { TimeAgoPipe } from '../pipes/time-ago.pipe';
@Component({
selector: 'app-dashboard',
standalone: true,
imports: [
// Moduli Angular (per compatibilità)
ReactiveFormsModule,
// Pipe standalone
AsyncPipe,
DatePipe,
CurrencyPipe,
TimeAgoPipe,
// Componenti standalone
UserCardComponent,
LoadingSpinnerComponent,
// Direttive standalone
HighlightDirective,
// Router
RouterLink,
RouterOutlet
],
templateUrl: './dashboard.component.html'
})
export class DashboardComponent {
// Il componente conosce tutte le sue dipendenze
}
比較: NgModules とスタンドアロンの依存関係
| NgModulesを使用する | スタンドアロンの場合 |
|---|---|
| モジュール内で宣言されたコンポーネント | 自立したコンポーネント |
| モジュールにインポートされた依存関係 | コンポーネントにインポートされた依存関係 |
| すべてのモジュール宣言はインポートを参照 | コンポーネントのみがそのインポートを参照します |
| モジュール内の未使用のインポートのリスク | 各インポートは明示的であり、必要です |
注意: 必要以上にインポートしないでください
スタンドアロン コンポーネントを使用すると、インポートしたくなるかもしれません。 CommonModule
便宜上全体。ただし、リストされている個々のパイプとディレクティブのみをインポートすることをお勧めします。
必要です(例: NgIf, NgFor, AsyncPipe)。
新しい構文では @if e @for Angular 17+ による、
制御フローはテンプレートにネイティブであるため、これらのインポートの多くは必要なくなりました。
5. NgModules を使用しないルーティング
スタンドアロン コンポーネントの最大の利点の 1 つは、 ルーティングの簡素化。
モジュールを使用すると、機能を遅延ロードするには、専用のモジュールを作成する必要がありました。
RouterModule.forChild()。スタンドアロン コンポーネントを使用すると、遅延読み込みを行うことができます
単一コンポーネントを直接使用する loadComponent.
loadComponent: 単一コンポーネントの遅延ロード
// app.routes.ts - Configurazione routing completa senza moduli
import { Routes } from '@angular/router';
export const routes: Routes = [
{
path: '',
loadComponent: () =>
import('./home/home.component').then(m => m.HomeComponent)
},
{
path: 'products',
loadComponent: () =>
import('./products/product-list.component').then(m => m.ProductListComponent)
},
{
path: 'products/:id',
loadComponent: () =>
import('./products/product-detail.component').then(m => m.ProductDetailComponent)
},
{
path: 'cart',
loadComponent: () =>
import('./cart/cart.component').then(m => m.CartComponent)
},
{
path: '**',
loadComponent: () =>
import('./not-found/not-found.component').then(m => m.NotFoundComponent)
}
];
loadChildren: ルート グループの遅延読み込み
サブルートのある複雑なフィーチャの場合は、これを使用できます。 loadChildren を指さす
の配列をエクスポートするファイル Routes、ラッパーモジュールは必要ありません。
// app.routes.ts
export const routes: Routes = [
{
path: 'admin',
loadChildren: () =>
import('./admin/admin.routes').then(m => m.ADMIN_ROUTES)
}
];
// admin/admin.routes.ts - File di route standalone
import { Routes } from '@angular/router';
export const ADMIN_ROUTES: Routes = [
{
path: '',
loadComponent: () =>
import('./admin-layout.component').then(m => m.AdminLayoutComponent),
children: [
{
path: 'dashboard',
loadComponent: () =>
import('./dashboard/dashboard.component').then(m => m.DashboardComponent)
},
{
path: 'users',
loadComponent: () =>
import('./users/users.component').then(m => m.UsersComponent)
},
{
path: 'settings',
loadComponent: () =>
import('./settings/settings.component').then(m => m.SettingsComponent)
}
]
}
];
遅延読み込み: NgModules とスタンドアロン
| アプローチ | Ngモジュール | スタンドアロン |
|---|---|---|
| 単一コンポーネントの遅延ロード | ラッパーモジュールがないと不可能 | loadComponent: () => import(...) |
| 遅延ロード機能 | loadChildren NgModuleを使用 |
loadChildren Routes 配列を使用 |
| 必要なファイル | feature.module.ts + feature-routing.module.ts | feature.routes.ts (のみ) |
| 粒度 | モジュールレベルで | 個々のコンポーネントレベルで |
6. bootstrapApplication() によるブートストラップ
スタンドアロン コンポーネントを使用すると、アプリケーションの起動方法も劇的に変わります。
もう必要ありません AppModule メソッドを使って platformBrowserDynamic().bootstrapModule()。
代わりに使用されます bootstrapApplication()、ルートコンポーネントと
プロバイダーの構成。
NgModule を使用した古いアプローチ
// main.ts - Approccio tradizionale
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));
新しいスタンドアロンアプローチ
// main.ts - Approccio standalone
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.ts
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter, withViewTransitions } from '@angular/router';
import { provideHttpClient, withInterceptors, withFetch } from '@angular/common/http';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { provideClientHydration, withEventReplay } from '@angular/platform-browser';
import { routes } from './app.routes';
import { authInterceptor } from './interceptors/auth.interceptor';
import { errorInterceptor } from './interceptors/error.interceptor';
export const appConfig: ApplicationConfig = {
providers: [
// Change detection (o provideExperimentalZonelessChangeDetection())
provideZoneChangeDetection({ eventCoalescing: true }),
// Router con view transitions
provideRouter(routes, withViewTransitions()),
// HTTP client con interceptors funzionali
provideHttpClient(
withFetch(),
withInterceptors([authInterceptor, errorInterceptor])
),
// Animazioni asincrone
provideAnimationsAsync(),
// Hydration per SSR
provideClientHydration(withEventReplay())
]
};
提供する機能*
スタンドアロンのアプローチにより、Angular は 機能を提供* それ これらは古い構成モジュールを置き換えます。主なものは次のとおりです。
| 機能を提供* | 置き換えます |
|---|---|
provideRouter() |
RouterModule.forRoot() |
provideHttpClient() |
HttpClientModule |
provideAnimationsAsync() |
BrowserAnimationsModule |
provideClientHydration() |
モジュール内のSSR構成 |
provideZoneChangeDetection() |
Zone.js の暗黙的な構成 |
7. スタンドアロン環境での依存関係の注入
Angular の依存性注入 (DI) はスタンドアロン コンポーネントとうまく連携します。 ただし、アプリケーションのさまざまなレベルでプロバイダーを構成するための新しいパターンが提供されます。
providedIn: 'root' - 推奨パターン
サービスを提供する最も簡単かつ最も推奨される方法は、
providedIn: 'root' デコレータで @Injectable。
これにより、ルート インジェクターにサービスがシングルトンとして登録され、どこでも利用できるようになります。
アプリケーションで。利点は、コンポーネントが存在しない場合、サービスがツリーシェイキング可能であることです。
注入すると、実稼働バンドルから削除されます。
import { Injectable, signal } from '@angular/core';
@Injectable({
providedIn: 'root' // Singleton tree-shakable
})
export class CartService {
private _items = signal<CartItem[]>([]);
readonly items = this._items.asReadonly();
readonly totalItems = computed(() => this._items().length);
readonly totalPrice = computed(() =>
this._items().reduce((sum, item) => sum + item.price * item.quantity, 0)
);
addItem(product: Product) {
this._items.update(items => [...items, {
...product,
quantity: 1
}]);
}
removeItem(id: number) {
this._items.update(items => items.filter(item => item.id !== id));
}
}
ルートレベルプロバイダー
機能内でのみ共有する必要があるサービスの場合は、サービスを定義できます。 プロバイダーをルート設定に直接追加します。これにより、 環境 インジェクター アプリケーションのそのセクションに特化しています。
// admin/admin.routes.ts
export const ADMIN_ROUTES: Routes = [
{
path: '',
providers: [
// Questo servizio è disponibile SOLO nei componenti admin
AdminAuthService,
// Anche i provider configurabili funzionano
{
provide: API_BASE_URL,
useValue: '/api/admin'
}
],
children: [
{
path: 'dashboard',
loadComponent: () =>
import('./dashboard.component').then(m => m.DashboardComponent)
},
{
path: 'users',
loadComponent: () =>
import('./users.component').then(m => m.UsersComponent)
}
]
}
];
コンポーネントレベルプロバイダー
@Component({
selector: 'app-editor',
standalone: true,
providers: [
// Nuova istanza per ogni componente editor
EditorStateService,
{
provide: UNDO_STACK_SIZE,
useValue: 50
}
],
template: `...`
})
export class EditorComponent {
private editorState = inject(EditorStateService);
}
スタンドアロン世界のプロバイダー階層
| レベル | 構成 | ほうき | 使用事例 |
|---|---|---|---|
| Root | providedIn: 'root' |
アプリ全体 (シングルトン) | AuthService、HttpService、StateStore |
| アプリ構成 | appConfig.providers |
アプリ全体 | ルーター、HTTPクライアント、アニメーション |
| ルート | route.providers |
特集・セクション | AdminService、FeatureConfig |
| 成分 | component.providers |
単一コンポーネント | フォームステート、エディターステート |
8. NgModules からスタンドアロンへの移行
Angular が提供するのは 公式移行図 それは自動化します NgModules からスタンドアロン コンポーネントへの変換プロセスの大部分。移行 3 つの段階的なフェーズで行われるため、安全かつ漸進的な方法で作業を進めることができます。
ステップ 1: コンポーネントをスタンドアロンに変換する
最初のステップでは、すべてのコンポーネント、ディレクティブ、およびパイプをスタンドアロンに変換し、追加します。
standalone: true 彼らは imports 必要。
# Fase 1: Converti componenti, direttive e pipe in standalone
ng generate @angular/core:standalone-migration --mode=convert-to-standalone
# Cosa fa:
# - Aggiunge standalone: true a tutti i componenti/direttive/pipe
# - Aggiunge gli imports necessari nel decoratore di ogni componente
# - Mantiene i NgModules esistenti per compatibilità
ステップ 2: 不要な NgModule を削除する
# Fase 2: Rimuovi i NgModules che non sono più necessari
ng generate @angular/core:standalone-migration --mode=prune-ng-modules
# Cosa fa:
# - Rimuove i NgModules che contengono solo declarations standalone
# - Aggiorna le importazioni nei file che li riferivano
# - Mantiene i moduli che definiscono providers o altro
ステップ 3: bootstrapApplication() に移動します。
# Fase 3: Converti il bootstrap dell'applicazione
ng generate @angular/core:standalone-migration --mode=standalone-bootstrap
# Cosa fa:
# - Converte main.ts per usare bootstrapApplication()
# - Crea app.config.ts con i provider
# - Rimuove AppModule se non più necessario
# - Migra i provider dal modulo alla configurazione
安全な移行のためのヒント
- 各ステージの前にコミットを作成します。 何か問題が発生した場合でも簡単に戻ることができるので、
- 各段階の後にテストを実行します。 続行する前に、アプリケーションが適切に動作していることを確認してください
- 循環依存関係を確認します。 移行により、モジュール内の隠れた循環依存関係が浮き彫りになる可能性がある
- プロバイダーを確認します。 一部のモジュールはプロバイダーを定義します
forRoot()eforChild()手動による注意が必要なもの - サードパーティのライブラリ: 使用するライブラリがスタンドアロンをサポートしていることを確認してください。ほとんどの場合、コンポーネントをスタンドアロンとしてエクスポートするか、関数を提供することでこれを実現します。
provide*()
段階的な手動移行
手動で移行する場合は、コンポーネントを変換する一般的なプロセスを次に示します。
// PRIMA: product-card.component.ts (in un NgModule)
@Component({
selector: 'app-product-card',
templateUrl: './product-card.component.html'
})
export class ProductCardComponent {
@Input() product!: Product;
@Output() addToCart = new EventEmitter<Product>();
}
// product.module.ts
@NgModule({
declarations: [ProductCardComponent, ProductListComponent],
imports: [CommonModule, RouterModule],
exports: [ProductCardComponent]
})
export class ProductModule { }
// DOPO: product-card.component.ts (standalone)
import { Component, input, output } from '@angular/core';
import { RouterLink } from '@angular/router';
import { CurrencyPipe } from '@angular/common';
@Component({
selector: 'app-product-card',
standalone: true,
imports: [RouterLink, CurrencyPipe],
templateUrl: './product-card.component.html'
})
export class ProductCardComponent {
product = input.required<Product>();
addToCart = output<Product>();
}
// product.module.ts → ELIMINATO!
// Le route puntano direttamente ai componenti
9. 高度なパターン
共有コンポーネントのバレルエクスポート
多くの共有スタンドアロン コンポーネントがある場合、バレルを作成すると便利です。
(index.ts) すべてを再エクスポートします。これによりインポートが簡素化されます
それらを使用するコンポーネント内で。
// shared/ui/index.ts - Barrel export
export { ButtonComponent } from './button/button.component';
export { CardComponent } from './card/card.component';
export { ModalComponent } from './modal/modal.component';
export { TabsComponent } from './tabs/tabs.component';
export { TooltipDirective } from './tooltip/tooltip.directive';
export { TimeAgoPipe } from './pipes/time-ago.pipe';
export { TruncatePipe } from './pipes/truncate.pipe';
// Utilizzo nel componente:
import {
ButtonComponent,
CardComponent,
TooltipDirective,
TruncatePipe
} from '../shared/ui';
@Component({
standalone: true,
imports: [ButtonComponent, CardComponent, TooltipDirective, TruncatePipe],
// ...
})
export class MyFeatureComponent { }
共通インポートの配列を作成する
複数のコンポーネントが同じ依存関係を共有する場合、インポートの配列を作成できます。 再利用するのが一般的です。これは、SharedModules に代わる軽量のパターンです。
// shared/common-imports.ts
import { RouterLink } from '@angular/router';
import { DatePipe, CurrencyPipe, AsyncPipe } from '@angular/common';
import { ButtonComponent } from './ui/button/button.component';
import { CardComponent } from './ui/card/card.component';
import { LoadingSpinnerComponent } from './ui/loading-spinner/loading-spinner.component';
export const COMMON_IMPORTS = [
RouterLink,
DatePipe,
CurrencyPipe,
AsyncPipe,
ButtonComponent,
CardComponent,
LoadingSpinnerComponent
] as const;
// Utilizzo:
@Component({
standalone: true,
imports: [...COMMON_IMPORTS, ReactiveFormsModule],
// ...
})
export class CheckoutComponent { }
@defer を使用した詳細な遅延読み込み
スタンドアロン コンポーネントとブロックの組み合わせ @defer Angular 17 で導入されました。
テンプレート レベルでさらに詳細な遅延読み込みを行うことができます。
<!-- Il componente HeavyChart viene caricato solo quando entra nel viewport -->
@defer (on viewport) {
<app-heavy-chart [data]="chartData()" />
} @placeholder {
<div class="chart-placeholder">Grafico in caricamento...</div>
} @loading (minimum 500ms) {
<app-loading-spinner />
}
<!-- Il componente Comments viene caricato solo all'interazione -->
@defer (on interaction) {
<app-comments [postId]="postId()" />
} @placeholder {
<button>Mostra commenti</button>
}
細分化された遅延読み込みの利点
NgModules では、遅延読み込みの最小粒度はモジュール全体であり、
そのすべてのコンポーネント。スタンドアロン コンポーネントを使用すると、個々のコンポーネントは次のことができます。
ルーター経由で独立してロードできます(loadComponent)
それはテンプレートを介して(@defer)。これにより、
初期バンドルが追加され、読み込み時間が短縮されます。
10. ベストプラクティス
スタンドアロン コンポーネントのすべての機能を調べた後、ベスト プラクティスを以下に示します。 最新の Angular アプリケーションでコードをより適切に整理するには、次の手順に従います。
スタンドアロン コンポーネントの 10 の黄金律
- デフォルトではスタンドアロン: 必ずスタンドアロン コンポーネントを作成してください。 NgModules はレガシー ライブラリとの互換性のためにのみ使用してください
- 必要なものだけをインポートします。 モジュール全体を次のようにインポートすることは避けてください。
CommonModule。個々のパイプとディレクティブをインポートする - 新しいテンプレート構文を使用します。 Con
@if,@fore@switchインポートする必要はなくなりましたNgIf,NgFor,NgSwitch - 1 つのコンポーネント、1 つのファイル: 各スタンドアロン コンポーネントには独自のファイルが必要です。同じファイル内で複数のコンポーネントを宣言しないようにする
- フィーチャのバレル エクスポート: ファイルを使用する
index.ts機能の共有コンポーネントを再エクスポートするには - コンポーネントではなくルート内のプロバイダー: 機能サービスの場合は、
route.providersと比べてcomponent.providers - providedIn: グローバル サービスの 'root': 保つ
providedIn: 'root'シングルトンおよびツリーシェイク可能なサービス向け - 積極的な遅延読み込み: アメリカ合衆国
loadComponentルーターe内@deferテンプレート内でコンポーネントをオンデマンドでロードする - 段階的に移行します。 すべてを一度に移行する必要はありません。スタンドアロンと NgModule は無期限に共存可能
- 各コンポーネントを個別にテストします。 スタンドアロンの大きな利点は、テストが容易なことです。ぜひ活用してください!
推奨されるプロジェクト構造
src/app/
app.component.ts # Root component (standalone)
app.config.ts # ApplicationConfig con providers
app.routes.ts # Route principali
core/ # Servizi singleton e guard
services/
auth.service.ts # providedIn: 'root'
api.service.ts # providedIn: 'root'
interceptors/
auth.interceptor.ts
guards/
auth.guard.ts
shared/ # Componenti, direttive, pipe riutilizzabili
ui/
button/
button.component.ts # standalone
card/
card.component.ts # standalone
modal/
modal.component.ts # standalone
index.ts # barrel export
directives/
tooltip.directive.ts # standalone
pipes/
time-ago.pipe.ts # standalone
features/ # Feature dell'applicazione
products/
product-list.component.ts # standalone
product-detail.component.ts # standalone
product-card.component.ts # standalone (locale)
products.routes.ts # sotto-route
admin/
admin-layout.component.ts
dashboard.component.ts
admin.routes.ts
スタンドアロンコンポーネントのテスト
最も重要な利点の 1 つは、テストの簡素化です。もう設定する必要はありません
複雑なテストモジュール TestBed: コンポーネントはすべてをもたらします。
その依存関係。
// product-card.component.spec.ts
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ProductCardComponent } from './product-card.component';
describe('ProductCardComponent', () => {
let fixture: ComponentFixture<ProductCardComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
// Con standalone, basta importare il componente!
imports: [ProductCardComponent]
// Non serve: declarations, imports di moduli, etc.
}).compileComponents();
fixture = TestBed.createComponent(ProductCardComponent);
});
it('dovrebbe mostrare il nome del prodotto', () => {
// Usa ComponentRef per impostare gli input
fixture.componentRef.setInput('product', {
id: 1,
name: 'Angular T-Shirt',
price: 29.99
});
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.textContent).toContain('Angular T-Shirt');
});
it('dovrebbe emettere addToCart quando si clicca il pulsante', () => {
fixture.componentRef.setInput('product', {
id: 1,
name: 'Angular T-Shirt',
price: 29.99
});
fixture.detectChanges();
const spy = jest.spyOn(fixture.componentInstance.addToCart, 'emit');
const button = fixture.nativeElement.querySelector('button');
button.click();
expect(spy).toHaveBeenCalledWith(expect.objectContaining({
id: 1
}));
});
});
NgModules をいつメンテナンスするか
スタンドアロン コンポーネントは未来ですが、NgModule が まだ役に立つ、または必要なもの:
- サードパーティのライブラリ: 一部のライブラリは依然として NgModules をエクスポートしています。配列に直接インポートできます
importsスタンドアロンコンポーネントの - 大規模なレガシー プロジェクト: プロジェクトに数百のコンポーネントがある場合、移行は段階的に行うことができます。すぐにすべてを行う必要はありません
- 多くの依存関係を持つ SharedModule: 数十のコンポーネントをインポートする SharedModule がある場合は、それを一時的に保持し、準備ができたらバレル エクスポートに置き換える方が現実的かもしれません。
概要と次のステップ
この記事では、Angular のスタンドアロン コンポーネントについて詳しく説明しました。 動機から移行、ルーティングから依存関係の注入まで。重要なポイントは次のとおりです。
主要な概念
- Gli スタンドアロンコンポーネント NgModule の必要性を排除し、各コンポーネントを自立させます。
- Da 角度 19、スタンドアロンは、コンポーネント、ディレクティブ、およびパイプのデフォルトの動作です。
- 配列
importsデコレータを使用すると、コンポーネント内で依存関係を直接宣言できます。 loadComponenteloadChildrenラッパーモジュールを使用せずに遅延読み込みを簡素化します。bootstrapApplication()eApplicationConfigNgModule 経由でブートストラップを置き換えます- 機能
provide*()構成モジュールを交換します (RouterModule.forRoot()、など) - La DI ルート、アプリ構成、ルート、コンポーネントの 4 つのレベルで機能します。
- Lo 移行の概略図 3 つの段階的なステップで変換を自動化します
- I バレル輸出 彼らは 共通インポートの配列 SharedModule を置き換えます
シリーズの次の記事では、 信号形式、その方法を見てみましょう シグナル革命は、現代的な代替手段を備えたフォーム管理にも拡張されます きめ細かい反応性を使用して高性能フォームを作成する Reactive Forms へ そして宣言文。







