AngularAtlased. 22.2

Cap. 13Arhitectură la scară60 / 75

Arhitectură 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ă ~0 min de citit

Arhitectura hexagonală (sau ports & adapters) pornește de la o idee simplă: regulile aplicației nu trebuie să știe de unde vin datele și cum sunt afișate. Domeniul stă în centru, scris în TypeScript pur. În jurul lui sunt cazurile de utilizare, care vorbesc cu lumea exterioară doar prin porturi, adică contracte. La margine sunt adaptoarele: HTTP, localStorage, componente Angular. Le poți schimba fără să atingi centrul.

În Angular, ideea se potrivește foarte bine cu DI: o clasă abstractă e în același timp tipul portului și token-ul de injecție. { provide: TaskRepository, useClass: HttpTaskRepository } e tot „cablajul” de care ai nevoie.

De ce contează
Când backend-ul se schimbă (alt API, GraphQL, offline-first), modifici un singur adaptor. Când testezi regulile, nu pornești HttpClient. Iar logica de business nu se mai împrăștie prin componente, unde e greu de găsit și de reutilizat.

Straturile și regula dependenței

Sunt patru straturi, iar importurile arată doar spre interior. Domeniul nu importă nimic din afara lui. Application cunoaște domeniul și porturile, dar nu și adaptoarele. Adaptoarele implementează porturile. Singurul loc care leagă un port de un adaptor concret e composition root-ul: app.config.ts sau providers-ul unei rute.

                 ┌──────────────────────── exterior: adaptoare ────────────────────────┐
                 │  presentation (driving)                 infrastructure (driven)     │
  utilizator ──▶ │  TasksPage, TaskItem                    HttpTaskRepository, TaskDto │ ──▶ server
                 │        │   ┌──────────── application ─────────────┐   ▲             │
                 │        └──▶│ TaskBoard / AddTask ──▶ TaskRepository│───┘ implementează│
                 │            │        │      ┌──── domain ────┐     │                  │
                 │            │        └─────▶│ Task, createTask│    │                  │
                 │            │               └─────────────────┘     │                  │
                 │            └───────────────────────────────────────┘                  │
                 └──────────────────────────────────────────────────────────────────────┘
  Regula: săgețile de import arată DOAR spre interior. Domeniul nu importă nimic din afară.
  app.config.ts (composition root) e singurul loc care leagă TaskRepository de un adaptor.
StratConțineAre voie să importe
domainentități, reguli, erori de domeniunimic (doar TypeScript)
applicationporturi (clase abstracte), cazuri de utilizare, starea pentru UIdomain, @angular/core (DI, signals)
infrastructureadaptoare: HTTP, storage, DTO-uri, mapareaapplication (porturile), domain, HttpClient
presentationcomponente smart și dumbapplication, domain (tipuri) — niciodată infrastructure
LiveRegula dependenței: e voie importul ăsta?

1. Alege fișierul care importă.

presentation · adaptoare „driving”
infrastructure · adaptoare „driven”
application · porturi + cazuri de utilizare
domain · TypeScript pur
framework & browser
composition root
Exemple:
Alege de unde și ce importă. Verdictul spune dacă regula e respectată și ce ar trebui să faci în loc.

Domeniul: TypeScript pur

Domeniul conține entitățile și regulile: „un titlu are minimum 3 caractere”, „nu pot exista două sarcini deschise cu același titlu”. Nu importă Angular, RxJS sau API-uri de browser. Chiar și ceasul și generarea de id-uri vin ca parametri, ca regulile să rămână deterministe. De aici câștigul principal: domeniul se testează cu funcții simple, fără TestBed și fără mock-uri.

// tasks/domain/task.ts — TypeScript pur: niciun import din Angular, RxJS sau browser
export type TaskId = string & { readonly __brand: 'TaskId' };

export interface Task {
  readonly id: TaskId;
  readonly title: string;
  readonly done: boolean;
  readonly createdAt: Date;
}

export class DomainError extends Error {
  override readonly name = 'DomainError';
}

export function normalizeTitle(raw: string): string {
  return raw.trim().replace(/\s+/g, ' ');
}

// `now` și `id` vin ca parametri: domeniul nu citește ceasul și nu generează id-uri singur
export function createTask(raw: string, existing: readonly Task[], now: Date, id: TaskId): Task {
  const title = normalizeTitle(raw);
  if (title.length < 3) throw new DomainError('Titlul are nevoie de cel puțin 3 caractere.');
  if (existing.some((t) => !t.done && t.title.toLowerCase() === title.toLowerCase())) {
    throw new DomainError('Există deja o sarcină deschisă cu acest titlu.');
  }
  return { id, title, done: false, createdAt: now };
}

