AngularAtlased. 22.2

Cap. 16UI & performanță avansată69 / 75

Liste uriașe: virtual scroll

100.000 de rânduri fără lag: CDK virtual scroll, itemSize, trackBy și alternativele (paginare, infinite scroll).

Fiecare rând dintr-o listă înseamnă elemente DOM, stiluri calculate, layout și, în Angular, un view cu binding-uri verificate de change detection. La câteva sute de rânduri nu se simte. La zeci de mii, pagina pornește în secunde, scroll-ul sacadează și memoria crește constant. Soluția clasică e să nu randezi ce nu se vede.

Virtual scroll păstrează în DOM doar rândurile din zona vizibilă, plus o rezervă sus și jos. La scroll, aceleași elemente sunt refolosite pentru alte date. Dimensiunea totală a listei e simulată, așa că bara de scroll arată ca pentru lista întreagă.

De ce contează
Componentele OnPush și semnalele reduc cât de des randezi. Nu reduc cât randezi. Cu 100.000 de rânduri, problema e numărul de noduri, nu change detection-ul. Doar virtualizarea, paginarea sau încărcarea pe bucăți o rezolvă.

CDK virtual scroll

Din @angular/cdk/scrolling folosești trei piese standalone: CdkVirtualScrollViewport (containerul cu scroll), CdkFixedSizeVirtualScroll (strategia: atributul itemSize) și CdkVirtualForOf (directiva *cdkVirtualFor, care înlocuiește @for). Viewport-ul trebuie să aibă o înălțime, iar itemSize trebuie să fie exact înălțimea reală a rândului în pixeli. Strategia calculează din ea ce indici sunt vizibili.

import { CdkFixedSizeVirtualScroll, CdkVirtualForOf, CdkVirtualScrollViewport } from '@angular/cdk/scrolling';

@Component({
  selector: 'app-orders',
  imports: [CdkVirtualScrollViewport, CdkFixedSizeVirtualScroll, CdkVirtualForOf],
  template: `
    <cdk-virtual-scroll-viewport itemSize="36" class="viewport">
      <div class="row" *cdkVirtualFor="let o of orders(); trackBy: byId">
        {{ o.id }} · {{ o.customer }} · {{ o.total }}
      </div>
    </cdk-virtual-scroll-viewport>
  `,
  styles: `
    .viewport { height: 480px; }       /* viewport-ul TREBUIE să aibă înălțime */
    .row { height: 36px; }             /* = itemSize, exact */
  `,
})
export class Orders {
  readonly orders = input.required<Order[]>();
  protected readonly byId = (_: number, o: Order) => o.id;
}
@for nu virtualizează
Blocul @for randează mereu toate elementele. Pentru virtualizare ai nevoie de directiva *cdkVirtualFor, care primește de la viewport intervalul de indici de randat.
Live100.000 de rânduri (CDK real)
elemente DOM în listă: 0randare inițială: 0 msprimul rând vizibil: #1
Uită-te la „elemente DOM”: în modul virtual rămân câteva sute, oricât derulezi. Sari la rândul 50.000 cu scrollToIndex. Comută pe @for normal: doar 5.000 de rânduri, dar de zeci de ori mai multe noduri și o randare inițială vizibil mai lentă.

Bufferul și reciclarea view-urilor

minBufferPx și maxBufferPx controlează rezerva randată în afara ecranului. Când rezerva scade sub minBufferPx, viewport-ul randează rânduri noi până ajunge la maxBufferPx. Un buffer mai mare înseamnă mai puține goluri la scroll rapid, dar mai mult DOM. View-urile care ies din ecran sunt păstrate într-un cache (templateCacheSize) și refolosite. De aceea contează trackBy: fără el, la fiecare scroll se recreează mai mult DOM decât e nevoie.

<!-- minBufferPx: sub atâția px de rezervă (sus/jos) se randează mai mult… maxBufferPx: …până la atâția px -->
<cdk-virtual-scroll-viewport
  itemSize="36"
  minBufferPx="360"
  maxBufferPx="720"
  (scrolledIndexChange)="first.set($event)">
  <div *cdkVirtualFor="let r of rows; trackBy: byId; templateCacheSize: 40">…</div>
