AngularAtlased. 22.2

Cap. 05Design de componente28 / 75

Componente generice <T>

Un DataTable<T> sau Select<T> tipizat corect, cu template-uri pentru celule.

arhitectură ~0 min de citit Documentația oficială

O componentă generică are un parametru de tip — DataTable<T>, Select<T>, List<T> — și funcționează cu orice tip de date, păstrând siguranța tipurilor în template și în output-uri. Angular deduce T din input-ul pe care îl legi (de exemplu din [rows]), exact ca o funcție generică în TypeScript.

Exemplu: DataTable<T>

Tabelul se ocupă de comportament (sortare, selecție, rând gol), iar cine îl folosește dă coloanele (configurare tipizată) și, opțional, template-uri pentru celule speciale.

export interface Column<T> {
  key: Extract<keyof T, string>;      // doar chei reale din T
  header: string;
  sortable?: boolean;
  align?: 'left' | 'right';
  format?: (row: T) => string;
}

@Component({
  selector: 'app-data-table',
  imports: [NgTemplateOutlet],
  template: `
    <table>
      <thead>
        <tr>
          @for (col of columns(); track col.key) { <th (click)="toggleSort(col.key)">{{ col.header }}</th> }
        </tr>
      </thead>
      <tbody>
        @for (row of sortedRows(); track trackBy()(row)) {
          <tr (click)="rowClick.emit(row)">
            @for (col of columns(); track col.key) {
              <td>
                @if (cellTemplates().get(col.key); as tpl) {
                  <ng-container [ngTemplateOutlet]="tpl" [ngTemplateOutletContext]="{ $implicit: row, value: row[col.key] }" />
                } @else {
                  {{ col.format ? col.format(row) : row[col.key] }}
                }
              </td>
            }
          </tr>
        }
      </tbody>
    </table>
  `,
})
export class DataTable<T> {                       // ← parametrul de tip al componentei
  readonly rows = input.required<readonly T[]>(); // ← de aici e dedus T
  readonly columns = input.required<readonly Column<T>[]>();
  readonly trackBy = input<(row: T) => unknown>((row) => row);
  readonly rowClick = output<T>();               // ← output tipizat

  private readonly cellDirectives = contentChildren(CellTemplate<T>);
  protected readonly cellTemplates = computed(
    () => new Map(this.cellDirectives().map((d) => [d.appCell(), d.template] as const)),
  );

  protected readonly sort = signal<{ key: keyof T; dir: 'asc' | 'desc' } | null>(null);
  protected readonly sortedRows = computed(() => { /* sortare pe this.sort() */ });
}
LiveAcelași tabel generic pentru useri și pentru produse

DataTable<UserDto>

Ana Popescu Cluj-Napoca admin 2021
Mihai Ionescu București editor 2022
Ioana Dumitru Iași viewer 2023
Radu Stan Timișoara editor 2020
Elena Marin Brașov viewer 2024
Andrei Georgescu Cluj-Napoca admin 2019
Maria Constantin Constanța viewer 2023
Vlad Petrescu Sibiu editor 2022
Cristina Pop Oradea viewer 2024
Bogdan Rusu Iași viewer 2021

Ultimul click, tipizat ca UserDto: — apasă pe un rând

DataTable<ProductDto>

Categorie
Laptop Pro 14 laptop ★ 4.7 4 buc. 7.499 RON
Laptop Air 13 laptop ★ 4.4 epuizat4.999 RON
Telefon X phone ★ 4.6 12 buc. 3.999 RON
Telefon Mini phone ★ 4.1 7 buc. 2.499 RON
Căști Studio audio ★ 4.8 20 buc. 1.299 RON
Boxă Portabilă audio ★ 4 epuizat449 RON
Mouse Ergo accessory ★ 4.5 35 buc. 349 RON
Tastatură Mecanică accessory ★ 4.9 9 buc. 699 RON
Hub USB-C accessory ★ 3.9 50 buc. 229 RON
Monitor 27" accessory ★ 4.6 3 buc. 1.899 RON
Sortează coloanele. Click pe un user: output-ul rowClick e tipizat ca UserDto, deci .email există fără cast.

Cum îl folosești

<app-data-table
  [rows]="users()"
  [columns]="userColumns"
  [trackBy]="byId"
  (rowClick)="open($event)"      <!-- $event: UserDto -->
>
  <ng-template appCell="role" [appCellOf]="users()" let-user>
    <app-role-badge [role]="user.role" />   <!-- user: UserDto, autocomplete funcționează -->
  </ng-template>
</app-data-table>

Încă un exemplu: Select<T>

