AngularAtlased. 22.2

Cap. 11Calitate & performanță51 / 75

Testare cu Vitest

Vitest e runner-ul implicit din v21. TestBed, componente cu semnale, servicii, HTTP și harness-uri.

v21v22 ~0 min de citit Documentația oficială

Din v21, Angular CLI folosește Vitest ca runner implicit pentru testele unitare (Karma e deprecat). TestBed rămâne API-ul principal, dar testele moderne arată altfel: input-uri date ca semnale prin bindings, await fixture.whenStable() în loc de fakeAsync (aplicațiile sunt zoneless) și store-uri pe semnale testate fără DOM.

Noutăți 21 & 22 în testare
  • v21: Vitest implicit (@angular/build:unit-test), stabil.
  • v21: TestBed furnizează un PlatformLocation fals implicit.
  • v22: TestBed.getLastFixture(); ChangeDetectorRef.checkNoChanges eliminat (folosește fixture.detectChanges()).
  • v22.2: TestBed.createDirective() pentru testarea directivelor.

Setup

# v21: proiectele noi folosesc Vitest (Karma e deprecat)
ng new my-app            # → "test": { "builder": "@angular/build:unit-test" } + vitest
ng test                  # rulează testele (watch în dev)
ng test --no-watch --coverage

# Proiect vechi pe Karma/Jasmine → migrare asistată
ng generate refactor-jasmine-vitest   # schematic experimental din @schematics/angular

Componente: input-uri ca semnale

import { TestBed } from '@angular/core/testing';
import { inputBinding, signal } from '@angular/core';
import { StarRating } from './star-rating';

describe('StarRating', () => {
  it('afișează numărul corect de stele', async () => {
    const value = signal(3);
    const fixture = TestBed.createComponent(StarRating, {
      bindings: [inputBinding('value', value), inputBinding('max', () => 5)], // input-uri ca semnale
    });
    await fixture.whenStable(); // zoneless: așteaptă randarea

    const stars = fixture.nativeElement.querySelectorAll('button');
    expect(stars).toHaveLength(5);
    expect(fixture.nativeElement.querySelectorAll('button.on')).toHaveLength(3);

    value.set(5);                 // schimbi input-ul ca pe orice semnal
    await fixture.whenStable();
    expect(fixture.nativeElement.querySelectorAll('button.on')).toHaveLength(5);
  });

  it('emite rate la click', async () => {
    const onRate = vi.fn();
    const fixture = TestBed.createComponent(StarRating, {
      bindings: [inputBinding('value', () => 1), outputBinding('rate', onRate)],
    });
    await fixture.whenStable();

    fixture.nativeElement.querySelectorAll('button')[3].click();
    expect(onRate).toHaveBeenCalledWith(4);
  });
});

Store-uri

// Store-uri pe semnale = cel mai ușor de testat: fără DOM, fără async
describe('TodoStore', () => {
  let store: TodoStore;

  beforeEach(() => {
    TestBed.configureTestingModule({ providers: [TodoStore] });
    store = TestBed.inject(TodoStore);
  });

  it('calculează câte au rămas', () => {
    store.add('A');
    store.add('B');
    store.toggle(store.todos()[0].id);
    expect(store.remaining()).toBe(1);
  });

  it('filtrează', () => {
    store.add('A');
    store.setFilter('done');
    expect(store.visible()).toEqual([]);
  });
});

// effect-urile rulează asincron — forțează-le cu:
TestBed.tick();
LiveTestele rulează chiar aici: strică codul și vezi care test te prinde
Introdu un bug
cart-store.ts
@Service()
export class CartStore {
private readonly _lines = signal<Line[]>([]);
readonly lines = this._lines.asReadonly();
readonly count = computed(() => this._lines().reduce((n, l) => n + l.qty, 0));
readonly total = computed(() =>
this._lines().reduce((sum, l) => sum + l.price * l.qty, 0),
);
add(p: Product): void {
const exists = this._lines().some((l) => l.id === p.id);
this._lines.update((list) =>
exists
? list.map((l) => (l.id === p.id ? { ...l, qty: l.qty + 1 } : l))
: [...list, { ...p, qty: 1 }],
);
}
remove(id: string): void {
this._lines.update((list) => list.filter((l) => l.id !== id));
}
}
ng test · watch
  1. CartStore › pornește gol
  2. CartStore › count() urmează fiecare add()
  3. CartStore › același produs de două ori e o linie cu qty 2
  4. CartStore › total() e preț × cantitate
  5. CartStore › remove() scoate doar produsul acela

Tests  5 passed (5)

Toate cele 5 teste trec. Acum alege un bug: testele rulează din nou imediat, ca în ng test în watch mode, și vezi care îl prinde.

Store-ul e cod real cu signals Angular; aserțiunile rulează în browserul tău, cu un runner minuscul care are același expect() ca Vitest. Fiecare bug e prins de exact un test.