</cdk-virtual-scroll-viewport>

Înălțimi variabile și alte limite

Strategia standard presupune rânduri de aceeași înălțime. O strategie autosize pentru elemente cu dimensiuni diferite există în @angular/cdk-experimental, dar documentația CDK spune explicit că nu e gata de producție. Dacă rândurile tale diferă, ai trei variante:

  • le aduci la aceeași înălțime (text trunchiat, detalii la click);
  • îți scrii propria VirtualScrollStrategy;
  • alegi o alternativă de mai jos.

Mai e un cost de UX: rândurile nerandate nu există în DOM. Ctrl+F nu le găsește, iar cititoarele de ecran nu știu de ele.

Alternative: încărcare pe bucăți

Multe liste nu au nevoie de virtualizare, ci doar să nu încarce totul deodată. Paginarea e cea mai simplă și cea mai accesibilă. Infinite scroll adaugă pagini când utilizatorul ajunge la un element „santinelă”, observat cu IntersectionObserver sau cu @defer (on viewport) (vezi @defer). CSS-ul content-visibility: auto lasă DOM-ul complet, dar sare peste layout și desenare pentru ce e în afara ecranului.

// Infinite scroll: un element „santinelă” la final + IntersectionObserver
afterNextRender(() => {
  const io = new IntersectionObserver(
    ([entry]) => entry.isIntersecting && this.loadNextPage(),
    { rootMargin: '0px 0px 200px 0px' },   // începe încărcarea cu 200 px înainte
  );
  io.observe(this.sentinel().nativeElement);
  this.destroyRef.onDestroy(() => io.disconnect());
});
LiveInfinite scroll cu santinelă
încărcate: 50 / 1.000elemente DOM: 0
#1Ana PopescuCluj-Napoca0,0
#2Mihai PetrescuTimișoara791,9
#3Ioana ConstantinSibiu1.583,8
#4Radu GeorgescuBucurești2.375,7
#5Elena MarinBrașov3.167,6
#6Andrei StanOradea3.959,5
#7Maria DumitruIași4.751,4
#8Vlad IonescuConstanța5.543,3
#9Cristina PopescuCluj-Napoca6.335,2
#10Bogdan PetrescuTimișoara7.127,1
#11Ana ConstantinSibiu7.919,0
#12Mihai GeorgescuBucurești8.710,9
#13Ioana MarinBrașov9.502,8
#14Radu StanOradea294,7
#15Elena DumitruIași1.086,6
#16Andrei IonescuConstanța1.878,5
#17Maria PopescuCluj-Napoca2.670,4
#18Vlad PetrescuTimișoara3.462,3
#19Cristina ConstantinSibiu4.254,2
#20Bogdan GeorgescuBucurești5.046,1
#21Ana MarinBrașov5.838,0
#22Mihai StanOradea6.629,9
#23Ioana DumitruIași7.421,8
#24Radu IonescuConstanța8.213,7
#25Elena PopescuCluj-Napoca9.005,6
#26Andrei PetrescuTimișoara9.797,5
#27Maria ConstantinSibiu589,4
#28Vlad GeorgescuBucurești1.381,3
#29Cristina MarinBrașov2.173,2
#30Bogdan StanOradea2.965,1
#31Ana DumitruIași3.757,0
#32Mihai IonescuConstanța4.548,9
#33Ioana PopescuCluj-Napoca5.340,8
#34Radu PetrescuTimișoara6.132,7
#35Elena ConstantinSibiu6.924,6
#36Andrei GeorgescuBucurești7.716,5
#37Maria MarinBrașov8.508,4
#38Vlad StanOradea9.300,3
#39Cristina DumitruIași92,2
#40Bogdan IonescuConstanța884,1
#41Ana PopescuCluj-Napoca1.676,0
#42Mihai PetrescuTimișoara2.467,9
#43Ioana ConstantinSibiu3.259,8
#44Radu GeorgescuBucurești4.051,7
#45Elena MarinBrașov4.843,6
#46Andrei StanOradea5.635,5
#47Maria DumitruIași6.427,4
#48Vlad IonescuConstanța7.219,3
#49Cristina PopescuCluj-Napoca8.011,2
#50Bogdan PetrescuTimișoara8.803,1
se încarcă mai multe când ajungi aici…
Derulează lista: când santinela de la final se apropie de zona vizibilă, se „încarcă” încă 50 de rânduri. Numărul de elemente DOM crește cu fiecare pagină. Aici e diferența față de virtual scroll.

