新しい Angular テンプレート: @if、@for、@defer
Angular 17 では、チームは新しいシステムを導入しました テンプレート内の制御フロー
これは歴史的な構造指令に代わるものです *ngIf, *ngFor e
ngSwitch。新しい構文 @if, @for,
@switch e @defer 根本的な変化を表しています
Angular テンプレートの作成方法: より読みやすく、よりパフォーマンスが高く、機能も充実
のように完全に新しい 宣言的な遅延読み込み コンポーネントの。
このシリーズの 3 番目の記事では、 モダンアンギュラー で分析します 各構成の深さ: 基本構文から高度なパターン、移行まで コミュニティによって統合されたベスト プラクティスに自動的に反映されます。まだ使用している場合は、 このガイドでは、古典的な構造ディレクティブを移行する時期が来た理由を説明します。
最新の 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 への移行 | 移行ガイド |
なぜ新しい構文なのか?構造指令の限界
構造指令 *ngIf, *ngFor e ngSwitch
Angular をほぼ 10 年間提供してきましたが、次のような構造上の制限があります。
新しい構文はそれを完全に修正します。
古典的な構造指令の問題点
- 不透明なマイクロ構文: のような表現
*ngFor="let item of items; trackBy: trackFn; let i = index; let first = first"読むのも覚えるのも難しい - ネイティブの else-if はありません: Con
*ngIfありませんelse if: さらにネストする必要があります*ngIfまたは使用しますng-templateconelse - バンドル内の重量: 構造ディレクティブは CommonModule モジュールからインポートされた Angular クラスであり、バンドルに含める必要があります。
- 暗黙的なトラック:
*ngFor関数の定義を強制するものではありませんtrackByサイレントパフォーマンスの問題を引き起こす - 遅延読み込みなし: オンデマンド コンポーネントをテンプレートから直接ロードするための宣言メカニズムはありません。
- 詳細度: 単純な if/else が必要な場合
ng-templateテンプレート参照変数を使用すると、ボイラープレートが増加します
可読性の違いを示す具体的な例を見てみましょう。
<!-- Vecchia sintassi: verbose e frammentata -->
<div *ngIf="user; else loadingTpl">
<h2>{{ user.name }}</h2>
<div *ngIf="user.role === 'admin'; else userTpl">
<span class="badge admin">Amministratore</span>
</div>
<ng-template #userTpl>
<span class="badge user">Utente</span>
</ng-template>
</div>
<ng-template #loadingTpl>
<div class="spinner">Caricamento...</div>
</ng-template>
<!-- Nuova sintassi: chiara e lineare -->
@if (user) {
<h2>{{ user.name }}</h2>
@if (user.role === 'admin') {
<span class="badge admin">Amministratore</span>
} @else {
<span class="badge user">Utente</span>
}
} @else {
<div class="spinner">Caricamento...</div>
}
新しい構文の利点の概要
| 待ってます | 構造指令 | 新しい制御フロー |
|---|---|---|
| 必要な輸入品 | CommonModule または単一インポート | なし (コンパイラに組み込まれています) |
| それ以外の場合 | ネイティブではサポートされていません | ネイティブの場合は @else |
| 必須トラック | いいえ (オプション) | はい (@for で必須) |
| 遅延読み込み | 利用不可 | 宣言型トリガーを使用した @defer |
| バンドルサイズ | バンドルに含まれるディレクティブ | ゼロオーバーヘッド (コンパイル済み) |
| 型チェック | 限定 | 完了 |
| 可読性 | 複雑なマイクロ構文 | JavaScript/TypeScriptに似ている |
@if と @else: 最新の条件付きレンダリング
ブロック @if 置き換える *ngIf という構文で
JavaScript の制御構造に似ています。ネイティブにサポート @else if
e @elseの必要性を排除します。 ng-template.
基本的な構文
<!-- Condizione semplice -->
@if (isLoggedIn()) {
<app-dashboard />
}
<!-- If con else -->
@if (user()) {
<h2>Benvenuto, {{ user().name }}</h2>
} @else {
<app-login-form />
}
<!-- If / else if / else -->
@if (status() === 'loading') {
<div class="spinner">Caricamento in corso...</div>
} @else if (status() === 'error') {
<div class="error">Si è verificato un errore</div>
} @else if (status() === 'empty') {
<div class="empty">Nessun risultato trovato</div>
} @else {
<app-data-table [data]="data()" />
}
キーワード「as」を含むエイリアス
の強力な機能 @if を割り当てる機能と、
条件の結果のエイリアス。これは特に仕事をするときに便利です
Signal または Observable は null 許容値を返します。
<!-- Salva il risultato della condizione in una variabile locale -->
@if (currentUser(); as user) {
<div class="profile">
<h3>{{ user.name }}</h3>
<p>Email: {{ user.email }}</p>
<p>Ruolo: {{ user.role }}</p>
</div>
}
<!-- Utile con pipe asincrone o trasformazioni -->
@if (users() | filterActive; as activeUsers) {
<p>Utenti attivi: {{ activeUsers.length }}</p>
}
*ngIf との主な違い
@ifインポートは必要ありません (これはディレクティブではなく、コンパイラの一部です)@else ifネイティブにサポートされているため、ng-template入れ子になった- 構文
as条件の真の値のエイリアスとして機能します - 型の絞り込みが正しく機能する: ブロック内
@ifタイプはすでに制限されています - もう必要ありません
CommonModuleまたは直輸入NgIf
トラック付き @for: 実行と必須の反復
ブロック @for 置き換える *ngFor 変化を導入する
基本: 式 トラックは必須です。このデザインの選択
開発者は、Angular が各要素を一意に識別する方法を定義する必要があります。
リストを使用して、パフォーマンスの問題を防止します。 *ngFor 彼らは気づかれなかった。
基本的な構文
<!-- Iterazione base con track per id -->
@for (product of products(); track product.id) {
<div class="product-card">
<h3>{{ product.name }}</h3>
<p>{{ product.price | currency:'EUR' }}</p>
</div>
}
<!-- Con variabili implicite -->
@for (item of items(); track item.id; let i = $index; let isFirst = $first; let isLast = $last) {
<div class="item" [class.first]="isFirst" [class.last]="isLast">
<span class="index">{{ i + 1 }}.</span>
<span>{{ item.name }}</span>
</div>
}
<!-- Track per indice (per liste senza id univoco) -->
@for (label of labels(); track $index) {
<span class="tag">{{ label }}</span>
}
@for で使用できる暗黙的変数
| 変数 | タイプ | 説明 |
|---|---|---|
$index | 番号 | 現在のインデックス (0 ベース) |
$first | ブール値 | 最初の要素の場合は true |
$last | ブール値 | 最後の要素の場合は true |
$even | ブール値 | インデックスが偶数の場合は true |
$odd | ブール値 | インデックスが奇数の場合は true |
$count | 番号 | 総アイテム数 |
トラックは必須なので
Angular がリストを更新するときは、どの DOM 要素を再作成するかを決定する必要があります。
どれを移動し、どれを変更しないでください。表現 track Angular に提供します
各要素の安定したアイデンティティにより、最適な更新が可能になります。
// Componente con lista aggiornata frequentemente
@Component({
selector: 'app-live-feed',
standalone: true,
template: `
<!-- CORRETTO: track per identità unica -->
<!-- Quando la lista cambia, Angular riusa i nodi DOM esistenti -->
@for (post of posts(); track post.id) {
<app-post-card [post]="post" />
}
<!-- SCONSIGLIATO: track per indice -->
<!-- Se un elemento viene aggiunto in cima, TUTTI i nodi vengono ricreati -->
@for (post of posts(); track $index) {
<app-post-card [post]="post" />
}
`
})
export class LiveFeedComponent {
posts = signal<Post[]>([]);
addNewPost(post: Post) {
// Con track post.id: solo 1 nodo DOM creato
// Con track $index: N nodi DOM ricreati
this.posts.update(list => [post, ...list]);
}
}
$index をトラックとして使用する場合
アメリカ合衆国 track $index リストの要素に ID がない場合のみ
安定しています (たとえば、単純な文字列の配列)。それ以外のすべての場合は、
常に次のような一意のプロパティ id。使用 $index リストに載っている
頻繁に変更されることは、パフォーマンス低下の主な原因の 1 つです。
Angular アプリケーション。
@switch: 複数選択の消去
ブロック @switch ディレクティブの組み合わせを置き換えます
ngSwitch, *ngSwitchCase e *ngSwitchDefault
より自然で読みやすい構文を使用します。
<!-- PRIMA: ngSwitch (verboso, richiede contenitore) -->
<div [ngSwitch]="orderStatus">
<div *ngSwitchCase="'pending'">
<span class="badge warning">In Attesa</span>
</div>
<div *ngSwitchCase="'processing'">
<span class="badge info">In Lavorazione</span>
</div>
<div *ngSwitchCase="'shipped'">
<span class="badge success">Spedito</span>
</div>
<div *ngSwitchCase="'delivered'">
<span class="badge success">Consegnato</span>
</div>
<div *ngSwitchDefault>
<span class="badge">Sconosciuto</span>
</div>
</div>
<!-- DOPO: @switch (pulito, senza contenitore) -->
@switch (orderStatus()) {
@case ('pending') {
<span class="badge warning">In Attesa</span>
}
@case ('processing') {
<span class="badge info">In Lavorazione</span>
}
@case ('shipped') {
<span class="badge success">Spedito</span>
}
@case ('delivered') {
<span class="badge success">Consegnato</span>
}
@default {
<span class="badge">Sconosciuto</span>
}
}
@switchに関する注意事項
@switch厳密な比較を使用します (===)、JavaScript と同じ- コンテナ要素は必要ありません。比較は式に対して直接行われます。
@defaultオプションですが、予期しないケースに対処するために推奨されます- フォールスルーをサポートしません: すべて
@case孤立している(望ましい行動) - とは異なり
ngSwitch、からの輸入はありませんCommonModule
@defer: 遅延読み込み宣言
@defer これは、新しいテンプレート構文によってもたらされた本当の革命です。
コンポーネント、ディレクティブ、パイプをロードできます。 オンデマンド 直接
遅延読み込み用の TypeScript コードを必要とせずに、テンプレートから取得できます。コンパイラ
Angular は内部の参照コードを自動的に分離します @defer
個別のチャンクに分割され、指定されたトリガーが起動した場合にのみダウンロードされます。
基本的な構文と関連ブロック
@defer (on viewport) {
<!-- Contenuto caricato lazily -->
<app-heavy-chart [data]="chartData()" />
} @placeholder {
<!-- Mostrato prima che il caricamento inizi -->
<div class="chart-placeholder">
<p>Il grafico apparira qui</p>
</div>
} @loading (after 150ms; minimum 300ms) {
<!-- Mostrato durante il caricamento del chunk -->
<div class="spinner">Caricamento grafico...</div>
} @error {
<!-- Mostrato se il caricamento fallisce -->
<div class="error">Impossibile caricare il grafico</div>
}
@defer に関連付けられたブロック
| ブロック | 範囲 | オプション |
|---|---|---|
@placeholder |
トリガーが発動する前に表示されるコンテンツ | minimum: 最低視聴時間 |
@loading |
チャンクのダウンロード中に表示されるコンテンツ | after: 表示する前に遅延します。 minimum: 最小持続時間 |
@error |
ロードに失敗した場合に表示されるコンテンツ | オプションなし |
利用可能なトリガー
@defer いつ開始するかを決定するいくつかのトリガーをサポートします
コンテンツの遅延読み込み。論理演算子と組み合わせることができます
洗練された積載条件を作成します。
<!-- 1. on viewport: carica quando l'elemento entra nel viewport -->
@defer (on viewport) {
<app-comments [postId]="postId()" />
} @placeholder {
<div style="height: 200px">Scorri per vedere i commenti</div>
}
<!-- 2. on idle: carica quando il browser è inattivo -->
@defer (on idle) {
<app-analytics-widget />
}
<!-- 3. on interaction: carica al click/focus su un elemento -->
<button #loadBtn>Mostra dettagli</button>
@defer (on interaction(loadBtn)) {
<app-product-details [id]="productId()" />
} @placeholder {
<p>Clicca il pulsante per vedere i dettagli</p>
}
<!-- 4. on hover: carica quando il mouse passa sopra un elemento -->
<div #previewArea>Passa il mouse per l'anteprima</div>
@defer (on hover(previewArea)) {
<app-image-preview [url]="imageUrl()" />
}
<!-- 5. on timer: carica dopo un intervallo di tempo -->
@defer (on timer(5s)) {
<app-newsletter-popup />
}
<!-- 6. when: carica quando una condizione diventa true -->
@defer (when isAuthenticated()) {
<app-admin-panel />
} @placeholder {
<p>Effettua il login per accedere al pannello admin</p>
}
<!-- 7. Combinazione di trigger -->
@defer (on viewport; on timer(10s)) {
<app-sidebar-ads />
} @placeholder {
<div class="ad-placeholder"></div>
}
トリガーの概要 @defer
| トリガー | アクティベーション | 典型的な使用例 |
|---|---|---|
on viewport |
プレースホルダー要素がビューポートに入ります | スクロールせずに見えるコンテンツ、コメント、フッター |
on idle |
ブラウザはアイドル状態です (requestIdleCallback) | 重要ではないウィジェット、分析 |
on interaction |
特定の要素をクリックまたはフォーカスします | 拡張可能なパネル、詳細はオンデマンド |
on hover |
特定の項目の上にマウスを移動します | 複雑なツールチップ、プレビュー |
on timer(Ns) |
N秒/ミリ秒後 | ポップアップ、遅延コンテンツ |
when condizione |
その表現が真実になるとき | ステータスベースのコンテンツ (ログイン、フラグ) |
インテリジェントなプリフェッチ
@defer もサポートしています 先読み: 始めることができます
チャンクをダウンロードするには 前に レンダリング トリガーがアクティブになることを確認します。これは良くなります
必要なときにコンテンツが用意されているため、ユーザー エクスペリエンスが向上します。
<!-- Prefetch on idle, render on viewport -->
<!-- Il browser scarica il chunk quando è inattivo, -->
<!-- ma lo rende visibile solo quando entra nel viewport -->
@defer (on viewport; prefetch on idle) {
<app-heavy-data-grid [data]="gridData()" />
} @placeholder {
<div class="grid-skeleton">
<div class="skeleton-row"></div>
<div class="skeleton-row"></div>
<div class="skeleton-row"></div>
</div>
}
<!-- Prefetch on hover, render on interaction -->
<!-- L'utente passa il mouse e il chunk si scarica, -->
<!-- quando clicca il contenuto appare istantaneamente -->
<button #editBtn>Modifica Profilo</button>
@defer (on interaction(editBtn); prefetch on hover(editBtn)) {
<app-profile-editor [user]="currentUser()" />
} @placeholder {
<p>Clicca per modificare il profilo</p>
}
@empty: 空のリストをエレガントに管理する
ブロック @empty の仲間です @for これにより、次のことが可能になります
反復されたコレクションが空の場合に代替コンテンツを表示します。新しいのの前に
構文、これには *ngIf 長さを制御するために分離
配列の。
<!-- PRIMA: due direttive separate -->
<div *ngIf="notifications.length === 0" class="empty-state">
<p>Nessuna notifica</p>
</div>
<div *ngFor="let notification of notifications">
{{ notification.message }}
</div>
<!-- DOPO: integrato nel control flow -->
@for (notification of notifications(); track notification.id) {
<div class="notification" [class]="notification.type">
<span class="icon">{{ notification.icon }}</span>
<p>{{ notification.message }}</p>
<time>{{ notification.date | date:'short' }}</time>
</div>
} @empty {
<div class="empty-state">
<img src="/assets/no-notifications.svg" alt="Nessuna notifica" />
<h3>Tutto in ordine!</h3>
<p>Non hai notifiche al momento.</p>
</div>
}
@empty の利点
- チェックの重複を排除する
array.length === 0 - @empty ブロックは、コレクションに要素が含まれていない場合にのみレンダリングされます。
- 反復ロジックと空の状態をグループ化することで読みやすさを向上
- 配列、配列信号、フィルター処理された結果など、あらゆる反復可能オブジェクトで動作します。
回路図による自動移行
Angular チームが提供するのは、 公式回路図 自動的に移行される テンプレートを古い構文から新しい構文に変更します。このツールは各テンプレートを分析し、 構造ディレクティブを変換し、クリーンなコードを生成します。
# Migrazione automatica del control flow
ng generate @angular/core:control-flow-migration
# Il comando:
# 1. Analizza tutti i template dell'applicazione
# 2. Converte *ngIf -> @if
# 3. Converte *ngFor -> @for (con track $index come default)
# 4. Converte ngSwitch -> @switch
# 5. Rimuove le importazioni di CommonModule dove non più necessarie
# Per un singolo componente
ng generate @angular/core:control-flow-migration --path=src/app/components/my-component
# Per una dry run (anteprima senza modificare i file)
ng generate @angular/core:control-flow-migration --dry-run
自動移行に注意してください
- トラックのデフォルト: 回路図の挿入
track $index私のデフォルトとして@for。移行後、各出現箇所を確認し、正しい一意のプロパティに置き換えます (例:track item.id) - 書式設定: 移行によりインデントが変更される可能性があります。移行後にプロジェクト フォーマッタを実行する
- 複雑なテンプレート: 複雑でネストされた構造ディレクティブを含むテンプレートは手動でのレビューが必要になる場合があります
- テスト: 移行後にすべてのテストを実行して、動作が変わっていないことを確認します。
- 最初にコミットします: 移行前にコードをコミットすると、違いを簡単に比較できます
<!-- PRIMA della migrazione -->
<div *ngIf="users$ | async as users; else loading">
<table>
<tr *ngFor="let user of users; trackBy: trackByUserId; let i = index">
<td>{{ i + 1 }}</td>
<td>{{ user.name }}</td>
<td [ngSwitch]="user.status">
<span *ngSwitchCase="'active'" class="green">Attivo</span>
<span *ngSwitchCase="'inactive'" class="red">Inattivo</span>
<span *ngSwitchDefault class="gray">N/D</span>
</td>
</tr>
</table>
</div>
<ng-template #loading>
<div class="spinner">Caricamento...</div>
</ng-template>
<!-- DOPO la migrazione automatica -->
@if (users$ | async; as users) {
<table>
@for (user of users; track user.id; let i = $index) {
<tr>
<td>{{ i + 1 }}</td>
<td>{{ user.name }}</td>
<td>
@switch (user.status) {
@case ('active') {
<span class="green">Attivo</span>
}
@case ('inactive') {
<span class="red">Inattivo</span>
}
@default {
<span class="gray">N/D</span>
}
}
</td>
</tr>
}
</table>
} @else {
<div class="spinner">Caricamento...</div>
}
高度なパターン: 制御フローの結合
新しい制御フローの真の力は、さまざまな構成を組み合わせたときに現れます。 実際のシナリオを解決するために。使用されている高度なパターンをいくつか見てみましょう 通常、本番環境で使用されます。
パターン 1: セクションの遅延読み込みを行うダッシュボード
<div class="dashboard">
<!-- Header sempre visibile -->
<header>
<h1>Dashboard</h1>
@if (user(); as u) {
<span>Benvenuto, {{ u.name }}</span>
}
</header>
<!-- KPI caricati immediatamente quando idle -->
@defer (on idle) {
<app-kpi-cards [metrics]="kpiData()" />
} @placeholder {
<div class="kpi-skeleton">
@for (i of [1,2,3,4]; track $index) {
<div class="skeleton-card"></div>
}
</div>
}
<!-- Grafico caricato quando entra nel viewport -->
@defer (on viewport; prefetch on idle) {
<app-revenue-chart [data]="revenueData()" />
} @placeholder {
<div class="chart-placeholder" style="height: 400px">
<p>Grafico ricavi</p>
</div>
} @loading (after 200ms) {
<div class="spinner">Caricamento grafico...</div>
}
<!-- Tabella dati caricata su richiesta -->
<button #showTable>Mostra dettagli</button>
@defer (on interaction(showTable); prefetch on hover(showTable)) {
@if (tableData(); as data) {
@for (row of data; track row.id) {
<app-data-row [row]="row" />
} @empty {
<p>Nessun dato disponibile per il periodo selezionato</p>
}
}
} @placeholder {
<p>Clicca per visualizzare i dettagli</p>
}
</div>
パターン 2: フィルター、ソート、空の状態を含むリスト
<div class="product-list">
<!-- Filtri -->
<div class="filters">
@for (category of categories(); track category.id) {
<button
[class.active]="selectedCategory() === category.id"
(click)="selectCategory(category.id)">
{{ category.name }}
<span class="count">({{ category.count }})</span>
</button>
}
</div>
<!-- Stato di caricamento -->
@if (isLoading()) {
<div class="loading">
@for (i of [1,2,3]; track $index) {
<div class="product-skeleton"></div>
}
</div>
} @else {
<!-- Lista prodotti -->
@for (product of filteredProducts(); track product.id) {
<div class="product-card">
<h3>{{ product.name }}</h3>
<p>{{ product.description }}</p>
@switch (product.availability) {
@case ('in_stock') {
<span class="badge success">Disponibile</span>
}
@case ('low_stock') {
<span class="badge warning">Ultime scorte ({{ product.stock }})</span>
}
@case ('out_of_stock') {
<span class="badge danger">Esaurito</span>
}
}
<div class="price">{{ product.price | currency:'EUR' }}</div>
</div>
} @empty {
<div class="empty-state">
<h3>Nessun prodotto trovato</h3>
<p>Prova a modificare i filtri di ricerca</p>
<button (click)="resetFilters()">Resetta filtri</button>
</div>
}
}
</div>
パターン 3: ユーザー権限を伴う @defer 条件付き
@Component({
selector: 'app-settings-page',
standalone: true,
template: `
<h1>Impostazioni</h1>
<!-- Sezione profilo: sempre disponibile -->
<app-profile-settings />
<!-- Pannello admin: caricato solo se l'utente è admin -->
@defer (when isAdmin()) {
<app-admin-settings />
} @placeholder {
@if (!isAdmin()) {
<div class="restricted">
<p>Accesso riservato agli amministratori</p>
</div>
}
}
<!-- Impostazioni avanzate: defer + condizione -->
@if (showAdvanced()) {
@defer (on idle) {
<app-advanced-settings />
} @loading {
<div class="spinner">Caricamento impostazioni avanzate...</div>
}
}
`
})
export class SettingsPageComponent {
private authService = inject(AuthService);
isAdmin = computed(() =>
this.authService.currentUser()?.role === 'admin'
);
showAdvanced = signal(false);
}
パフォーマンスへの影響
新しい制御フローは読みやすいだけでなく、具体的で測定可能な利点もあります。 アプリケーションのパフォーマンスに関して。 Angular コンパイラは最適化されたコードを生成します これにより、構造ディレクティブの実行時のオーバーヘッドが排除されます。
ベンチマーク: 新しい制御フローと構造ディレクティブ
| メトリック | ディレクティブ (*ngIf、*ngFor) | 新しい制御フロー | 改善 |
|---|---|---|---|
| バンドルサイズ (CommonModule) | ~12KB (縮小) | 0KB(内蔵コンパイラ) | -100% |
| 1000 項目のリストのレンダリング | ~45ms | ~38ms | -15% |
| 1 回の編集で再レンダリング | ~8ms | ~3ms | -62% |
| @for のメモリ使用量 | ディレクティブ + テンプレート参照 + 埋め込みビュー | コンパイルされた命令のみ | 重要な |
| @defer を使用した遅延読み込み | 利用不可(ルートレベルのみ) | コンポーネント/テンプレートレベルで | 新機能 |
新しい制御フローの方が速いため
- 直接コンパイル: ブロック
@ife@for実行時にディレクティブをインスタンス化するオーバーヘッドなしで、Angular コンパイラーによって最適化された JavaScript ステートメントにコンパイルされます。 - ViewContainerRef がありません: 構造指令の使用
ViewContainerRef埋め込みビューを作成/破棄します。新しい制御フローは DOM を直接処理します - 最適化されたトラック: の差分アルゴリズム
@forより効率的になるように最初から書き直されましたDefaultIterableDiffer - 木の揺れ: これらはディレクティブではないため、使用しなくてもバンドル内のスペースを占有しません。それらを使用したとしても、生成されるコードは最小限です
- @defer チャンク分割: コンパイラは遅延コンテンツ用に別のチャンクを自動的に作成し、初期ペイロードを削減します。
新しい制御フローのベスト プラクティス
Angular コミュニティによる数か月の本番使用を経て、それらは定着しました。 新しい制御フローを最大限に活用するための明確なベスト プラクティス。
新しい制御フローの 10 のベスト プラクティス
-
トラック内では常に一意のプロパティを使用します。 避ける
track $indexただし、リストにアイデンティティのないプリミティブ値が含まれている場合は除きます。あなたが好むtrack item.id -
空の状態には @empty を利用します。 すべて順調です
@forデータを表示する ユーザーはロックを持っている必要があります@empty役立つメッセージ付き -
スクロールせずに見えるコンテンツには @defer を使用します。 目に見えないものすべて
最初のレンダリングでは、次のようにラップする必要があります
@defer (on viewport) -
プリフェッチとトリガーを組み合わせます。 アメリカ合衆国
prefetch on idleのために ブラウザーの非アクティブ時にチャンクを事前ダウンロードする -
いくつかのケースでは @switch よりも @else を優先します。 条件が2~3つある場合は、
@if / @else ifよりも読みやすいです@switch - @defer には常に @placeholder を指定します。 ユーザーは次のことを行う必要があります コンテンツが間もなく表示されることを理解します。空のプレースホルダーは混乱を招く
-
@loading で minimum 以降を使用します。 「フラッシュ」の読み込みは避けてください。
すぐに読み込まれるコンテンツの場合
@loading (after 150ms; minimum 300ms) - ネストしすぎないでください。 3 レベルを超えるネストされた制御フロー テンプレートが判読できなくなります。サブコンポーネントの抽出
- 完全に移行します。 古い構文と新しい構文を混合しないでください 同じテンプレート。メンテナンスが混乱し、複雑になる
-
自動移行後のレビュー: 回路図では、
track $indexデフォルトとして。それぞれを確認してください@for正しいトラック エクスプレッションを割り当てます
避けるべきアンチパターン
<!-- ERRORE 1: Track per $index su liste dinamiche -->
<!-- Se un elemento viene inserito in testa, TUTTI i nodi DOM vengono ricreati -->
@for (user of users(); track $index) {
<app-user-card [user]="user" /> <!-- Ricreato inutilmente! -->
}
<!-- FIX: track user.id -->
<!-- ERRORE 2: @defer senza placeholder -->
<!-- L'utente vede uno spazio vuoto senza capire cosa succede -->
@defer (on viewport) {
<app-comments />
}
<!-- FIX: aggiungi @placeholder con indicazione visiva -->
<!-- ERRORE 3: @defer per contenuti critici above-the-fold -->
<!-- Il contenuto principale non deve essere lazy -->
@defer (on idle) {
<app-hero-section /> <!-- MAI! Il hero deve essere immediato -->
}
<!-- ERRORE 4: Annidamento eccessivo -->
@if (condition1()) {
@for (item of items(); track item.id) {
@if (item.visible) {
@switch (item.type) {
@case ('A') {
@for (sub of item.children; track sub.id) {
<!-- Troppo annidato! Estrai un componente -->
}
}
}
}
}
}
<!-- FIX: estrai sotto-componenti per ridurre l'annidamento -->
移行チェックリスト
新しい制御フローへの移行チェックリスト
- 移行前に現在のコードをコミットする
- 走る
ng generate @angular/core:control-flow-migration --dry-run - 提案された変更を確認します
- 実際の移行を実行する
- すべて置き換える
track $indexユニークな特性を持つ - ブロックを追加する
@empty必要に応じて - どこに追加するかを検討する
@defer遅延読み込み用 - のインポートを削除します
CommonModule,NgIf,NgFor,NgSwitch - フォーマッタを実行してインデントを滑らかにする
- すべてのテストを実行します (ユニット + e2e)
- 各ページを視覚的に確認してください
概要と次のステップ
Angular の新しい制御フローは、開発者のエクスペリエンスにおける質的な飛躍を表しています
フレームワークの。の構文 @if, @for, @switch
e @defer 読みやすいだけでなく、よりパフォーマンスの高いコードが生成されます。
宣言的な遅延読み込みと、必須の追跡などのベスト プラクティスを強制します。
この記事の重要な概念
- @if / @else if / @else: 置き換えます
*ngIfelse-if とエイリアスをネイティブでサポートas - @for トラック付き: 置き換えます
*ngForパフォーマンスを保証する必須トラック付き。暗黙的な変数 ($index、$first、$last、$even、$odd、$count) を含みます。 - @switch / @case / @default: 置き換えます
ngSwitchきれいな構文と厳密な比較 - @defer: トリガー (ビューポート、アイドル、インタラクション、ホバー、タイマー、いつ) とプリフェッチを使用した宣言的な遅延読み込み
- @空の: 空のリストのエレガントな管理。
@for - 移行: 回路図
@angular/core:control-flow-migration変換を自動化する - パフォーマンス: ゼロバンドルオーバーヘッド (ディレクティブではなくコンパイル済み)、最適化された差分分析、チャンク分割
@defer
シリーズの次の記事では、 スタンドアロンコンポーネント、 Angular から NgModules を完全に排除し、構造を簡素化するアーキテクチャ アプリケーションの機能を強化し、ルート レベルでのツリーシェイキングと遅延読み込みを改善します。