export function toggleTask(task: Task): Task {
  return { ...task, done: !task.done };
}

Porturile: clase abstracte ca token-uri

Portul descrie ce are nevoie aplicația („listează, salvează, șterge”), nu cum. Îl scrii ca clasă abstractă: există la runtime, deci inject(TaskRepository) funcționează direct. O interfață ar dispărea la compilare și ți-ar trebui un InjectionToken separat (vezi InjectionToken). Erorile fac parte din contract: adaptoarele își traduc erorile tehnice (timeout, 503) în RepositoryUnavailableError, ca UI-ul să nu depindă de HttpErrorResponse.

// tasks/application/task-repository.ts — PORTUL
import { Task, TaskId } from '../domain/task';

// Clasă abstractă, nu interfață: există la runtime, deci e direct token de DI.
// Nu are @Injectable și nu știe de nicio implementare.
export abstract class TaskRepository {
  abstract list(): Promise<readonly Task[]>;
  abstract save(task: Task): Promise<void>; // upsert
  abstract remove(id: TaskId): Promise<void>;
}

// Parte din contract: adaptoarele își traduc erorile tehnice în asta.
export class RepositoryUnavailableError extends Error {
  override readonly name = 'RepositoryUnavailableError';
}

Cazurile de utilizare

Un caz de utilizare orchestrează: citește prin port, aplică regulile din domeniu, salvează prin port. În Angular e un serviciu obișnuit care injectează portul, nu adaptorul. Pentru UI e comod un „board” care ține și starea în semnale (tasks, error, open). Componentele doar îl citesc, exact ca la state management.

// tasks/application/add-task.ts — un caz de utilizare = o clasă cu o singură metodă publică
import { Injectable, inject } from '@angular/core';
import { Task, TaskId, createTask } from '../domain/task';
import { TaskRepository } from './task-repository';

@Injectable({ providedIn: 'root' })
export class AddTask {
  private readonly repo = inject(TaskRepository); // portul — nu știe ce adaptor răspunde

  async execute(title: string): Promise<Task> {
    const existing = await this.repo.list();
    const task = createTask(title, existing, new Date(), crypto.randomUUID() as TaskId); // regula: în domeniu
    await this.repo.save(task); // I/O: prin port
    return task;
  }
}

Adaptoarele

Un adaptor extends TaskRepository și face tot I/O-ul: HTTP, localStorage, memorie. Tot aici e și maparea DTO → domeniu (is_done: 1 devine done: true, string-ul ISO devine Date; vezi Interfețe & modele). Formatul serverului nu trece de adaptor.

// tasks/infrastructure/http-task-repository.ts — ADAPTORUL HTTP
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Injectable, inject } from '@angular/core';
import { Observable, firstValueFrom } from 'rxjs';
import { Task, TaskId } from '../domain/task';
import { RepositoryUnavailableError, TaskRepository } from '../application/task-repository';
import { TaskDto, taskFromDto, taskToDto } from './task-dto';

@Injectable()
export class HttpTaskRepository extends TaskRepository {
  private readonly http = inject(HttpClient);
  private readonly url = '/api/tasks';

  async list(): Promise<readonly Task[]> {
    const dtos = await this.call(this.http.get<TaskDto[]>(this.url));
    return dtos.map(taskFromDto); // DTO → domeniu, aici și nicăieri altundeva
  }

  async save(task: Task): Promise<void> {
    await this.call(this.http.put<TaskDto>(`${this.url}/${task.id}`, taskToDto(task)));
  }

  async remove(id: TaskId): Promise<void> {
    await this.call(this.http.delete<void>(`${this.url}/${id}`));
  }

  // HttpErrorResponse nu iese din adaptor: nucleul primește eroarea portului
  private async call<T>(request: Observable<T>): Promise<T> {
    try {
      return await firstValueFrom(request);
    } catch (e) {
      if (e instanceof HttpErrorResponse) throw new RepositoryUnavailableError(`HTTP ${e.status}`);
      throw e;
    }
  }
}
LiveAcelași UI, trei adaptoare

{ provide: TaskRepository, useClass: InMemoryTaskRepository } — Map în memorie — datele dispar când injectorul e distrus.