Când DA, când NU

Virtualizează
  • Mii sau milioane de rânduri, deja în memorie: loguri, tranzacții, tabele de date.
  • Rânduri de aceeași înălțime (sau pe care le poți aduce la aceeași înălțime).
  • Liste derulate des, unde fluiditatea scroll-ului contează.
  • Selectoare cu foarte multe opțiuni (dropdown cu 10.000 de elemente).
Alege altceva
  • Sub câteva sute de rânduri: @for cu track e suficient și mai simplu.
  • Datele vin oricum paginat de la server: paginare sau infinite scroll.
  • Conținut pe care utilizatorii îl caută cu Ctrl+F sau trebuie să fie complet accesibil: paginare, content-visibility.
  • Carduri cu înălțimi foarte diferite: content-visibility sau @defer pe secțiuni.

Capcane frecvente

  • Viewport fără înălțime: se întinde cât lista, deci randează tot, adică nicio virtualizare. Dă-i o înălțime fixă sau flex.
  • itemSize diferit de înălțimea reală (padding, border, line-height): rândurile „sar”, apar goluri, iar scrollToIndex ajunge în altă parte.
  • Fără trackBy la date care se reîncarcă: tot DOM-ul vizibil e recreat la fiecare actualizare.
  • Containerul își schimbă dimensiunea (panou închis, tab activat) și viewport-ul nu știe. Apelează checkViewportSize().
  • Infinite scroll fără disconnect() la observer: callback-uri după distrugerea componentei.

Fișă rapidă

NUMĂR DE RÂNDURI   SOLUȚIE
< ~300             @for simplu, cu track
300 – câteva mii   paginare sau infinite scroll (date încărcate pe bucăți) · content-visibility: auto
mii – milioane     CDK virtual scroll (randează doar ce se vede + buffer)

CDK
  importuri        CdkVirtualScrollViewport, CdkFixedSizeVirtualScroll, CdkVirtualForOf (@angular/cdk/scrolling)
  obligatoriu      înălțime pe viewport + itemSize = înălțimea reală a rândului
  opțiuni          minBufferPx / maxBufferPx · templateCacheSize · appendOnly · orientation="horizontal"
  API              scrollToIndex(i, 'smooth') · scrolledIndexChange · getRenderedRange() · checkViewportSize()
  înălțimi variabile  autosize e în @angular/cdk-experimental, nu e gata de producție

CAPCANE  @for nu virtualizează · fără trackBy se recreează DOM la fiecare scroll · Ctrl+F nu găsește rândurile nerandate

Cap. 16 · 0 din 4 terminate

Ai terminat lecția?

Marcheaz-o și apare pe harta ta: în cuprins și pe prima pagină.

Se marchează singură după ce citești toată lecția.

  1. Web Workers
  2. Liste uriașe: virtual scroll
  3. Animații moderne & View Transitions
  4. Memory leaks: cum le găsești și le repari
Vezi toată harta ta