Aceleași teste, scrise pentru ng test (Vitest + TestBed):

describe('CartStore', () => {
  const TEA = { id: 'tea', price: 3.5 };
  const MUG = { id: 'mug', price: 4 };
  let cart: CartStore;

  // Fiecare test are un TestBed nou, deci un CartStore nou
  beforeEach(() => (cart = TestBed.inject(CartStore)));

  it('pornește gol', () => {
    expect(cart.count()).toBe(0);
    expect(cart.total()).toBe(0);
  });

  it('count() urmează fiecare add()', () => {
    expect(cart.count()).toBe(0); // citit înainte de add(): asta prinde array-ul modificat pe loc
    cart.add(TEA);
    expect(cart.count()).toBe(1);
    cart.add(MUG);
    expect(cart.count()).toBe(2);
  });

  it('același produs de două ori e o linie cu qty 2', () => {
    cart.add(TEA);
    cart.add(TEA);
    expect(cart.lines()).toHaveLength(1);
    expect(cart.lines()[0].qty).toBe(2);
  });

  it('total() e preț × cantitate', () => {
    cart.add(TEA);
    cart.add(TEA);
    cart.add(MUG);
    expect(cart.total()).toBe(11);
  });

  it('remove() scoate doar produsul acela', () => {
    cart.add(TEA);
    cart.add(MUG);
    cart.remove('tea');
    expect(cart.lines().map((l) => l.id)).toEqual(['mug']);
  });
});

HTTP

import { provideHttpClient } from '@angular/common/http';
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';

describe('ProductsApi', () => {
  let api: ProductsApi;
  let http: HttpTestingController;

  beforeEach(() => {
    TestBed.configureTestingModule({ providers: [provideHttpClient(), provideHttpClientTesting()] });
    api = TestBed.inject(ProductsApi);
    http = TestBed.inject(HttpTestingController);
  });

  afterEach(() => http.verify()); // niciun request neașteptat

  it('mapează DTO → Product', async () => {
    const result = firstValueFrom(api.get(1));
    http.expectOne('/api/products/1').flush({ id: 1, name: 'Mouse', price: 99, stock: 0 });
    expect(await result).toEqual({ id: 1, name: 'Mouse', price: 99, inStock: false });
  });
});

Directive și fixture-uri (v22)

// v22.2: TestBed.createDirective — testezi o directivă fără componentă-gazdă scrisă de mână
it('aplică highlight la hover', async () => {
  const fixture = TestBed.createDirective(Highlight, {
    tagName: 'span',
    bindings: [inputBinding('appHighlight', () => 'yellow')],
  });
  fixture.nativeElement.dispatchEvent(new MouseEvent('mouseenter'));
  fixture.detectChanges();
  expect(fixture.nativeElement.style.backgroundColor).toBe('yellow');
});

// v22: TestBed.getLastFixture() — ultimul fixture creat (util în helpers)
const fixture = TestBed.getLastFixture<MyComponent>();

Timere și debounce (zoneless)

// Zoneless: fără fakeAsync/tick din zone.js → timere false din Vitest
beforeEach(() => vi.useFakeTimers());
afterEach(() => vi.useRealTimers());

it('debounce', async () => {
  const fixture = TestBed.createComponent(Search);
  fixture.componentInstance.query.set('ang');
  await vi.advanceTimersByTimeAsync(300);
  await fixture.whenStable();
  // ...
});

Harness-uri

// Component harnesses (CDK): teste care nu depind de structura DOM
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { MatButtonHarness } from '@angular/material/button/testing';

const loader = TestbedHarnessEnvironment.loader(fixture);
const save = await loader.getHarness(MatButtonHarness.with({ text: 'Salvează' }));
await save.click();

// Angular Aria are și ea harness-uri: @angular/aria/tabs/testing etc.

Ce testezi, unde

CeCumCost
Funcții pure, mapperi, validatoriteste unitare simple, fără TestBedminim
Store-uri / servicii cu semnaleTestBed.inject, fără DOMmic
Componente dumbcreateComponent + bindingsmic
Componente smart / paginistore fals în providers + HttpTestingControllermediu
Fluxuri completee2e (Playwright / Cypress)mare

Store-urile ușor de testat sunt descrise la State management cu signals; un test care prinde un memory leak înainte să ajungă în producție, la Memory leaks: cum le găsești și le repari.

Capcane frecvente

  • Testezi implementarea (că s-a apelat o metodă privată) în loc de comportament (ce vede utilizatorul).
  • Uiți await fixture.whenStable() în zoneless → DOM-ul nu e încă actualizat.
  • fakeAsync fără zone.js nu funcționează → timere false din Vitest.
  • Effect-uri netestate: rulează asincron — TestBed.tick() le execută.

Cap. 11 · 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. Performanță — checklist
  2. Testare cu Vitest
  3. DevTools: cum te uiți în aplicație
  4. Angular Aria
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