Din Angular 19, orice componentă, directivă și pipe e standalone implicit — nu mai scrii standalone: true și nu mai ai nevoie de NgModule. Fiecare componentă își declară singură dependențele în imports, iar aplicația pornește cu o funcție, nu cu un modul.
Cum pornește aplicația
Trei fișiere: main.ts pornește aplicația, app.config.ts ține providerii globali, iar app.ts e componenta rădăcină. Ca să construiești o aplicație mică peste ele, urmează Prima ta aplicație, pas cu pas.
import { bootstrapApplication } from '@angular/platform-browser';
import { App } from './app/app';
import { appConfig } from './app/app.config';
bootstrapApplication(App, appConfig).catch((err) => console.error(err));- v21: aplicațiile noi sunt zoneless — fără
zone.jsîn bundle, fără provider special. - v21:
HttpCliente disponibil în root fărăprovideHttpClient(). Îl apelezi doar ca să configurezi (interceptori, XHR). - v22: componentele sunt
OnPushimplicit. Vezi Change detection. - v22: TypeScript 6 e obligatoriu.
| Versiune Angular | 22.2.2 |
| Zone.js încărcat? | nu — aplicație zoneless |
| Componenta rădăcină e standalone? | da |
| NgModule-uri scrise în aplicație | 0 |
| Rute configurate (top-level) | 4 |
| Pagini încărcate lazy (câte un chunk) | 75 |
imports: fiecare componentă își declară dependențele
Template-ul unei componente vede doar ce e în imports-ul ei: alte componente, directive, pipe-uri. Asta face dependențele explicite, permite tree-shaking mai bun și face lazy loading-ul trivial.
import { Component, input } from '@angular/core';
import { CurrencyPipe, DatePipe } from '@angular/common';
import { RouterLink } from '@angular/router';
import { Avatar } from '@shared/ui/avatar';
@Component({
selector: 'app-user-card',
// Fără CommonModule: @if/@for sunt în limbaj, pipe-urile se importă individual
imports: [RouterLink, DatePipe, CurrencyPipe, Avatar],
template: `
<app-avatar [name]="user().name" />
<a [routerLink]="['/users', user().id]">{{ user().name }}</a>
<small>membru din {{ user().joinedAt | date: 'MMM yyyy' }}</small>
`,
})
export class UserCard {
readonly user = input.required<User>();
}CommonModule era importat pentru *ngIf, *ngFor, ngClass și pipe-uri. Acum control flow-ul e în limbaj, [class] înlocuiește ngClass, iar pipe-urile le imporți individual (DatePipe, CurrencyPipe). Rezultatul: imports scurte și precise.
Înainte și acum
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core';
import { provideRouter, withComponentInputBinding } from '@angular/router';
import { authInterceptor } from './core/auth/auth-interceptor';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideBrowserGlobalErrorListeners(), // erori globale → ErrorHandler
provideRouter(routes, withComponentInputBinding()), // router + param-uri ca input()
provideHttpClient(withInterceptors([authInterceptor])), // doar dacă vrei interceptori
// Nu mai e nevoie de: provideZonelessChangeDetection() (implicit din v21)
// Nu mai e nevoie de: provideHttpClient() simplu (HttpClient e în root din v21)
],
};// Înainte (Angular ≤ 14): totul trecea printr-un NgModule
@NgModule({
declarations: [AppComponent, HeaderComponent, UserCardComponent],
imports: [BrowserModule, HttpClientModule, RouterModule.forRoot(routes)],
providers: [{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }],
bootstrap: [AppComponent],
})
export class AppModule {}
platformBrowserDynamic().bootstrapModule(AppModule);Dar am librării cu NgModule…
Nicio problemă: un NgModule se poate pune direct în imports-ul unei componente standalone. Pentru provideri la nivel de aplicație din module vechi există importProvidersFrom().
// O librărie veche care exportă doar un NgModule? Se importă direct.
@Component({
imports: [LegacyChartsModule], // NgModule în imports-ul unei componente standalone — permis
template: `<legacy-chart [data]="data()" />`,
})
export class Report {}
// Un NgModule care trebuie să adauge providers la nivel de aplicație:
export const appConfig: ApplicationConfig = {
providers: [importProvidersFrom(LegacyAnalyticsModule.forRoot({ key: '...' }))],
};Migrarea unui proiect existent
# Migrare automată la standalone (rulezi în 3 pași, cum îți cere schematicul)
ng generate @angular/core:standalone
# Alte migrări utile pentru un proiect vechi (toate există în @angular/core v22)
ng generate @angular/core:control-flow # *ngIf/*ngFor/ngSwitch → @if/@for/@switch
ng generate @angular/core:common-to-standalone # CommonModule → importuri individuale
ng generate @angular/core:inject # constructor DI → inject()
ng generate @angular/core:signals # @Input/@Output/@ViewChild → input()/output()/viewChild()
ng generate @angular/core:ngclass-to-class # [ngClass] → [class]
ng generate @angular/core:ngstyle-to-style # [ngStyle] → [style]
ng generate @angular/core:service # @Injectable({providedIn:'root'}) → @Service() (v22)
ng generate @angular/core:route-lazy-loading # component: X → loadComponent: () => import(...)
ng generate @angular/core:self-closing-tag # <app-x></app-x> → <app-x />
ng generate @angular/core:cleanup-unused-importsCând DA, când NU
- Orice cod nou: componente, directive, pipe-uri.
- Lazy loading la nivel de componentă (
loadComponent) sau de grup de rute (loadChildrencu un fișier de rute). - Configurare prin funcții
provideX()în loc deModule.forRoot().
- Întreții o librărie publică ce trebuie să suporte versiuni foarte vechi de Angular.
- Ai o bază de cod mare, încă nemigrată — migrezi incremental, cele două modele conviețuiesc.
Capcane frecvente
- „X is not a known element”: ai uitat să adaugi componenta în
imports. Fiecare componentă își importă singură copiii. - Pipe necunoscut:
datenu mai vine „gratis” — importăDatePipe. standalone: falseapare doar în cod vechi/migrat. Nu-l scrie în cod nou.- Provideri pe care îi pui în
app.config.tssunt globali. Pentru un singur feature, pune-i pe rută (vezi Providers).