Toate cele 75 de pagini · scrie ca să filtrezi

  • 01Ce e nou în Angular 21 & 22Toate schimbările importante din v21 și v22, pe scurt, cu link către pagina care le explică.Start
  • 01Prima ta aplicație, pas cu pasO listă de lecturi construită de la o componentă goală la o aplicație terminată — signals, @for, input-uri și output-uri, un serviciu și un formular, pas cu pas.Start
  • 01Decizii rapide„Unde pun asta?”, „Signal sau Observable?”, „Shared sau feature?” — răspunsuri scurte la întrebările de zi cu zi.Start
  • 01Întrebări de interviu Angular60 de întrebări reale de interviu pentru junior, mid și senior, cu răspunsul de dat, întrebarea care urmează și greșelile de evitat. Plus o simulare de interviu.Start
  • 02Standalone & bootstrapCum pornește o aplicație fără NgModule: bootstrapApplication, app.config.ts, imports în componentă.Fundamente moderne
  • 02Data binding și evenimenteInterpolare, binding pe proprietăți și pe atribute, evenimente cu $event și filtre de taste și cum e construit [( )] — ce intră în DOM și ce se întoarce.Fundamente moderne
  • 02Control flow: @if, @for, @switchSintaxa de blocuri care înlocuiește *ngIf/*ngFor/ngSwitch, cu track, @empty și verificare exhaustivă.Fundamente moderne
  • 02@let și expresiile noi din templateVariabile locale cu @let, arrow functions, spread, regex, instanceof, comentarii în tag-uri.Fundamente moderne
  • 02@defer — lazy loading în templateÎncarci componente grele doar când e nevoie: triggere, prefetch, placeholder, loading, error.Fundamente moderne
  • 02Class & style bindingsDe ce [class.x] și [style.x] au înlocuit ngClass și ngStyle (cu migrare automată din v21).Fundamente moderne
  • 02Stilurile componentelor și încapsularea:host, cele trei moduri de încapsulare, teme cu variabile CSS și de ce ::ng-deep e ultima soluție.Fundamente moderne
  • 02Pipes: când și cumPipe pur vs impur, pipe custom și cum alegi între pipe, computed() și o metodă.Fundamente moderne
  • 03signal() și computed()Primitivele reactive: stare scriibilă, stare derivată, egalitate, untracked și reguli de imutabilitate.Signals & reactivitate
  • 03effect() — când DA, când NUEfecte secundare sincronizate cu semnale: localStorage, logging, API-uri imperative. Și capcanele lor.Signals & reactivitate
  • 03linkedSignal() — stare derivată editabilăUn semnal care se resetează când se schimbă sursa, dar pe care îl poți și seta manual.Signals & reactivitate
  • 03resource, rxResource, httpResourceDate asincrone ca semnale: status, value, error, reload, anulare automată. Stabile din v22.Signals & reactivitate
  • 03debounced() — semnale cu întârziereVarianta „debounce” a unui semnal, fără RxJS. Perfect pentru căutare.Signals & reactivitate
  • 03Liste: căutare, filtrare, sortare, paginareUn lanț de computed-uri care recalculează doar ce s-a schimbat, cu filtrele ținute în URL, ca un link să redeschidă aceeași vedere.Signals & reactivitate
  • 03Observables de la zeroCe e un Observable, subscribe și unsubscribe, operatorii pe care îi folosești în fiecare săptămână, cold vs hot — și când ajunge un signal.Signals & reactivitate
  • 03Signals ↔ RxJStoSignal, toObservable, takeUntilDestroyed, outputFromObservable — și când RxJS rămâne alegerea bună.Signals & reactivitate
  • 04input() și output()Inputuri ca semnale (required, transform, alias) și outputuri tipizate. Data down, events up.Componente: comunicare
  • 04model() — two-way bindingBanana-in-a-box [( )] cu un singur API. Când îl folosești și când e mai bine input + output.Componente: comunicare
  • 04viewChild & contentChildInterogări ca semnale: viewChild, viewChildren, contentChild, contentChildren, opțiunea read.Componente: comunicare
  • 04Lifecycle & DestroyRefCe hook-uri mai contează în era signals, afterNextRender, DestroyRef și ngOnChanges tipizat.Componente: comunicare
  • 04Content projectionng-content cu select, sloturi multiple, conținut implicit și capcana proiecției condiționale.Componente: comunicare
  • 04ng-template & ngTemplateOutletTemplate-uri ca valori: render props în Angular, context tipizat și personalizare de componente.Componente: comunicare
  • 05Smart vs DumbContainer vs presentational: cine ține starea, cine doar afișează, și cum le împarți corect.Design de componente
  • 05Componente generice <T>Un DataTable<T> sau Select<T> tipizat corect, cu template-uri pentru celule.Design de componente
  • 05Componente dinamiceNgComponentOutlet, createComponent cu inputBinding/outputBinding și un dashboard construit din config.Design de componente
  • 05Directive & host compositionDirective atribut, metadata host: {} și Directive Composition API (hostDirectives).Design de componente
  • 05Controale custom de formularFormValueControl (Signal Forms) vs ControlValueAccessor: un rating cu stele, în ambele variante.Design de componente
  • 05Change detectionOnPush e implicit din v22, zone.js lipsește din v21. Ce declanșează randarea și cum să nu te arzi.Design de componente
  • 06inject() & @Service()Noul decorator @Service() din v22, inject() vs constructor și contextul de injecție.Dependency Injection
  • 06Providers & ierarhia de injectoriRoot vs rută vs componentă, useClass/useValue/useFactory/useExisting și modificatorii optional/self/skipSelf.Dependency Injection
  • 06InjectionToken & provideX()Configurare tipizată, funcții provideFeature(), makeEnvironmentProviders și inițializatori.Dependency Injection
  • 06injectAsync() — servicii lazyÎncarci un serviciu greu abia când ai nevoie de el, cu prefetch la idle.Dependency Injection
  • 07Structura de folderecore / shared / features, ce intră în fiecare și cum arată un feature pe dinăuntru.Arhitectura aplicației
  • 07Shared vs featureCând muți o componentă în shared și (mai ales) când NU. Checklist concret.Arhitectura aplicației
  • 07Interfețe & modeleInterfață inline sau fișier separat? DTO vs model de domeniu, type vs interface, unde le pui.Arhitectura aplicației
  • 07State management cu signalsStore-uri simple pe semnale, facade, stare locală vs globală și când îți trebuie NgRx.Arhitectura aplicației
  • 07Style guide & namingConvențiile oficiale actuale: nume de fișiere fără sufixe, protected, readonly, ordinea membrilor.Arhitectura aplicației
  • 07Barrels, aliasuri & granițeindex.ts, path aliases, API public pe feature și reguli de dependență care se pot verifica automat.Arhitectura aplicației
  • 07Erori: @boundary & ErrorHandlerNoul bloc @boundary din v22.2, ErrorHandler global, erori HTTP și erori din resource.Arhitectura aplicației
  • 08Routing modern & lazy loadingloadComponent, rute copil, parametri ca input-uri și moștenirea parametrilor (default nou în v22).Routing
  • 08Guards & resolverscanActivate, canMatch, canDeactivate și resolve ca funcții, cu redirect prin UrlTree / RedirectCommand.Routing
  • 09Signal Forms — bazeleFormulare construite pe semnale: form(), [formField], validare prin schemă. Stabile din v22.Formulare
  • 09Signal Forms — avansatValidare custom și cross-field, async/HTTP cu debounce, liste dinamice, disabled/hidden, scheme reutilizabile.Formulare
  • 09Reactive Forms tipizateFormGroup tipizat, nonNullable, FormArray — și cum conviețuiesc cu Signal Forms.Formulare
  • 10HttpClient, interceptors & API layerHttpClient fără setup (v21), Fetch implicit (v22), interceptori funcționali și stratul de API cu mapare DTO.HTTP & date
  • 11Performanță — checklistCe chiar contează: OnPush + signals, track, @defer, lazy routes, imagini, bundle și hidratare.Calitate & performanță
  • 11Testare cu VitestVitest e runner-ul implicit din v21. TestBed, componente cu semnale, servicii, HTTP și harness-uri.Calitate & performanță
  • 11DevTools: cum te uiți în aplicațieChrome DevTools și Angular DevTools pas cu pas: ce arată fiecare panou, cu exerciții care merg chiar pe site-ul ăsta.Calitate & performanță
  • 11Angular AriaComponente headless, accesibile, pe care le stilizezi cum vrei: tabs, accordion, menu, listbox...Calitate & performanță
  • 12SSR, prerender & render modesCând randezi pe server, când la build și când doar în browser: RenderMode pe rută, app.routes.server.ts și costurile reale.SSR & hidratare
  • 12Hidratare: event replay & incrementalCum preia browserul HTML-ul de pe server fără să-l refacă: hidratare completă, event replay, @defer (hydrate on …) și erorile de nepotrivire.SSR & hidratare
  • 12Cod compatibil cu serverulwindow nu există pe server: PLATFORM_ID, afterNextRender, DOCUMENT, TransferState, cache-ul HTTP și PendingTasks.SSR & hidratare
  • 13Micro frontendsCând merită să spargi o aplicație în mai multe deploy-uri și când e doar complexitate: Native Federation, Module Federation, iframes, Web Components.Arhitectură la scară
  • 13Web Components cu Angular ElementscreateCustomElement: o componentă Angular folosită ca tag HTML oriunde, plus comunicarea între aplicații prin evenimente și BroadcastChannel.Arhitectură la scară
  • 13Monorepo, librării & granițeUn workspace cu mai multe aplicații și librării: ng generate library, Nx, entry points secundare, publicare și reguli de dependență.Arhitectură la scară
  • 13Arhitectură hexagonală (ports & adapters)Domeniul fără Angular, porturi ca clase abstracte și adaptoare interschimbabile: HTTP, localStorage sau in-memory, fără să atingi UI-ul.Arhitectură la scară
  • 14Semnalele pe dinăuntruGraful reactiv: producători și consumatori, push-pull, de ce computed e lazy și glitch-free, problema diamantului și când rulează efectele.Reactivitate avansată
  • 14RxJS avansat: operatori de ordin superiorswitchMap, mergeMap, concatMap și exhaustMap pe o diagramă live, plus retry cu backoff, shareReplay și cum eviți scurgerile.Reactivitate avansată
  • 14NgRx SignalStoreState management la scară pe semnale: withState, withComputed, withMethods, rxMethod, entități și hooks — și când un store simplu e suficient.Reactivitate avansată
  • 14Mașini de stări cu signalsFără stări imposibile: o mașină tipizată pe signals, cu tranziții, guards și diagrama stărilor, live.Reactivitate avansată
  • 15WebSockets: timp real care rezistă rețeleiUn socket tipizat ca signals: reconectare cu backoff și jitter, heartbeat, coadă de mesaje cât ești offline și siguranță la SSR.Timp real și offline
  • 15Actualizări optimiste și rollbackInterfață instantanee, dar onestă: snapshot și rollback, răspunsuri sosite în altă ordine, chei de idempotență.Timp real și offline
  • 15Offline-first: Service Worker și cacheService Worker-ul Angular: strategii de cache, anunțarea versiunilor noi și o coadă pentru modificările făcute offline.Timp real și offline
  • 16Web WorkersCalcule grele fără să blochezi interfața: un worker real în pagină, comunicare prin mesaje și când nu merită.UI & performanță avansată
  • 16Liste uriașe: virtual scroll100.000 de rânduri fără lag: CDK virtual scroll, itemSize, trackBy și alternativele (paginare, infinite scroll).UI & performanță avansată
  • 16Animații moderne & View Transitionsanimate.enter și animate.leave în locul @angular/animations (depreciat), tranziții CSS și View Transitions la navigare.UI & performanță avansată
  • 16Memory leaks: cum le găsești și le repariSubscripții, timere și listeneri care trăiesc mai mult decât componenta lor — cum îi vezi în DevTools și cum îi închizi.UI & performanță avansată
  • 17Securitate: XSS, sanitizare & CSPCe face Angular automat împotriva XSS, când devine periculos bypassSecurityTrust, plus CSP cu nonce, Trusted Types și XSRF.Securitate & globalizare
  • 17Autentificare: token-uri & refreshUn interceptor care atașează token-ul, reîmprospătează o singură dată la 401 și reia cererile din coadă — plus unde ții token-ul.Securitate & globalizare
  • 17OAuth 2.1 și OpenID Connect cu PKCEFluxul authorization code cu PKCE, pas cu pas: verifier și challenge, state și nonce, ID token-ul și de ce un BFF e mai sigur.Securitate & globalizare
  • 17Internaționalizare (i18n)$localize, atributul i18n, ICU plural/select, build per limbă vs. traduceri la runtime și formatarea după locale.Securitate & globalizare