UI identic: TasksPanel → TaskBoard → TaskRepository
  • Nicio sarcină.
0 deschise din 0 gata
Cine a răspuns
  • 11:45.8DI TaskRepository → InMemoryTaskRepository
  • 11:45.8InMemoryTaskRepository list() → 0 (Map)
Schimbă adaptorul și adaugă sarcini. Componenta și cazurile de utilizare sunt identice; jurnalul arată ce adaptor a răspuns la fiecare apel. Pe HTTP, poți pune serverul offline.

Composition root: unde alegi adaptorul

Legătura port → adaptor stă într-un singur loc. Global, în app.config.ts. Local, în providers-ul unei rute, unde adaptorul trăiește cât ruta (vezi Providers & ierarhia de injectori). Ca feature-ul să-și păstreze detaliile, expune o funcție provideTasks(). Composition root-ul alege doar „http” sau „local”.

// app.config.ts — COMPOSITION ROOT: singurul fișier care știe ce adaptor folosim
export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(withInterceptors([authInterceptor])),
    { provide: TaskRepository, useClass: HttpTaskRepository },
  ],
};

Testare: aici se vede câștigul

Ai trei niveluri de teste, fiecare ieftin:

  • domeniul, cu funcții pure;
  • cazurile de utilizare, cu adaptorul in-memory în locul HTTP-ului;
  • adaptoarele, cu un test de contract, adică aceeași suită rulată pe fiecare implementare a portului.

Testul de contract prinde exact bugurile care altfel scapă: un adaptor care întoarce DTO-uri în loc de entități sau care dublează în loc să actualizeze.

// Testul cazului de utilizare: adaptorul in-memory în locul HTTP-ului
import { TestBed } from '@angular/core/testing';

describe('AddTask', () => {
  let addTask: AddTask;
  let repo: InMemoryTaskRepository;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [{ provide: TaskRepository, useClass: InMemoryTaskRepository }],
    });
    addTask = TestBed.inject(AddTask);
    repo = TestBed.inject(TaskRepository) as InMemoryTaskRepository;
  });

  it('salvează prin port', async () => {
    await addTask.execute('Scrie teste');
    expect((await repo.list()).map((t) => t.title)).toEqual(['Scrie teste']);
  });

  it('nu salvează nimic dacă regula de domeniu e încălcată', async () => {
    await expect(addTask.execute('ab')).rejects.toThrow(DomainError);
    expect(await repo.list()).toEqual([]);
  });
});
LiveTestul de contract, rulat pe fiecare adaptor
6 teste × 4 adaptoare
Test (același pentru toate) In-memorylocalStorageHTTPHTTP fără mapare
un depozit nou e gol····
save() apoi list() întoarce sarcina····
save() pe același id actualizează, nu dublează····
remove() șterge doar sarcina cerută····
done e boolean, createdAt e Date····
întoarce entități de domeniu, nu DTO-uri····
Rulează suita: aceleași teste trec pe in-memory, localStorage și HTTP, dar pică pe adaptorul „HTTP fără mapare”, care scapă DTO-uri în domeniu.

Cum impui regula în echipă

O regulă de arhitectură ținută doar în cap nu rezistă primului deadline. Pune-o în lint. Cu no-restricted-imports din ESLint, separat pe straturi, orice import greșit pică în CI. Într-un monorepo Nx faci același lucru cu tag-uri și @nx/enforce-module-boundaries (vezi Monorepo, librării & granițe și Barrels, aliasuri & granițe).

// Regula dependenței, verificată la fiecare commit (ESLint no-restricted-imports cu regex)
export default [
  {
    files: ['src/app/features/*/domain/**/*.ts'],
    rules: {
      'no-restricted-imports': ['error', { patterns: [
        { regex: '^(@angular/|rxjs)', message: 'Domeniul e TypeScript pur: fără Angular, fără RxJS.' },
        { regex: '/(application|infrastructure|ui)/', message: 'Domeniul nu depinde de straturile exterioare.' },
      ] }],
    },
  },
  {
    files: ['src/app/features/*/application/**/*.ts'],
    rules: {
      'no-restricted-imports': ['error', { patterns: [
        { regex: '/(infrastructure|ui)/', message: 'Application depinde de porturi, nu de adaptoare sau de UI.' },
        { regex: '^@angular/common/http', message: 'HTTP doar în infrastructure.' },
      ] }],
    },
  },
  {
    files: ['src/app/features/*/ui/**/*.ts'],
    rules: {
      'no-restricted-imports': ['error', { patterns: [
        { regex: '/infrastructure/', message: 'UI-ul injectează portul sau cazul de utilizare, nu adaptorul.' },
      ] }],
    },
  },
];
// Într-un monorepo Nx: domain/application/infrastructure ca librării separate, cu tag-uri
// și @nx/enforce-module-boundaries (vezi pagina Monorepo).

