AngularAtlased. 22.2

Cap. 03Signals & reactivitate18 / 75

Liste: căutare, filtrare, sortare, paginare

Un lanț de computed-uri care recalculează doar ce s-a schimbat, cu filtrele ținute în URL, ca un link să redeschidă aceeași vedere.

Aproape orice aplicație are o listă în care cauți, filtrezi, sortezi și treci de la o pagină la alta. Cu signals ajung câteva rânduri: filtrele sunt signal-uri, iar lista de pe ecran e un computed construit din ele. Partea interesantă e cum împarți acel computed.

Pagina asta construiește un catalog pas cu pas: un lanț de computed-uri care recalculează doar ce s-a schimbat, linkedSignal ca să resetezi pagina și filtrele ținute în URL, ca un link sau un reload să aducă înapoi aceeași vedere.

De ce contează
O listă care recalculează totul la fiecare tastă merge bine cu 50 de elemente și se poticnește la 5.000. O listă ale cărei filtre trăiesc doar în memorie le pierde la reload, iar butonul ei Back face ceva la care nu se așteaptă nimeni.

Starea: câteva signal-uri

Fiecare control scrie un signal: query, category, sort, page. Nu se mai păstrează nimic altceva — lista vizibilă e mereu derivată, deci nu se poate îndepărta niciodată de filtre.

export class Catalog {
  private readonly products = inject(ProductStore).all; // Signal<Product[]>

  // un signal per control
  readonly query = signal('');
  readonly category = signal<Category | 'all'>('all');
  readonly sort = signal<Sort>('relevance');
  readonly page = signal(1);
}

Un lanț de computed-uri

Pune fiecare pas în propriul computed: filtered citește datele și filtrele, sorted citește filtered și ordinea, visible citește sorted și pagina. Un computed rulează din nou doar când s-a schimbat ceva din ce a citit, deci schimbarea paginii rulează doar tăierea listei.

// fiecare pas citește doar ce îi trebuie — și rulează din nou doar când asta se schimbă
readonly filtered = computed(() => {
  const q = fold(this.query().trim());
  const cat = this.category();
  return this.products().filter(
    (p) => (cat === 'all' || p.category === cat) && fold(p.name).includes(q),
  );
});

readonly sorted = computed(() =>
  this.filtered().toSorted(comparator(this.sort())), // o copie: nu sorta niciodată array-ul din signal
);

readonly visible = computed(() => {
  const start = (this.page() - 1) * PAGE_SIZE;
  return this.sorted().slice(start, start + PAGE_SIZE); // se schimbă pagina → rulează doar asta
});

readonly pages = computed(() => Math.max(1, Math.ceil(this.sorted().length / PAGE_SIZE)));
LiveAcelași catalog, în două forme
TelefonTelefoane și tablete351,99 EUR★ 2.6
Telefon MiniTelefoane și tablete509,99 EUR★ 2.7
Telefon AirTelefoane și tablete552,99 EUR★ 4.3
Telefon ProTelefoane și tablete920,99 EUR★ 4.6
Telefon MaxTelefoane și tablete778,99 EUR★ 4.8
TabletăTelefoane și tablete377,99 EUR★ 3.9
Pagina 1 din 11 · 65 produse
PasRulăriUltima rulare
filtered1a verificat 65 produse
sorted164 comparații
visible1a luat 6 elemente
Încearcă: pagina următoare → rulează doar visible.
Scrie în căutare, schimbă ordinea, apoi treci prin pagini — și urmărește coloana Rulări. Apoi treci pe un singur computed mare și fă la fel.

În lanț, butoanele de pagină rulează doar visible; sortarea rulează sorted și visible; doar filtrele ajung la filtered. Într-un singur computed mare, fiecare click filtrează și sortează din nou toate cele 65 de produse. Contează și ordinea: întâi filtrezi (rămân mai puține de sortat), la final tai pagina. Și rulează doar forma de pe ecran: un computed pe care nu-l citește nimeni nu se execută niciodată. Dacă tastezi repede, poți vedea mai puține rulări decât taste: un computed se recalculează doar când e citit, iar Angular îl citește o dată pe randare.

Înapoi la pagina 1 când se schimbă filtrele

Dacă ești pe pagina 5 și cauți ceva cu două pagini de rezultate, ajungi pe „pagina 5 din 2”. linkedSignal e făcut exact pentru asta: un signal în care poți scrie și care se resetează singur ori de câte ori i se schimbă sursa — aici, orice filtru sau ordinea. Vezi linkedSignal.

// un signal în care poți scrie și care revine la 1 când se schimbă un filtru sau ordinea
readonly page = linkedSignal({
  source: () => [this.query(), this.category(), this.sort()],
  computation: () => 1,
});

next() {
  this.page.update((p) => Math.min(p + 1, this.pages()));
}

O căutare care se simte firesc

Compară text normalizat, cu litere mici și fără diacritice (normalize('NFD')), ca „casti” să găsească „Căști”. Sortează numele cu localeCompare în limba utilizatorului. Pentru căutări care merg la server, așteaptă o pauză în tastare cu debounced().

/** litere mici, fără diacritice: "casti" găsește "Căști", "cafe" găsește "Café" */
export function fold(text: string): string {
  return text.normalize('NFD').replace(/\p{Diacritic}/gu, '').toLowerCase();
}

