새로운 Angular 템플릿: @if, @for, @defer
Angular 17을 통해 팀은 새로운 시스템을 도입했습니다. 템플릿의 제어 흐름
이는 역사적인 구조적 지침을 대체합니다. *ngIf, *ngFor e
ngSwitch. 새로운 구문 @if, @for,
@switch e @defer 근본적인 변화를 나타낸다
Angular 템플릿을 작성하는 방식: 더 읽기 쉽고 성능이 뛰어나며 기능이 뛰어납니다.
처럼 완전히 새로운 선언적 지연 로딩 구성 요소의.
이 시리즈의 세 번째 기사에서는 현대적인 각도 우리는 분석할 것입니다 각 구성의 깊이: 기본 구문부터 고급 패턴, 마이그레이션까지 커뮤니티에 의해 통합된 모범 사례에 자동으로 적용됩니다. 아직도 사용하고 계시다면 고전적인 구조 지시문을 사용하는 경우 이 가이드에서는 마이그레이션해야 하는 이유를 보여줍니다.
최신 Angular 시리즈 개요
| # | Articolo | 집중하다 |
|---|---|---|
| 1 | 각도 신호 | 세분화된 응답성 |
| 2 | 영역 없는 변경 감지 | Zone.js 삭제 |
| 3 | 현재 위치 - 새 템플릿 @if, @for, @defer | 현대적인 제어 흐름 |
| 4 | 독립형 구성 요소 | NgModule이 없는 아키텍처 |
| 5 | 신호 형태 | 신호가 포함된 반응형 양식 |
| 6 | SSR 및 증분 수화 | 서버 측 렌더링 |
| 7 | Angular의 핵심 웹 바이탈 | 성능 및 지표 |
| 8 | 각도 PWA | 프로그레시브 웹 앱 |
| 9 | 고급 종속성 주입 | DI 트리 셰이크 가능 |
| 10 | Angular 17에서 21로 마이그레이션 | 마이그레이션 가이드 |
왜 새로운 구문인가? 구조적 지시어의 한계
구조적 지시어 *ngIf, *ngFor e ngSwitch
거의 10년 동안 Angular를 제공해왔지만 구조적 한계가 있었습니다.
새로운 구문은 이 문제를 완전히 수정합니다.
고전적 구조 지시어의 문제
- 불투명한 마이크로 구문: 다음과 같은 표현
*ngFor="let item of items; trackBy: trackFn; let i = index; let first = first"읽고 기억하기가 어렵습니다 - 기본 else 없음: 와 함께
*ngIf없다else if: 더 많이 중첩해야 합니다.*ngIf또는 사용ng-template~와 함께else - 번들 무게: 구조적 지시문은 번들에 포함되어야 하는 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-if | 기본적으로 지원되지 않음 | @else 네이티브인 경우 |
| 필수트랙 | 아니요(선택사항) | 예(@for에 필수) |
| 지연 로딩 | 사용할 수 없음 | 선언적 트리거를 사용한 @defer |
| 번들 크기 | 번들에 포함된 지시어 | 오버헤드 없음(컴파일됨) |
| 유형 확인 | 제한된 | 완벽한 |
| 가독성 | 복잡한 마이크로 구문 | JavaScript/TypeScript와 유사함 |
@if 및 @else: 현대적인 조건부 렌더링
블록 @if 대체하다 *ngIf 다음과 같은 구문을 사용합니다.
JavaScript 제어 구조와 유사합니다. 기본적으로 지원 @else if
e @else, eliminando la necessità di 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 with track: 수행 및 필수 반복
블록 @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 | 부울 | 인덱스가 짝수이면 참 |
$odd | 부울 | 인덱스가 홀수이면 참 |
$count | 숫자 | 총 항목 수 |
트랙이 필수이기 때문에
Angular가 목록을 업데이트할 때 어떤 DOM 요소를 다시 생성할지, 어떤 요소를 다시 생성할지 결정해야 합니다.
이동하고 변경하지 않고 유지할 항목. 표현 track Angular에 제공
각 요소에 대한 안정적인 ID를 제공하여 최적의 업데이트를 가능하게 합니다.
// 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 목록에
자주 변경되는 것은 성능 저하의 주요 원인 중 하나입니다.
각도 애플리케이션.
@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선택사항이지만 예상치 못한 경우를 처리하기 위해 권장됩니다.- 폴스루(fall-through)를 지원하지 않습니다: 모든
@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% |
| 단일 편집으로 다시 렌더링 | ~8ms | ~3ms | -62% |
| @for의 메모리 공간 | 지시문 + 템플릿 참조 + 내장된 뷰 | 컴파일된 지침만 | 중요한 |
| @defer를 사용한 지연 로딩 | 사용할 수 없음(경로 수준에만 해당) | 구성 요소/템플릿 수준에서 | 새로운 기능 |
새로운 제어 흐름이 더 빠르기 때문입니다.
- 직접 컴파일: 블록
@ife@for런타임에 지시문을 인스턴스화하는 오버헤드 없이 Angular 컴파일러에 의해 최적화된 JavaScript 문으로 컴파일됩니다. - ViewContainerRef 없음: 구조적 지시어 사용
ViewContainerRef내장된 뷰를 생성/파기합니다. 새로운 제어 흐름은 DOM을 직접 처리합니다. - 최적화된 트랙: diffing 알고리즘은
@for보다 효율적으로 처음부터 다시 작성되었습니다.DefaultIterableDiffer - 나무 흔들기: 지시어가 아니기 때문에 사용하지 않더라도 번들에서 공간을 차지하지 않습니다. 그리고 사용하더라도 생성되는 코드는 최소화됩니다.
- @defer 청크 분할: 컴파일러는 게으른 콘텐츠에 대한 별도의 청크를 자동으로 생성하여 초기 페이로드를 줄입니다.
새로운 제어 흐름에 대한 모범 사례
Angular 커뮤니티에서 몇 달 동안 프로덕션을 사용한 후 설립되었습니다. 새로운 제어 흐름을 최대한 활용하기 위한 명확한 모범 사례
새로운 제어 흐름의 10가지 모범 사례
-
항상 트랙에서 고유한 속성을 사용하세요. 피하다
track $index목록에 ID가 없는 기본 값이 포함되어 있지 않은 경우. 당신은 선호track item.id -
빈 상태에 대해 @empty를 활용합니다. 모든 것이 괜찮습니다
@for데이터 표시 사용자는 자물쇠를 가지고 있어야 합니다@empty유용한 메시지와 함께 -
스크롤 없이 볼 수 있는 콘텐츠에는 @defer를 사용하세요. 눈에 보이지 않는 모든 것
첫 번째 렌더링에서는 래핑되어야 합니다.
@defer (on viewport) -
프리페치와 트리거를 결합합니다. 미국
prefetch on idle에 대한 브라우저 비활성 동안 사전 다운로드 청크 -
몇몇 경우에는 @switch보다 @else를 선호합니다. 2~3가지 조건이 있는 경우
@if / @else if그것은보다 더 읽기 쉽습니다@switch - @defer에는 항상 @placeholder를 제공하세요. 사용자는 반드시 콘텐츠가 곧 나타날 것임을 이해합니다. 빈 자리 표시자는 혼란스럽습니다.
-
@loading에서 최소값과 이후 사용: "플래시" 로딩을 피하세요.
빠르게 로드되는 콘텐츠의 경우
@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)를 포함합니다. - @스위치 / @케이스 / @기본: 대체
ngSwitch깔끔한 구문과 긴밀한 비교 - @지연: 트리거(뷰포트, 유휴, 상호 작용, 호버, 타이머, 언제) 및 미리 가져오기를 통한 선언적 지연 로딩
- @비어 있는: 빈 목록의 우아한 관리, 직접 통합
@for - 이주: 회로도
@angular/core:control-flow-migration변환을 자동화하다 - 성능: 번들 오버헤드 없음(지시문이 아닌 컴파일됨), 최적화된 diffing, 청크 분할
@defer
시리즈의 다음 기사에서는 독립형 구성 요소, Angular에서 NgModule을 완전히 제거하여 구조를 단순화한 아키텍처 애플리케이션을 개선하고 경로 수준에서 트리 쉐이킹 및 지연 로딩을 개선합니다.