// Alt exemplu clasic: un Select<T> care merge cu orice obiect
@Component({
  selector: 'app-select',
  template: `
    <select (change)="pick($any($event.target).selectedIndex - 1)">
      <option disabled [selected]="!value()">{{ placeholder() }}</option>
      @for (o of options(); track $index) {
        <option [selected]="isSelected(o)">{{ displayWith()(o) }}</option>
      }
    </select>
  `,
})
export class Select<T> {
  readonly options = input.required<readonly T[]>();
  readonly value = model<T | null>(null);
  readonly displayWith = input<(item: T) => string>((item) => String(item));
  readonly compareWith = input<(a: T, b: T) => boolean>((a, b) => a === b);
  readonly placeholder = input('Alege…');

  protected isSelected(o: T) {
    const v = this.value();
    return v !== null && this.compareWith()(o, v);
  }
  protected pick(i: number) {
    this.value.set(this.options()[i] ?? null);
  }
}

// <app-select [options]="countries()" [(value)]="country" [displayWith]="byName" />

Compoziție: wrapper de domeniu peste componenta generică

Componenta generică nu trebuie să știe nimic despre domeniu. Când ai același tabel de useri în trei pagini, nu adaugi showRoleColumn în DataTable — faci un UsersTable în feature-ul users care îl înfășoară.

// Compoziție: componenta de domeniu înfășoară componenta generică.
// DataTable<T> stă în shared/ui; UsersTable stă în features/users/ui.
@Component({
  selector: 'app-users-table',
  imports: [DataTable, CellTemplate, RoleBadge],
  template: `
    <app-data-table [rows]="users()" [columns]="columns" [trackBy]="byId" clickable (rowClick)="open.emit($event)">
      <ng-template appCell="role" [appCellOf]="users()" let-user>
        <app-role-badge [role]="user.role" />
      </ng-template>
    </app-data-table>
  `,
})
export class UsersTable {
  readonly users = input.required<User[]>();
  readonly open = output<User>();
  protected readonly byId = (u: User) => u.id;
  protected readonly columns: Column<User>[] = [/* coloanele de domeniu, într-un singur loc */];
}

Anti-pattern: „god component”

// ✗ „Componenta generică” care a crescut necontrolat
@Component({ selector: 'app-table' })
export class Table {
  @Input() data: any[] = [];                  // any → zero siguranță
  @Input() showRoleColumn = false;            // cunoștințe de domeniu în shared
  @Input() roleColors: Record<string, string> = {};
  @Input() isProductTable = false;            // if-uri pe „ce fel de tabel sunt”
  @Input() priceCurrency = 'RON';
  @Input() highlightAdmins = true;
  @Input() exportToExcel = false;
  // ... încă 30 de input-uri
}
// Soluția: tipuri generice + template-uri pentru celule + wrapper-e de domeniu.

Când o fac generică?

Fă-o generică
  • Același comportament pe date diferite: tabel, listă, select, autocomplete, tree, paginator.
  • Ai deja 2–3 utilizări reale (nu „poate o să trebuiască”).
  • Poți descrie API-ul fără cuvinte de domeniu (fără „user”, „order”, „invoice”).
  • Vrei s-o pui în shared/ui sau într-o librărie.
Las-o specifică
  • Ai o singură utilizare — generalizarea prematură costă mai mult decât duplicarea.
  • Diferențele sunt de comportament, nu de date (atunci ai două componente diferite).
  • API-ul ar avea nevoie de flag-uri de tip isProductTable.
  • E o componentă de domeniu (InvoiceSummary) — rămâne în feature.

Configurare vs template-uri

  • Configurare (columns cu format): pentru cazurile simple, 80% din coloane.
  • Template-uri (appCell): pentru celule bogate (badge-uri, butoane, link-uri).
  • Proiecție (<ng-content>): pentru zone fixe (toolbar, footer).

Pentru părți de interfață pe care le alege cel care folosește componenta, vezi ng-template & ngTemplateOutlet; pentru tipurile din spatele lui T, Interfețe & modele.

Capcane frecvente

  • T dedus ca unknown: dacă input-ul din care se deduce lipsește sau e any. Tipizează datele la sursă.
  • Template-urile let-* sunt any fără ngTemplateContextGuard — adaugă directiva cu guard.
  • track row implicit e ok pentru date stabile; pentru date reîncărcate de pe server dă un trackBy pe id.
  • Prea multe input-uri: peste ~8–10 e semnal că amesteci responsabilități. Împarte sau folosește template-uri.

Cap. 05 · 0 din 6 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. Smart vs Dumb
  2. Componente generice <T>
  3. Componente dinamice
  4. Directive & host composition
  5. Controale custom de formular
  6. Change detection
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