// nume sortate cum se așteaptă utilizatorul în limba lui
const byName = (a: Product, b: Product) => a.name.localeCompare(b.name, locale);

Randarea: track după id

@for (p of visible(); track p.id) îl lasă pe Angular să mute rândurile existente în loc să le recreeze când se schimbă ordinea. Cu track $index, fiecare rând ar fi randat din nou după fiecare sortare. Mai multe la control flow.

<input type="search" [value]="query()" (input)="query.set(search.value)" #search />

@for (p of visible(); track p.id) {
  <app-product-row [product]="p" />
} @empty {
  <p>Niciun produs nu se potrivește.</p>
}

<button (click)="page.set(page() - 1)" [disabled]="page() === 1">Înapoi</button>
<span>Pagina {{ page() }} din {{ pages() }}</span>
<button (click)="page.set(page() + 1)" [disabled]="page() === pages()">Înainte</button>

Filtrele în URL

Filtrele ținute doar în signal-uri dispar la reload și nu pot fi trimise nimănui. Pune-le în query string — /catalog?q=pro&cat=audio — și URL-ul devine starea: un link redeschide aceeași vedere, iar Back se întoarce la vederea anterioară în loc să plece de pe pagină.

LiveUnde stau filtrele: în memorie sau în bara de adresă
/catalog

65 rezultate · Telefon, Telefon Mini, Telefon Air

Istoricul browserului
  1. /catalog
Link către vederea asta

Cine deschide linkul vede exact ce conține linkul — nimic în plus.

În fiecare mod, scrie câteva litere, schimbă categoria, apoi folosește Back, Reload și Deschide linkul.

Doar cu signal-uri, Back pleacă din catalog, iar Reload îl golește. Dacă pui fiecare schimbare în istoric, problema dispare, dar Back devine un undo pentru fiecare tastă. Compromisul obișnuit: înlocuiești intrarea curentă din istoric cât timp utilizatorul tastează și adaugi una nouă pentru alegeri clare, cum e o categorie.

// app.config.ts: provideRouter(routes, withComponentInputBinding())

export class Catalog {
  private readonly router = inject(Router);

  // ?q=…&cat=… ajung ca input-uri; URL-ul e sursa de adevăr
  readonly q = input('');
  readonly cat = input<Category | 'all'>('all');

  search(text: string) {
    // tastare: înlocuiești intrarea curentă din istoric, nu adaugi câte una pe tastă
    this.update({ q: text || null }, true);
  }

  pickCategory(cat: Category | 'all') {
    this.update({ cat: cat === 'all' ? null : cat }, false); // un pas real: Back se întoarce aici
  }

  private update(queryParams: Params, replaceUrl: boolean) {
    // null scoate un parametru: valorile implicite nu ajung în URL
    this.router.navigate([], { queryParams, queryParamsHandling: 'merge', replaceUrl });
  }
}

Cu withComponentInputBinding(), parametrii din query ajung ca input-uri ale componentei; ca să-i scrii înapoi, faci un router.navigate() cu queryParamsHandling: 'merge'. Ține URL-ul scurt lăsând deoparte valorile implicite, cum face și laboratorul.

Când lista devine uriașă

Peste câteva mii de rânduri, randarea devine gâtul de sticlă, nu filtrarea: randează doar ce e pe ecran, cu virtual scroll. Când datele stau pe server, trimite filtrele ca parametri ai cererii și lasă httpResource să reîncarce când se schimbă.

Capcane frecvente

  • Sortezi pe loc array-ul din signal. list.sort() îl modifică. Fă întâi o copie ([...list] sau toSorted()), altfel următorul filtru lucrează pe o sursă reordonată.
  • Ții lista filtrată într-un signal. Atunci fiecare control trebuie să-și amintească s-o actualizeze. Deriv-o cu computed.
  • „Pagina 5 din 2”. Pagina nu s-a resetat când s-au schimbat filtrele. Folosește linkedSignal.
  • O intrare în istoric pentru fiecare tastă. Folosește replaceUrl: true cât timp utilizatorul tastează.
  • track $index pe o listă sortabilă. Fiecare sortare randează din nou fiecare rând și pierde focusul și ce era scris în câmpuri.

Fișă rapidă

stare                  un signal per control: query, category, sort, page
derivat                filtered → sorted → visible, fiecare cu propriul computed()
ordine                 întâi filtrezi, sortezi mai puține elemente, tai pagina la final
leneș                  un computed pe care nu-l citește nimeni nu rulează niciodată
resetarea paginii      linkedSignal({ source: () => [filtre…], computation: () => 1 })
nu modifica            toSorted() / [...list].sort(); array-ul din signal rămâne cum era
căutare                normalize('NFD') + litere mici; localeCompare(b, locale) pentru nume
randare                @for (… ; track p.id), niciodată track $index pe o listă sortabilă
URL                    ?q=&cat=&sort=; withComponentInputBinding(); fără valorile implicite
istoric                replaceUrl cât tastezi, push pentru pașii reali (categorie, pagină)
liste uriașe           virtual scroll pentru randare; httpResource când filtrează serverul

Cap. 03 · 0 din 8 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. signal() și computed()
  2. effect() — când DA, când NU
  3. linkedSignal() — stare derivată editabilă
  4. resource, rxResource, httpResource
  5. debounced() — semnale cu întârziere
  6. Liste: căutare, filtrare, sortare, paginare
  7. Observables de la zero
  8. Signals ↔ RxJS
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