Când DA, când NU

Merită ports & adapters
  • Reguli de business reale (prețuri, validări, fluxuri) care trebuie testate și reutilizate.
  • Backend instabil sau în schimbare: API nou, migrare REST → GraphQL, mod offline.
  • Aceeași logică pe mai multe surse de date (server, cache local, mock pentru demo-uri).
  • Aplicații mari și longevive, cu mai multe echipe pe același feature.
E overkill — fă-o simplu
  • Ecrane CRUD care doar afișează ce vine de la server. Ajunge un serviciu cu httpResource.
  • Prototipuri și aplicații mici: patru straturi pentru două formulare încetinesc echipa.
  • Când „portul” are o singură implementare și nu o vei testa separat. Abstracția nu aduce nimic.
  • Logică de UI pură (hover, expandat): rămâne în componentă.

O variantă pragmatică funcționează în majoritatea proiectelor: domeniu pur pentru regulile importante, un port doar acolo unde chiar ai sau vei avea mai multe implementări, iar restul rămâne în structura obișnuită data-access / ui (vezi Structura de foldere).

Capcane frecvente

Portul care importă adaptorul
@Injectable({ providedIn: 'root', useClass: HttpTaskRepository }) pe clasa abstractă pare comod, dar portul ajunge să depindă de infrastructură, iar HttpClient intră în bundle și acolo unde nu-l folosești. Alegerea adaptorului stă în composition root.
// ✗ „Implementarea implicită” pusă pe port
@Injectable({ providedIn: 'root', useClass: HttpTaskRepository }) // portul importă adaptorul!
export abstract class TaskRepository { /* ... */ }
// Application → infrastructure: regula dependenței e încălcată, iar HttpClient
// ajunge în bundle chiar și pentru aplicația care folosește doar localStorage.

// ✓ Portul rămâne gol; alegerea stă în composition root
export abstract class TaskRepository { /* ... */ }
providers: [{ provide: TaskRepository, useClass: HttpTaskRepository }]
  • DTO-uri care scapă în domeniu. Dacă tipul de la server ajunge în componente, orice schimbare de API se propagă peste tot. Maparea stă în adaptor.
  • Componente care injectează adaptorul concret (inject(HttpTaskRepository)). Atunci n-ai port deloc. Injectează portul sau cazul de utilizare.
  • Domeniu „anemic”. Entități fără reguli, cu toată logica în servicii: ai straturile, dar nu și beneficiul.
  • Erori tehnice care urcă până în UI. HttpErrorResponse în componentă înseamnă că UI-ul știe că e HTTP. Tradu erorile în adaptor.
  • Ceas și id-uri citite în domeniu (new Date(), crypto.randomUUID()). Regulile devin greu de testat. Primește-le ca parametri.

Fișă rapidă

STRATURI (importurile arată doar spre interior)
  domain          Task, createTask()         TypeScript pur — fără Angular, RxJS, browser
  application     TaskRepository (port)      clasă abstractă = tip + token de DI
                  AddTask / TaskBoard        inject(TaskRepository), semnale pentru UI
  infrastructure  HttpTaskRepository         extends TaskRepository, DTO ↔ domeniu, erori traduse
  presentation    TasksPage, TaskItem        injectează application, niciodată infrastructure
  composition     app.config / route.providers  { provide: TaskRepository, useClass: Http… }

REȚETE
  schimbi adaptorul        alt useClass în composition root (sau provideTasks('local'))
  test pentru use case     providers: [{ provide: TaskRepository, useClass: InMemoryTaskRepository }]
  test pentru adaptor      contract comun + HttpTestingController pentru maparea HTTP
  DTO                      doar în infrastructure; portul vorbește în entități
  erori                    adaptorul aruncă RepositoryUnavailableError, nu HttpErrorResponse

CÂND NU
  CRUD fără reguli, app mică, prototip → httpResource în componentă + mapare în parse

Cap. 13 · 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. Micro frontends
  2. Web Components cu Angular Elements
  3. Monorepo, librării & granițe
  4. Arhitectură hexagonală (ports & adapters)
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