독립형 구성 요소: NgModules가 없는 Angular
의 도입으로 독립형 구성 요소 Angular 14와 그 프로그레시브 Angular 15부터 표준으로 채택된 프레임워크는 아키텍처에 획기적인 발전을 이루었습니다. 기본: 사용 의무의 제거 Ng모듈 조직하다 코드. 이러한 진화는 개발을 극적으로 단순화하고 상용구와 코드를 줄였습니다. 새로운 개발자가 Angular에 훨씬 더 쉽게 접근할 수 있게 되었습니다.
이 시리즈의 네 번째 기사에서는 현대적인 각도 독립형을 살펴보겠습니다.
심층 구성 요소: 문제 해결부터 전체 마이그레이션까지,
라우팅, 부트스트래핑, 종속성 주입 및 고급 패턴. 혹시 어려움을 겪었다면
declarations, imports e exports 각도 모듈에서는
이 기사에서는 영구적으로 제거하는 방법을 보여줍니다.
무엇을 배울 것인가
- NgModule이 문제인 이유와 독립형 구성 요소가 문제를 해결하는 방법
- 독립형 구성 요소, 지시문 및 파이프를 만드는 방법
- 다음을 사용하여 종속성을 관리합니다.
imports구성 요소에 직접 - 모듈 없이 라우팅 및 지연 로딩 구성
- 다음을 사용하여 애플리케이션 부트스트래핑
bootstrapApplication() - 독립형 세계에서의 의존성 주입
- 공식 회로도를 사용하여 기존 프로젝트 마이그레이션
- 엔터프라이즈 애플리케이션을 위한 고급 패턴 및 모범 사례
최신 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로 마이그레이션 | 마이그레이션 가이드 |
1. NgModules 문제
버전 2부터 Angular는 i를 사용했습니다. Ng모듈 기본 단위로
코드 조직의. 모든 구성 요소, 지시문 또는 파이프를 선언해야 했습니다.
어레이를 통해 정확히 하나의 모듈에 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 { }
NgModule의 일반적인 문제
- 과도한 상용구: 각 구성 요소를 가져오고 모듈에서 선언해야 합니다. 대규모 프로젝트에서는 선언을 위해서만 수백 줄이 필요합니다.
- 순환 종속성: 두 모듈을 서로 가져오면 진단하기 어려운 오류가 발생합니다.
- 선언과 수입의 혼란: 새로운 개발자는 구성 요소(선언)를 넣을 위치와 모듈(가져오기)을 넣을 위치를 끊임없이 혼동합니다.
- "구성 요소가 NgModule의 일부가 아닙니다." 오류: 초보자가 가장 좌절하는 실수 중 하나는 구성 요소 선언을 잊어버려서 발생하는 것입니다.
- 연결: 구성 요소는 이를 선언하는 모듈과 결합되어 재사용 및 단위 테스트가 어렵습니다.
- 복잡한 지연 로딩: 기능을 느리게 로드하려면 라우팅 구성이 포함된 전용 모듈을 생성해야 합니다.
많은 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. 독립형 구성요소란 무엇입니까?
에이 독립형 구성요소 자신을 다음과 같이 선언하는 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. 여기서 그들은 스스로 선언합니다. 모든 템플릿 종속성:
다른 구성 요소, 지시문, 파이프 및 심지어 정수 NgModules(라이브러리와의 호환성을 위해)
유산). 각 구성 요소는 중개자 없이 필요한 것이 무엇인지 정확히 알고 있습니다.
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와 독립 실행형의 종속성
| NgModule 사용 | 독립형 포함 |
|---|---|
| 모듈에 선언된 구성요소 | 자급자족 구성요소 |
| 모듈로 가져온 종속성 | 구성요소로 가져온 종속성 |
| 모든 모듈 선언은 import를 참조하세요. | 구성 요소만 가져오기를 볼 수 있습니다. |
| 모듈에서 사용되지 않은 수입품의 위험 | 각 가져오기는 명시적이고 필요합니다. |
주의: 필요 이상으로 가져오지 마십시오.
독립 실행형 구성 요소를 사용하면 가져오고 싶은 유혹을 받을 수 있습니다. CommonModule
편의상 전체. 그러나 나열된 개별 파이프와 지시문만 가져오는 것이 가장 좋습니다.
당신이 필요합니다 (예를 들어 NgIf, NgFor, AsyncPipe).
새로운 구문 사용 @if e @for 각도 17+로,
제어 흐름이 템플릿에 기본적으로 적용되므로 이러한 가져오기 중 상당수는 더 이상 필요하지 않습니다.
5. NgModules 없이 라우팅
Standalone Components의 가장 큰 장점 중 하나는 라우팅 단순화.
모듈을 사용하면 기능을 지연 로드하려면 다음을 사용하여 전용 모듈을 만들어야 했습니다.
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)
}
]
}
];
지연 로딩: NgModule과 독립 실행형
| 접근하다 | Ng모듈 | 독립형 |
|---|---|---|
| 지연 로드 단일 구성 요소 | 래퍼 모듈 없이는 불가능 | loadComponent: () => import(...) |
| 지연 로드 기능 | loadChildren NgModule을 사용하여 |
loadChildren 경로 배열 사용 |
| 필요한 파일 | 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(종속성 주입)는 독립형 구성 요소와 잘 작동합니다. 그러나 애플리케이션의 다양한 수준에서 공급자를 구성하는 새로운 패턴을 제공합니다.
제공된In: '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);
}
독립 실행형 세계의 공급자 계층 구조
| 수준 | 구성 | 빗자루 | 사용 사례 |
|---|---|---|---|
| 뿌리 | providedIn: 'root' |
전체 앱(싱글톤) | AuthService, HttpService, StateStore |
| 앱 구성 | appConfig.providers |
전체 앱 | 라우터, HttpClient, 애니메이션 |
| 노선 | route.providers |
기능/섹션 | AdminService, FeatureConfig |
| 요소 | component.providers |
단일 구성요소 | FormState, EditorState |
8. NgModules에서 독립형으로 마이그레이션
Angular는 하나를 제공합니다 공식 마이그레이션 도식 자동화하는 NgModules에서 독립 실행형 구성 요소로의 변환 프로세스의 대부분. 마이그레이션 세 가지 점진적인 단계로 진행되므로 안전하고 점진적인 방식으로 진행할 수 있습니다.
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가지 황금률
- 기본적으로 독립형: 항상 독립형 구성요소를 생성하십시오. 레거시 라이브러리와의 호환성을 위해서만 NgModule을 사용하십시오.
- 필요한 것만 가져옵니다. 전체 모듈을 다음과 같이 가져오지 마세요.
CommonModule. 개별 파이프 및 지시어 가져오기 - 새로운 템플릿 구문을 사용하세요: 와 함께
@if,@fore@switch더 이상 가져올 필요가 없습니다.NgIf,NgFor,NgSwitch - 하나의 구성요소, 하나의 파일: 각 독립형 구성요소에는 자체 파일이 있어야 합니다. 동일한 파일에 여러 구성요소를 선언하지 마세요.
- 기능에 대한 배럴 내보내기: 파일 사용
index.ts피쳐의 공유 구성요소를 다시 내보내려면 - 구성 요소가 아닌 경로의 공급자: 기능 서비스의 경우 다음을 선호합니다.
route.providers비해component.providers - 제공된In: 글로벌 서비스의 '루트': 유지하다
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
독립형 구성 요소 테스트
가장 중요한 이점 중 하나는 테스트가 단순화된다는 것입니다. 더 이상 구성할 필요가 없습니다.
복잡한 테스트 모듈 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
}));
});
});
NgModule을 유지해야 하는 시기
독립 실행형 구성 요소는 미래이지만 NgModule이 여전히 유용하거나 필요합니다.
- 타사 라이브러리: 일부 라이브러리는 여전히 NgModules를 내보냅니다. 배열로 직접 가져올 수 있습니다.
imports독립형 구성요소의 - 대규모 레거시 프로젝트: 수백 개의 구성 요소가 포함된 프로젝트가 있는 경우 마이그레이션은 점진적일 수 있습니다. 모든 것을 즉시 할 필요는 없습니다
- 종속성이 많은 SharedModule: 수십 개의 구성 요소를 가져오는 SharedModule이 있는 경우 임시로 유지하고 준비가 되면 배럴 내보내기로 교체하는 것이 더 실용적일 수 있습니다.
요약 및 다음 단계
이 기사에서는 Angular의 독립 실행형 구성 요소를 자세히 살펴보았습니다. 동기 부여에서 마이그레이션까지, 라우팅에서 종속성 주입까지. 주요 사항은 다음과 같습니다.
주요 개념
- 그만큼 독립형 구성 요소 NgModule이 필요하지 않아 각 구성 요소가 자급자족할 수 있습니다.
- Da 각도 19, 독립 실행형은 구성 요소, 지시문 및 파이프의 기본 동작입니다.
- 배열
imports데코레이터를 사용하면 구성 요소에서 직접 종속성을 선언할 수 있습니다. loadComponenteloadChildren래퍼 모듈 없이 지연 로딩을 단순화합니다.bootstrapApplication()eApplicationConfigNgModule을 통해 부트스트랩을 대체합니다.- 기능
provide*()구성 모듈 교체(RouterModule.forRoot(), 등.) - La DI 루트, 앱 구성, 경로 및 구성 요소의 네 가지 수준에서 작동합니다.
- Lo 마이그레이션 회로도 세 가지 점진적인 단계로 변환을 자동화합니다.
- I 배럴 수출 그들 일반적인 수입품 배열 SharedModule을 대체합니다.
시리즈의 다음 기사에서는 신호 형태, 우리는 어떻게 볼 것인가 신호 혁명은 현대적인 대안을 통해 양식 관리로 확장됩니다. 세밀한 반응성을 사용하여 고성능 폼을 생성하는 Reactive Forms 그리고 선언문.







