AngularAtlased. 22.2

Cap. 01Start04 / 75

Întrebări de interviu Angular

60 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.

v21v22 ~0 min de citit

60 de întrebări care chiar apar la interviurile de Angular în 2026, împărțite pe nivel: ce trebuie să știe un junior, ce se așteaptă de la un mid care face singur feature-uri și ce e întrebat un senior despre mecanisme interne, arhitectură și compromisuri. Fiecare răspuns începe cu ce spui întâi, apoi ce adaugi ca să arăți că înțelegi, întrebarea care urmează de obicei și răspunsul care pică interviul.

Cum folosești pagina

  1. Răspunde cu voce tare înainte să deschizi răspunsul. Când citești ai impresia că știi; la interviu va trebui să o spui.
  2. Începe cu răspunsul scurt, apoi adaugă una-două idei din „detalii”. Intervievatorii preferă o poziție clară în locul unei liste lungi.
  3. Marchează ce știi cu „Știu asta”. Se salvează în acest browser, iar simularea îți dă întâi restul.
  4. Fiecare răspuns are link către lecția care explică subiectul pe larg.

Simulare de interviu

Câteva întrebări alese la întâmplare, pe rând, cu răspunsul ascuns până îl ceri. La final primești lista întrebărilor pe care merită să le recitești.

LiveSimulator de interviu

Răspunde cu voce tare, ca și cum te-ar asculta cineva. Apoi deschide răspunsul model și fii sincer cu tine.

Nivel
Întrebări
Întrebările pe care nu le-ai marcat ca știute vin primele.

Junior: fundamentele

La primul job de Angular nu se așteaptă să știi mecanismele interne, dar se așteaptă să știi fără ezitare bazele moderne: componente standalone, noul control flow, semnale, input-uri și output-uri, servicii cu inject(), routing și formulare. Arată că știi de ce, nu doar cum.

0 din 20 marcate ca știute
  1. J-01Ce este o componentă și ce înseamnă „standalone”? Cum pornește o aplicație fără NgModule? Componente

    Spune asta întâi

    O componentă este o clasă TypeScript cu @Component care leagă un template de stare și comportament și se folosește prin selector. Standalone (implicit) înseamnă că își declară singură dependențele în imports, deci nu mai e nevoie de NgModule: aplicația pornește cu bootstrapApplication(App, appConfig).

    Apoi intră în detalii

    • Nu mai scrii standalone: true — e implicit. Fiecare componentă importă exact ce folosește template-ul (alte componente, pipe-uri, RouterLink), deci dependențele sunt explicite și tree-shakable.
    • Configurarea la nivel de aplicație stă în app.config.ts, ca providers: provideRouter(routes), provideHttpClient(...) doar dacă îl configurezi etc.
    • O bibliotecă veche care exportă doar un NgModule poate fi pusă direct în imports la componentă; providerii de modul trec prin importProvidersFrom().
    import { bootstrapApplication } from '@angular/platform-browser';
    import { App } from './app/app';
    import { appConfig } from './app/app.config';
    
    bootstrapApplication(App, appConfig).catch((err) => console.error(err));

    Semnal de alarmă

    Spune că orice componentă trebuie declarată într-un AppModule sau importă CommonModule „ca să fie”.

  2. J-02Ce tipuri de data binding există într-un template? Template-uri

    Spune asta întâi

    Interpolarea {{ value }} și property binding [prop]="expr" merg de la componentă spre DOM; event binding (event)="handler($event)" merge de la DOM spre componentă; two-way binding [(value)]="sig" le combină pe amândouă.

    Apoi intră în detalii

    • [disabled] leagă o proprietate DOM; pentru ce există doar ca atribut (ARIA, colspan) folosești [attr.aria-label].
    • [(x)] („banana in a box”) e doar zahăr sintactic pentru [x]="v()" + (xChange)="v.set($event)". Cu un semnal pasezi semnalul însuși, fără paranteze.
    • Expresiile din template trebuie să fie ieftine și fără efecte secundare; citirea unui semnal ca count() e în regulă, o metodă grea rulează la fiecare verificare.
    <h2>{{ user().name }}</h2>
    <img [src]="user().avatarUrl" [attr.aria-label]="user().name" />
    <button [disabled]="saving()" (click)="save()">Save</button>
    <input (input)="query.set($any($event.target).value)" />
    <app-toggle [(checked)]="notifications" />

    Semnal de alarmă

    Crede că [value]="x" și value="x" sunt același lucru (al doilea e un string static) sau scrie [(checked)]="notifications()".

  3. J-03Cum scrii condiții și bucle în template și de ce are nevoie @for de track? Template-uri

    Spune asta întâi

    Cu control flow-ul integrat: @if / @else, @for cu @empty și @switch / @case. track este obligatoriu: îi spune lui Angular cum identifică fiecare element, ca să refolosească nodurile DOM în loc să recreeze toată lista.

    Apoi intră în detalii

    • Faci track după un id stabil și unic (track user.id); $index doar pentru liste statice; track pe obiectul însuși înseamnă că fiecare refetch distruge și recreează DOM-ul.
    • Face parte din limbaj: fără importuri de NgIf/NgFor. @for expune $index, $first, $last, $even, $odd, $count.
    • @if (user(); as u) dă un alias tipizat, non-null. Din v21.1 mai multe @case pot împărți un bloc, iar @default never; (v21.2) face dintr-un caz uitat al unui union o eroare de compilare.
    @if (tasks().length) {
      <ul>
        @for (task of tasks(); track task.id; let i = $index) {
          <li>{{ i + 1 }}. {{ task.title }}</li>
        }
      </ul>
    } @else {
      <p>You have no tasks.</p>
    }

    Semnal de alarmă

    Folosește în continuare *ngIf/*ngFor în cod nou sau nu poate explica la ce folosește track.

  4. J-04Ce este @let și când l-ai folosi? Template-uri

    Spune asta întâi

    @let declară o variabilă locală, read-only, în template, vizibilă în blocul curent și în copiii lui. Îl folosesc ca să dau nume unei expresii lungi folosite de mai multe ori sau ca să despachetez o valoare o singură dată, de ex. @let user = user$ | async;.

    Apoi intră în detalii

    • Nu poate fi reatribuit din template; se recalculează când se schimbă ce citește.
    • E util în @for pentru o valoare per element (de ex. prețul final după reducere).
    • Logica reală rămâne în clasă, ca computed(); @let e pentru lizibilitate, nu pentru reguli de business.

    Semnal de alarmă

    Folosește @let ca pe o variabilă mutabilă sau mută cu el logică de business complexă în template.

  5. J-05Cum adaugi condiționat o clasă CSS sau un stil? Template-uri

    Spune asta întâi

    Cu binding-uri native: [class.active]="isActive()" pentru o clasă, [class]="{ ... }" pentru mai multe și [style.width.px]="width()" pentru stiluri. Nu au nevoie de import, iar clasele statice se combină cu cele din binding, nu se suprascriu.

    Apoi intră în detalii

    • [class] acceptă string, array sau obiect; [style] acceptă string sau obiect, iar unitățile se pun în binding (.px, .%, .rem).
    • [ngClass]/[ngStyle] sunt varianta veche; v21 are migrările ngclass-to-class și ngstyle-to-style.
    • Pentru elementul gazdă al componentei folosești metadata host ('[class.is-loading]': 'loading()'), nu @HostBinding.

    Semnal de alarmă

    Construiește manual string-uri de clase în componentă sau umblă direct la classList pe element.

  6. J-06Ce este un pipe și ce înseamnă „pure”? Template-uri

    Spune asta întâi

    Un pipe transformă o valoare pentru afișare direct în template, ca {{ price | currency }}. Implicit e pure: Angular îl rulează din nou doar când se schimbă valoarea de intrare sau argumentele (prin referință), deci e memoizat.

    Apoi intră în detalii

    • Pipe-urile sunt standalone și se importă ca orice dependență (imports: [DatePipe]); unul custom e o clasă cu @Pipe și transform().
    • Pentru că verificarea e prin referință, un pipe pure nu rulează din nou dacă modifici un array pe loc.
    • O metodă apelată din template rulează la fiecare verificare; pentru calcule specifice componentei preferă computed(), iar pipe-urile păstrează-le pentru formatare reutilizabilă. Evită pure: false.

    Semnal de alarmă

    Face impur un pipe de filtrare ca „să meargă” pe o listă mare.

  7. J-07Cum trimite un părinte date unei componente copil și cum îi răspunde copilul? Componente

    Spune asta întâi

    Copilul declară input() pentru datele care intră și output() pentru evenimentele care ies. Părintele leagă [value]="..." și (rate)="onRate($event)"; copilul apelează rate.emit(4).

    Apoi intră în detalii

    • Un input() este un semnal read-only: îl citești cu value() și derivi din el cu computed() — fără ngOnChanges.
    • input.required<T>() face dintr-un binding lipsă o eroare de compilare; transform: numberAttribute / booleanAttribute transformă max="10" sau un simplu atribut readonly în tipul corect.
    • Copilul nu poate face set() pe propriul input. Dacă trebuie să schimbe valoarea, emite un eveniment și lasă părintele să decidă sau folosește model().
    export class StarRating {
      readonly value = input.required<number>();
      readonly max = input(5, { transform: numberAttribute });
      readonly rate = output<number>();
    
      protected readonly stars = computed(() =>
        Array.from({ length: this.max() }, (_, i) => i + 1),
      );
    }

    Semnal de alarmă

    Spune că ai nevoie de ngOnChanges ca să reacționezi la un input sau folosește @Input/EventEmitter în cod nou.

  8. J-08Ce este model() și prin ce diferă de input() + output()? Componente

    Spune asta întâi

    model() este un semnal writable care funcționează ca un input plus un output automat xChange, deci părintele poate folosi two-way binding: [(checked)]="notifications". Când copilul apelează set() sau update(), semnalul părintelui se actualizează.

    Apoi intră în detalii

    • Folosește model() când valoarea aparține cu adevărat ambelor părți, ca la un toggle, un stepper sau un input custom.
    • Folosește input() + output() când copilul doar propune, iar părintele decide (validează, confirmă, salvează pe server).
    • În two-way binding pasezi semnalul însuși (notifications), nu valoarea lui (notifications()).

    Semnal de alarmă

    Nu poate explica în ce se desface [( )] sau folosește model() pentru orice.

  9. J-09Ce este content projection? Componente

    Spune asta întâi

    Îi permite părintelui să pună propriul markup între tag-urile unei componente, iar componenta decide unde îl afișează cu <ng-content />. Cu select poți avea mai multe sloturi, de exemplu un titlu și acțiuni într-un card.

    Apoi intră în detalii

    • select primește un selector de tag, atribut sau clasă ([card-title]); <ng-content /> fără select prinde tot restul, iar în interior poți pune conținut fallback.
    • Capcană: conținutul proiectat e creat de părinte chiar dacă copilul îl ascunde cu @if. Pentru conținut cu adevărat lazy, pasezi un <ng-template>.
    • Proiecția e pentru markup flexibil (layout-uri, carduri, dialoguri); input-urile sunt pentru date pe care componenta le formatează singură.

    Semnal de alarmă

    Trimite HTML ca string printr-un input și îl afișează cu innerHTML în loc să-l proiecteze.

  10. J-10Ce sunt signal() și computed()? Signals

    Spune asta întâi

    Un signal() este o valoare reactivă: o citești apelând-o și o schimbi cu set() sau update(). Un computed() este o valoare read-only derivată din alte semnale; e lazy și memoizat, recalculat doar când i se schimbă dependențele.

    Apoi intră în detalii

    • Dependențele sunt urmărite automat: ce semnale citește funcția devin dependențe, fără abonamente de gestionat.
    • Semnalele compară cu ===: items().push(x) nu anunță pe nimeni; folosește update((list) => [...list, x]).
    • Regulă practică: tot ce se poate calcula din altă stare e un computed(), nu un al doilea semnal ținut sincronizat. Expui starea cu asReadonly().
    export class Counter {
      protected readonly count = signal(0);
      protected readonly double = computed(() => this.count() * 2);
    
      protected increment() {
        this.count.update((c) => c + 1);
      }
    }
    // template: {{ count() }} · {{ double() }}

    Semnal de alarmă

    Încearcă set() pe un computed() sau uită parantezele și leagă funcția semnalului în loc de valoarea lui.

  11. J-11Ce este effect() și când nu ar trebui să-l folosești? Signals

    Spune asta întâi

    effect() rulează o funcție o dată, apoi din nou de fiecare dată când se schimbă semnalele pe care le citește. E pentru efecte secundare spre lumea exterioară — localStorage, logging, o bibliotecă terță — nu pentru calcularea stării.

    Apoi intră în detalii

    • Nu folosi un effect ca să copiezi un semnal în altul: asta e un computed() (sau linkedSignal() dacă trebuie să fie și editabil).
    • Nu încărca date dintr-un effect: nu ai anulare, iar răspunsurile vechi pot câștiga cursa. Folosește httpResource() / resource().
    • Trebuie creat într-un context de injecție (constructor sau câmp), se distruge odată cu componenta, iar onCleanup curăță timerele înainte de următoarea rulare.

    Semnal de alarmă

    Folosește effect() ca să țină fullName sincronizat cu firstName și lastName.

  12. J-12Care e diferența dintre constructor și ngOnInit? Cum faci curățenie când o componentă e distrusă? Componente

    Spune asta întâi

    Constructorul rulează la crearea clasei și este contextul de injecție: inject(), semnalele și efectele stau acolo sau în inițializarea câmpurilor. ngOnInit rulează o dată, după ce sunt setate primele input-uri. Pentru curățenie folosesc inject(DestroyRef).onDestroy() sau takeUntilDestroyed().

    Apoi intră în detalii

    • Citirea unui input.required() în constructor aruncă eroare, pentru că părintele nu l-a legat încă. Cu input-uri semnal rar ai nevoie de ngOnInit: un computed() sau httpResource() citește input-ul când e gata.
    • inject() nu merge în ngOnInit sau într-un handler de click — e în afara contextului de injecție (eroarea NG0203).
    • DestroyRef merge și în servicii, directive și funcții reutilizabile, ceea ce ngOnDestroy nu poate. Pentru acces la DOM după randare, folosește afterNextRender().
    export class Clock {
      readonly now = signal(new Date());
    
      constructor() {
        const id = setInterval(() => this.now.set(new Date()), 1000);
        inject(DestroyRef).onDestroy(() => clearInterval(id));
      }
    }

    Semnal de alarmă

    Spune că constructorul e „doar pentru DI” și pune toată logica în ngOnInit sau nu curăță niciodată intervalele și abonamentele.

  13. J-13Ce este un serviciu și cum îl folosești într-o componentă? DI

    Spune asta întâi

    Un serviciu este o clasă care ține logică sau stare partajată între componente — apeluri API, un coș, utilizatorul logat. În v22 îl marchez cu @Service(), care e furnizat în root ca singleton, și îl obțin cu inject(CartService).

    Apoi intră în detalii

    • @Service() este echivalent cu @Injectable({ providedIn: 'root' }): creat la prima injecție și tree-shakable. Cere inject() — DI prin constructor e eroare de compilare.
    • @Injectable rămâne valid (cod existent, biblioteci pentru versiuni mai vechi). Dacă pui serviciul în providers la o componentă, primești câte o instanță per componentă, nu una pe aplicație.
    • inject() merge și în guards, resolvers și interceptors funcționale și evită apelurile lungi super(...) la moștenire.
    @Service()
    export class CartService {
      private readonly _items = signal<CartItem[]>([]);
      readonly items = this._items.asReadonly();
      readonly count = computed(() => this._items().length);
    
      add(item: CartItem) {
        this._items.update((list) => [...list, item]);
      }
    }

    Semnal de alarmă

    Creează serviciul cu new CartService() sau crede că un serviciu pus în providers rămâne același singleton global.

  14. J-14Cum configurezi routing-ul și cum navighezi între pagini? Routing

    Spune asta întâi

    Definești un array Routes, îl înregistrezi cu provideRouter(routes) și pui <router-outlet /> unde se afișează paginile. Navighezi cu routerLink în template și cu router.navigate() în cod; paginile se încarcă de obicei lazy cu loadComponent.

    Apoi intră în detalii

    • loadComponent: () => import('./home-page') pune pagina într-un chunk separat, descărcat doar când e vizitată ruta; loadChildren face la fel pentru un feature întreg.
    • Ordinea contează — câștigă prima potrivire — deci wildcard-ul '**' stă la final. routerLinkActive adaugă o clasă pe link-ul activ.
    • Folosește routerLink, nu href: href reîncarcă toată aplicația.

    Semnal de alarmă

    Navighează cu <a href> sau window.location ori pune ruta wildcard prima.

  15. J-15Cum citește o pagină un parametru de rută ca :id? Routing

    Spune asta întâi

    Cu withComponentInputBinding() în provideRouter, parametrii de cale, query params și data rutei ajung ca input-uri ale componentei: readonly id = input.required({ transform: numberAttribute }). Fiind semnal, tot ce derivă din el se actualizează când se schimbă id-ul.

    Apoi intră în detalii

    • Capcană: navigarea de la /products/1 la /products/2 refolosește aceeași instanță a componentei; codul care citește id-ul o singură dată (de ex. un snapshot în ngOnInit) afișează date vechi.
    • Parametrii sunt string-uri în URL, deci un transform ca numberAttribute îți dă un număr real.
    • Din v22 rutele copil moștenesc implicit params și data de la toți părinții (paramsInheritanceStrategy: 'always').
    @Component({ templateUrl: './product-page.html' })
    export default class ProductPage {
      readonly id = input.required({ transform: numberAttribute }); // :id
      readonly tab = input<'specs' | 'reviews'>('specs');           // ?tab=
    
      readonly product = httpResource<Product>(() => '/api/products/' + this.id());
    }

    Semnal de alarmă

    Știe doar route.snapshot.paramMap.get('id') și nu știe că instanța componentei poate fi refolosită.

  16. J-16Cum apelezi un API REST în Angular? HTTP

    Spune asta întâi

    Cu HttpClient, injectat într-un serviciu: http.get<Product[]>('/api/products') întoarce un Observable. Din v21 e disponibil în root fără setup; provideHttpClient() e doar pentru configurare, de exemplu interceptors.

    Apoi intră în detalii

    • Observable-ul e cold: nicio cerere nu pleacă până nu se abonează cineva (subscribe(), firstValueFrom(), pipe-ul async).
    • Pentru citiri reactive într-o componentă, httpResource() dă value(), isLoading() și error() ca semnale și anulează cererile vechi; pentru scrieri (POST, PUT) apelezi HttpClient.
    • Genericul get<T> e doar o aserțiune de tip, nu validare. În v22 Fetch e backend-ul implicit; preocupările transversale (token, erori) merg în interceptors funcționale.

    Semnal de alarmă

    Apelează http.get() fără să se aboneze și se miră că nu se întâmplă nimic sau apelează fetch direct din componente.

  17. J-17Cum construiești un formular cu validare în Angular 22? Formulare

    Spune asta întâi

    Pentru formulare noi folosesc Signal Forms, stabile în v22: un semnal ține modelul, form(model, schema) construiește un arbore de câmpuri cu reguli ca required(p.email), iar [formField] leagă fiecare input. Reactive Forms rămân alegerea pentru aplicații existente pline de ele sau pentru biblioteci care suportă doar ControlValueAccessor.

    Apoi intră în detalii

    • Semnalul-model este sursa de adevăr — fără copie internă, fără patchValue: model.set(data) umple formularul, iar formularul scrie direct în model.
    • Starea fiecărui câmp e semnale: f.email().invalid(), touched(), errors(); tot formularul e și el un câmp (f().valid(), f().submitting()).
    • Afișează erorile doar după touched() și validează întotdeauna și pe server — validarea pe client e UX, nu securitate.
    export class LoginForm {
      readonly model = signal({ email: '', password: '' });
    
      readonly loginForm = form(this.model, (p) => {
        required(p.email);
        email(p.email);
        minLength(p.password, 8);
      });
    }
    // template: <input [formField]="loginForm.email" />

    Semnal de alarmă

    Validează manual în handler-ul de submit cu if-uri sau spune că Signal Forms sunt încă doar experimentale în v22.

  18. J-18Ce face @defer? Performanță

    Spune asta întâi

    @defer încarcă lazy o parte dintr-un template: componentele din el ajung într-un chunk separat, descărcat doar când se declanșează un trigger — implicit on idle, sau on viewport, on interaction, when cond(). Reduce bundle-ul inițial fără să atingi rutele.

    Apoi intră în detalii

    • @placeholder se afișează înainte de trigger (necesar pentru on viewport / interaction / hover), @loading în timpul descărcării, @error dacă descărcarea eșuează.
    • Capcană: dacă o componentă din defer e referită și altundeva în același fișier (în afara blocului), e încărcată eager și chunk-ul se pierde.
    • prefetch on hover descarcă din timp și randează mai târziu — bun pentru acțiuni „la un click distanță”.
    @defer (on viewport) {
      <app-comments [postId]="post().id" />
    } @placeholder {
      <div class="comments-skeleton"></div>
    } @loading (after 100ms) {
      <app-spinner />
    }

    Semnal de alarmă

    Crede că @defer e doar un @if întârziat și nu pomenește de chunk-ul JavaScript separat.

  19. J-19Cum știe Angular când să actualizeze ecranul? Ce s-a schimbat în v21 și v22? Change detection

    Spune asta întâi

    Change detection-ul re-randează template-urile când ceva îl anunță pe Angular: se schimbă un semnal citit în template, are loc un eveniment din template, pipe-ul async emite sau se apelează markForCheck(). Din v21 aplicațiile noi sunt zoneless (fără zone.js), iar din v22 componentele sunt OnPush implicit.

    Apoi intră în detalii

    • Fără zone.js, un setTimeout sau un subscribe() care schimbă un câmp simplu nu actualizează UI-ul. Soluția e un semnal (sau toSignal() / pipe-ul async).
    • O componentă OnPush e verificată doar când un input primește o referință nouă, are loc un eveniment în ea, se schimbă un semnal pe care îl citește sau e marcată pentru verificare — altfel subarborele ei e sărit.
    • ChangeDetectionStrategy.Eager este vechiul comportament „verifică mereu” (Default e deprecat); ng update îl adaugă unde e nevoie.

    Semnal de alarmă

    Spune că „zone.js detectează automat orice schimbare” într-o aplicație v21+ sau repară actualizările lipsă presărând detectChanges().

  20. J-20Cum testezi o componentă într-un proiect Angular nou? Testare

    Spune asta întâi

    Din v21 ng test rulează Vitest (Karma e deprecat). Creez componenta cu TestBed.createComponent(), setez input-urile, fac await fixture.whenStable() ca să se termine randarea zoneless, apoi verific în fixture.nativeElement.

    Apoi intră în detalii

    • Testează comportamentul pe care îl vede utilizatorul (text, click-uri, output-uri emise), nu detaliile private de implementare.
    • Input-urile se pot da ca semnale cu inputBinding(); output-urile se urmăresc cu outputBinding() și vi.fn().
    • Pentru HTTP folosești provideHttpClientTesting() și HttpTestingController în loc de un server real; serviciile și store-urile cu semnale se testează cu TestBed.inject(), fără DOM.
    it('shows the right number of stars', async () => {
      const fixture = TestBed.createComponent(StarRating, {
        bindings: [inputBinding('value', () => 3)],
      });
      await fixture.whenStable();
    
      const stars = fixture.nativeElement.querySelectorAll('button');
      expect(stars).toHaveLength(5);
    });

    Semnal de alarmă

    Nu a scris niciodată un test sau se bazează pe fakeAsync/tick din zone.js într-un proiect zoneless cu Vitest.

Mid: feature-uri făcute singur

La acest nivel întrebările sunt despre alegeri: computed sau effect, ce operator RxJS, unde stă un provider, cum împarți o componentă, cum o testezi. Un răspuns bun de mid numește varianta, motivul și cazul în care cealaltă variantă câștigă.

0 din 20 marcate ca știute
  1. M-01Când folosești computed() și când effect()? De ce nu e bine să setezi semnale într-un effect? Signals

    Spune asta întâi

    computed() e pentru stare derivată: o valoare pură, lazy și memorată. effect() e pentru efecte secundare care duc valorile semnalelor spre lumea non-reactivă (DOM, localStorage, logging). Semnal → semnal printr-un effect e aproape mereu un computed() sau un linkedSignal() deghizat.

    Apoi intră în detalii

    • Dacă setezi un semnal într-un effect ai o rulare în plus, un moment în care ținta e învechită (un render cu valoare greșită) și un graf de dependențe greu de urmărit.
    • Efectele nu sunt sincrone: setezi un semnal de 3 ori și efectul rulează o dată, cu valoarea finală. Efectele de componentă rulează în change detection; cele din servicii root, ca microtask-uri.
    • Dependențele sunt dinamice: contează doar semnalele citite la ultima rulare. Folosești untracked() pentru citiri care nu trebuie să redeclanșeze efectul.
    • Un effect are nevoie de context de injecție (constructor sau inițializare de câmp) ori de un injector explicit; altfel primești NG0203. Pentru timere și listener-e folosești onCleanup.
    • HTTP dintr-un effect e un semnal de alarmă: nu ai anulare, deci răspunsurile pot veni în altă ordine. Pentru asta există resource / httpResource.
    readonly firstName = signal('Ana');
    readonly lastName = signal('Pop');
    
    // ✗ effect used to synchronise state
    // effect(() => this.fullName.set(this.firstName() + ' ' + this.lastName()));
    
    // ✓ derived state
    readonly fullName = computed(() => this.firstName() + ' ' + this.lastName());
    
    // ✓ effect: reactive world → outside world
    constructor() {
      effect(() => localStorage.setItem('name', this.fullName()));
    }

    Semnal de alarmă

    „Folosesc effect ca să țin două semnale sincronizate” sau „computed și effect sunt cam același lucru, doar că effect rulează mai des”.

  2. M-02Ce problemă rezolvă linkedSignal()? Dă un exemplu real. Signals

    Spune asta întâi

    E un semnal writable care se resetează dintr-o sursă: derivat ca un computed(), dar îl poți și set(). Cazuri tipice: o selecție care pornește de la prima opțiune dintr-o listă, numărul paginii care revine la 1 când se schimbă filtrele, o copie editabilă a unui input.

    Apoi intră în detalii

    • Înlocuiește tiparul fragil semnal + effect: fără rulare în plus, fără render cu valoare veche.
    • Forma lungă (source + computation) îți dă valoarea previous, deci poți păstra alegerea utilizatorului dacă încă există în lista nouă.
    • Un set() manual e valabil doar până la următoarea schimbare a sursei; apoi câștigă computation.
    • Din v22.1, opțiunea set interceptează scrierile, de exemplu ca să limitezi o cantitate la stocul disponibil.

    Semnal de alarmă

    Copierea input-ului într-un semnal în ngOnInit (nu se mai resetează niciodată) sau sincronizarea cu un effect.

  3. M-03Cum funcționează resource / httpResource și de ce sunt mai bune decât un subscribe la HTTP într-un effect? HTTP

    Spune asta întâi

    Un resource transformă încărcarea asincronă în semnale: o funcție reactivă params (sau URL), iar când se schimbă, cererea veche e anulată și pornește una nouă. Primești gratis value(), status(), isLoading(), error() și reload(), fără race conditions.

    Apoi intră în detalii

    • resource primește un abortSignal în loader; httpResource trece prin HttpClient, deci se aplică interceptorii; rxResource primește un stream Observable și se comportă ca switchMap.
    • Dacă params întoarce undefined, statusul e idle: loader-ul nu rulează. În v22 poți și arunca ResourceParamsStatus.IDLE / LOADING și poți înlănțui resurse cu ctx.chain().
    • hasValue() e type guard; defaultValue elimină undefined; parse validează sau mapează răspunsul (de ex. DTO → model, Zod).
    • Resursele sunt pentru citire. Scrierile (POST/PUT/DELETE) merg prin HttpClient, apoi faci update() local pe resursă (optimist) sau reload().
    readonly userId = input.required<number>();
    
    // a new id → the old request is cancelled, a new one starts
    readonly user = httpResource<User>(() => '/api/users/' + this.userId());
    
    // template:
    // @if (user.isLoading()) { <app-spinner /> }
    // @if (user.error()) { <button (click)="user.reload()">Retry</button> }
    // @else if (user.hasValue()) { <h2>{{ user.value().name }}</h2> }

    Semnal de alarmă

    Folosirea httpResource pentru un POST la click pe buton sau să nu știi ce se întâmplă cu o cerere în curs când se schimbă params.

  4. M-04Cum implementezi o căutare pe măsură ce scrii fără să trimiți o cerere la fiecare tastă? Signals

    Spune asta întâi

    În v22 înfășor semnalul de căutare cu debounced(query, 300) și îi dau value()-ul unui httpResource. Debounce-ul elimină zgomotul; resource-ul anulează cererile vechi, deci câștigă mereu ultima căutare.

    Apoi intră în detalii

    • debounced() întoarce un Resource: value() se actualizează după ultima schimbare, iar isLoading() e true cât timp utilizatorul încă scrie.
    • E experimental în v22. Alternativa clasică e toSignal(toObservable(query).pipe(debounceTime(300), distinctUntilChanged())): trei conversii pentru un singur debounce.
    • wait poate fi o funcție, de exemplu fără întârziere când câmpul e golit.
    • În Signal Forms există o regulă dedicată debounce(path.field, 300), iar validateHttp are propria opțiune debounce.
    import { httpResource } from '@angular/common/http';
    import { debounced, signal } from '@angular/core';
    
    export class UserSearch {
      readonly query = signal('');
      readonly search = debounced(this.query, 300);
    
      readonly users = httpResource<UserDto[]>(
        () => '/api/users?q=' + encodeURIComponent(this.search.value()),
        { defaultValue: [] },
      );
    }

    Semnal de alarmă

    Un setTimeout în handler-ul de input cu subscribe() manual și fără anularea cererii anterioare.

  5. M-05Cum combini semnale și RxJS într-o componentă fără să lași subscription-uri agățate? Când păstrezi RxJS? RxJS

    Spune asta întâi

    Semnale pentru starea afișată, RxJS la margini (evenimente, stream-uri, async complex). toSignal() face subscribe și unsubscribe automat; la un subscribe() manual pun takeUntilDestroyed(), care îl leagă de DestroyRef-ul componentei.

    Apoi intră în detalii

    • toSignal() fără initialValue are tipul T | undefined; requireSync: true merge doar dacă sursa emite sincron (de ex. un BehaviorSubject).
    • toObservable() emite asincron (printr-un effect) și doar ultima valoare dacă semnalul e setat de mai multe ori în același tick — nu e un stream de evenimente.
    • takeUntilDestroyed() fără argument merge doar în context de injecție; în rest îi dai explicit inject(DestroyRef). Pentru cleanup fără RxJS folosești DestroyRef.onDestroy().
    • Păstrezi RxJS pentru websocket-uri, combinarea stream-urilor de evenimente, retry/backoff și politici de flattening (switchMap, exhaustMap…). Semnalele nu au noțiunea de timp.

    Semnal de alarmă

    „Semnalele înlocuiesc complet RxJS” — sau invers: subscribe peste tot și copierea valorilor în câmpuri, fără unsubscribe.

  6. M-06Explică switchMap vs mergeMap vs concatMap vs exhaustMap, cu un caz de utilizare pentru fiecare. RxJS

    Spune asta întâi

    Diferă prin ce se întâmplă când vine o valoare nouă cât timp Observable-ul interior anterior încă rulează: switchMap îl anulează (căutare), mergeMap rulează în paralel (upload-uri), concatMap pune la coadă, în ordine (salvări ordonate), exhaustMap o ignoră pe cea nouă (submit, login).

    Apoi intră în detalii

    • Alegerea operatorului e alegerea unei politici de concurență; cea greșită produce bug-uri reale: comenzi duble cu mergeMap pe butonul de plată, salvări pierdute cu switchMap la autosave.
    • mergeMap acceptă o limită de concurență, de ex. mergeMap(upload, 3).
    • Pui catchError în pipe-ul interior: după operatorul de flattening, prima eroare completează stream-ul exterior și căutarea „moare”.
    • Cu semnale, httpResource și rxResource îți dau deja semantica switchMap pentru citiri.
    // the last one wins; errors handled INSIDE, so the stream survives
    query$.pipe(
      debounceTime(300),
      switchMap((q) => this.api.search(q).pipe(catchError(() => of([])))),
    );
    
    files$.pipe(mergeMap((file) => this.api.upload(file), 3)); // parallel, max 3
    patches$.pipe(concatMap((patch) => this.api.save(patch))); // in order
    submit$.pipe(exhaustMap(() => this.api.login(this.credentials()))); // ignore double clicks

    Semnal de alarmă

    „Folosesc mereu switchMap” sau subscribe() imbricate în loc de un operator de flattening.

  7. M-07Într-o aplicație Angular 22 zoneless, ce declanșează change detection și de ce un câmp actualizat în setTimeout s-ar putea să nu apară pe ecran? Change detection

    Spune asta întâi

    Fără zone.js nu mai e nimeni care să intercepteze timerele sau promise-urile. Change detection e programat când se schimbă un semnal citit în template, la un eveniment din template/host, de pipe-ul async, de markForCheck(), setInput() sau de o navigare. Un câmp simplu setat în setTimeout sau subscribe() nu anunță pe nimeni — fă-l semnal.

    Apoi intră în detalii

    • Din v22 componentele fără changeDetection sunt OnPush; comportamentul vechi e ChangeDetectionStrategy.Eager (Default e deprecat). ng update adaugă Eager unde e nevoie.
    • O componentă OnPush e verificată doar la o referință nouă pe input (===), la un eveniment în ea sau în copii, la un semnal citit în template, la pipe-ul async sau la markForCheck(). Altfel tot subarborele e sărit.
    • Mutarea unui array primit ca input (push) nu schimbă referința: folosești update-uri imutabile.
    • markForCheck() e soluția de urgență pentru callback-uri vechi; detectChanges() e sincron și rar necesar.
    // ✗ works with zone.js, NOT zoneless + OnPush
    // now = new Date();
    // setInterval(() => (this.now = new Date()), 1000);
    
    // ✓ a signal schedules change detection by itself
    export class Clock {
      readonly now = signal(new Date());
    
      constructor() {
        const id = setInterval(() => this.now.set(new Date()), 1000);
        inject(DestroyRef).onDestroy(() => clearInterval(id));
      }
    }

    Semnal de alarmă

    Apelarea detectChanges() peste tot sau convingerea că OnPush înseamnă „componenta nu se actualizează decât când se schimbă input-urile”.

  8. M-08Prin ce diferă viewChild() și contentChildren() de vechii decoratori și când le poți citi în siguranță? Componente

    Spune asta întâi

    Query-urile semnal întorc semnale, deci rezultatele sunt reactive: derivi din ele cu computed() în loc de ngAfterViewInit și QueryList.changes. În constructor view-ul nu există încă, deci lucrul cu DOM-ul merge în afterNextRender(); viewChild.required() aruncă eroare dacă e citit prea devreme.

    Apoi intră în detalii

    • viewChild caută în template-ul propriu al componentei; contentChild caută în ce a proiectat părintele (de ex. un container Tabs care își descoperă copiii Tab).
    • contentChildren se uită implicit doar la copiii direcți; pentru potriviri mai adânci folosești descendants: true.
    • read alege ce primești: ElementRef, TemplateRef, ViewContainerRef, o directivă sau (v22.2) Injector-ul copilului.
    • Capcană: un query după tipul unei componente în clasă o face eager, deci nu mai e separată într-un chunk de un bloc @defer.

    Semnal de alarmă

    Citirea unui view query în constructor sau în ngOnInit și surpriza că e undefined.

  9. M-09Content projection cu ng-content vs un ng-template randat cu ngTemplateOutlet: când alegi una și când cealaltă? Template-uri

    Spune asta întâi

    ng-content e pentru sloturi statice: părintele creează conținutul o dată, fie că îl afișează copilul, fie că nu. Un ng-template e o rețetă pe care copilul o instanțiază când și de câte ori vrea, cu un context — necesar pentru conținut lazy și pentru API-uri de tip „cum arată un rând”.

    Apoi intră în detalii

    • Dacă pui ng-content într-un @if, ascunzi DOM-ul, dar componentele proiectate există deja și ciclul lor de viață a rulat deja.
    • Sloturile numite folosesc select (tag, atribut, clasă); ngProjectAs proiectează mai multe elemente într-un slot fără wrapper.
    • Contextul trimite date înapoi: $implicit ajunge în let-item. Fără o directivă cu ngTemplateContextGuard, variabilele let- sunt any.
    • Un template vede injectorul locului unde a fost declarat; din v21.2 [ngTemplateOutletInjector]="'outlet'" îl face să folosească injectorul locului unde e randat.
    @Component({
      selector: 'app-collapsible',
      imports: [NgTemplateOutlet],
      template: `
        <button (click)="open.set(!open())">Toggle</button>
        @if (open()) { <ng-container [ngTemplateOutlet]="content() ?? null" /> }
      `,
    })
    export class Collapsible {
      readonly content = contentChild(TemplateRef); // created only when opened
      readonly open = signal(false);
    }
    // <app-collapsible><ng-template><app-heavy-report /></ng-template></app-collapsible>

    Semnal de alarmă

    Convingerea că un @if în jurul lui ng-content face conținutul proiectat lazy.

  10. M-10Cum ai construi o componentă de tabel reutilizabilă și type-safe fără să devină o „god component”? Componente

    Spune asta întâi

    Faci componenta generică (DataTable<T>): T e dedus din rows, cheile coloanelor sunt limitate la keyof T, output-urile sunt tipizate. Personalizarea se face prin template-uri de celulă, nu prin input-uri boolean, iar wrapper-ele de domeniu stau în feature.

    Apoi intră în detalii

    • Semnul unei god component: date any[], flag-uri isProductTable, 30 de input-uri și cunoștințe de domeniu în shared/.
    • Template-urile de celulă sunt găsite cu contentChildren() pe o directivă; ngTemplateContextGuard tipizează let-row ca T în loc de any.
    • Comportamentul îl dai ca funcții (trackBy, displayWith, compareWith), ca să nu trebuiască să știe componenta forma datelor.
    • O greșeală de tipar în cheia unei coloane devine eroare de compilare, nu o coloană goală în producție.

    Semnal de alarmă

    Adăugarea unui nou input boolean de fiecare dată când o pagină are nevoie de ceva diferit.

  11. M-11Cum randezi o componentă aleasă la runtime, de exemplu widget-urile unui dashboard dintr-o configurație? Componente

    Spune asta întâi

    Urci o scară: un set mic și cunoscut → @switch; componenta e o valoare → NgComponentOutlet cu inputs; control complet (output-uri, directive) → ViewContainerRef.createComponent() cu bindings (inputBinding, outputBinding, twoWayBinding).

    Apoi intră în detalii

    • Un registru de loader-e () => import(…) face din fiecare widget un chunk lazy separat.
    • inputBinding('title', () => …) e reactiv, spre deosebire de vechiul setInput() pe care trebuia să-l apelezi din nou la fiecare schimbare, plus subscription-uri manuale la output-uri.
    • ComponentFactoryResolver a fost eliminat în v22: dai clasa direct lui createComponent.
    • Pentru toast-uri și dialoguri în afara arborelui folosești funcția createComponent() cu un EnvironmentInjector, apoi appRef.attachView() ca să intre în change detection, și o distrugi tu.
    private readonly vcr = inject(ViewContainerRef);
    
    private async render(type: WidgetType) {
      const component = await WIDGET_REGISTRY[type](); // lazy chunk
      this.vcr.clear();
      this.vcr.createComponent(component, {
        bindings: [
          inputBinding('title', () => this.config().title), // stays in sync
          outputBinding('remove', () => this.removed.emit(this.config().id)),
        ],
      });
    }

    Semnal de alarmă

    Folosirea ComponentFactoryResolver sau a innerHTML cu tag-uri de componente.

  12. M-12Cum gestionezi starea unei pagini de feature fără o librărie de state? Unde intră componentele smart și dumb? Arhitectură

    Spune asta întâi

    Un serviciu store bazat pe semnale: semnale writable private, semnale publice read-only (asReadonly()) și selectori computed(), plus metode cu nume de domeniu care fac update-uri imutabile. Pagina rutată (smart) furnizează și injectează store-ul; componentele dumb primesc date prin input-uri și emit intenții prin output-uri.

    Apoi intră în detalii

    • Alegi scope-ul după durata de viață: starea de feature în providers pe pagină sau rută (moare odată cu ea), starea globală (user, coș, temă) într-un @Service() root.
    • Datele de pe server stau într-un httpResource în store; starea care trebuie să supraviețuiască unui refresh sau să poată fi trimisă ca link stă în URL (query params + input()).
    • O componentă dumb poate avea stare UI locală și poate injecta lucruri de UI (ElementRef, DestroyRef), dar niciodată servicii de date sau router-ul — altfel nu mai poate fi reutilizată sau testată ușor, iar o listă de 50 de carduri face 50 de cereri.
    • NgRx SignalStore merită când echipa vrea convenții, plugin-uri și devtools, nu din oficiu.

    Semnal de alarmă

    Toată starea într-un singur serviciu global root sau semnale writable expuse pe care orice componentă le modifică direct.

  13. M-13Explică ierarhia DI: când furnizezi în root, pe o rută sau pe o componentă și când îți trebuie un InjectionToken? DI

    Spune asta întâi

    inject() caută în injectorii de element (elementul curent, apoi părinții din DOM), apoi în injectorul rutei, în root și în platform, și aruncă NG0201 dacă nu găsește nimic. Root (@Service()) înseamnă un singleton pe toată aplicația; providers pe rută, o instanță per feature; providers pe componentă, o instanță per componentă și copiii ei. Token-urile sunt pentru valori care nu sunt clase: config, URL-uri, funcții.

    Apoi intră în detalii

    • @Service() (v22) e echivalent cu providedIn: 'root', e tree-shakable și permite doar inject(). autoProvided: false te obligă să-l pui în providers.
    • inject() e mai bun decât DI prin constructor: fără lanțuri super(…), merge în guards, interceptori și factory-uri și permite funcții reutilizabile injectX(). Are nevoie de context de injecție (altfel NG0203).
    • useClass, useValue, useFactory, useExisting și multi: true pentru array-uri de tip plugin; optional, self, skipSelf, host modifică căutarea.
    • viewProviders nu sunt vizibili pentru conținutul proiectat; EnvironmentProviders (provideX()) pot sta doar la nivel de aplicație sau rută, nu într-o componentă.

    Semnal de alarmă

    Adăugarea unui serviciu root în providers pe o componentă „ca să fie” — asta creează o a doua instanță și bug-uri de stare greu de găsit.

  14. M-14Guards și resolvers funcționale: care e diferența dintre canMatch și canActivate și când ai evita un resolver? Routing

    Spune asta întâi

    Guard-urile sunt funcții simple care folosesc inject(). canMatch decide dacă ruta „există” pentru utilizator — dacă nu, router-ul încearcă ruta următoare și chunk-ul lazy nu se descarcă. canActivate blochează intrarea pe o rută care s-a potrivit. Un resolver întârzie navigarea până vin datele; pentru liste și dashboard-uri prefer httpResource în pagină.

    Apoi intră în detalii

    • Întorci true/false, un UrlTree sau un RedirectCommand (redirect + opțiuni de navigare, ca replaceUrl); din v22.2 poți și arunca un RedirectCommand.
    • Resolver: pagina apare cu datele gata, dar navigarea așteaptă, iar o eroare o anulează. httpResource: navigare instantă, loading-ul și eroarea sunt stare în UI.
    • Cu withComponentInputBinding(), datele rezolvate, params și query params ajung ca input()-uri.
    • Guard-urile sunt UX, nu securitate: și API-ul trebuie să verifice autorizarea. canDeactivate protejează modificările nesalvate.

    Semnal de alarmă

    Guard-uri clasă cu injecție prin constructor ca variantă implicită sau tratarea unui guard ca singurul control de acces.

  15. M-15Scrie un interceptor HTTP funcțional care adaugă token-ul de autentificare. Care sunt capcanele? HTTP

    Spune asta întâi

    Un HttpInterceptorFn e o funcție (req, next) care întoarce next(req) — sau o clonă a cererii, pentru că cererile sunt imutabile. Se înregistrează cu provideHttpClient(withInterceptors([…])) și poate folosi inject().

    Apoi intră în detalii

    • Atașezi token-ul doar pentru API-ul tău (verifici URL-ul), niciodată pentru host-uri terțe.
    • Ordinea contează: cererile trec prin interceptori în ordinea din listă, răspunsurile se întorc în ordine inversă.
    • Apelezi inject() la începutul funcției, nu în callback-uri RxJS precum catchError, care rulează mai târziu, în afara contextului de injecție.
    • Folosești HttpContextToken pentru metadate per cerere (de ex. să sari peste bara de loading) în loc de header-e false. Și httpResource, și validateHttp trec prin interceptori.
    export const authInterceptor: HttpInterceptorFn = (req, next) => {
      const token = inject(AuthStore).token();
      if (!token || !req.url.startsWith('/api')) return next(req);
    
      return next(req.clone({ setHeaders: { Authorization: 'Bearer ' + token } }));
    };
    
    // app.config.ts
    provideHttpClient(withInterceptors([authInterceptor, errorInterceptor]));

    Semnal de alarmă

    Modificarea directă a req.headers sau trimiterea token-ului către orice URL.

  16. M-16Cum structurezi tratarea erorilor într-o aplicație Angular: erori HTTP, erori de randare și restul? Arhitectură

    Spune asta întâi

    Pe straturi: erorile HTTP previzibile (404, 422) sunt stare de UI în store sau componentă (resource.error()); cele transversale (401, 5xx) merg într-un interceptor; o zonă stricată a paginii e izolată cu @boundary; tot ce nu e prins ajunge într-un ErrorHandler global custom, pentru logging și telemetrie.

    Apoi intră în detalii

    • @boundary (v22.2, developer preview) are blocuri @error (let e = $error; let reset = $reset), opțional cu condiții when — câștigă prima potrivire.
    • @boundary prinde doar erori de randare, nu erori din handler-e de evenimente sau din cod async — acelea ajung tot la ErrorHandler.
    • provideBrowserGlobalErrorListeners() trimite window.onerror și promise-urile respinse netratate către ErrorHandler.
    • Un interceptor ar trebui să arunce eroarea mai departe (throwError(() => err)) după reacția transversală, ca apelantul să poată reacționa și el. Erorile de validare nu sunt excepții: stau în errors() din formular.
    @boundary {
      <app-price-widget [price]="price()" />
      <app-recommendations />
    } @error (let e = $error; when e instanceof PermissionError) {
      <p>You don't have access to this widget.</p>
    } @error (let e = $error; let retry = $reset) {
      <app-error-card [message]="e.message" (retry)="retry()" />
    }

    Semnal de alarmă

    Un try/catch cu console.log în fiecare componentă sau înghițirea erorilor în interceptor, ca nimeni altcineva să nu le mai vadă.

  17. M-17Cum funcționează validarea în Signal Forms, inclusiv cea asincronă, și prin ce diferă de Reactive Forms tipizate? Formulare

    Spune asta întâi

    În Signal Forms modelul e un semnal simplu, iar form(model, schema) construiește peste el un arbore de câmpuri, cu reguli declarate într-o funcție schemă (required, email, validate, validateHttp…). Orice stare de câmp e un semnal: valid(), errors(), pending(), touched(). Reactive Forms țin o copie proprie a valorii în FormControl-uri și expun schimbările ca Observables.

    Apoi intră în detalii

    • Fără copie internă: model.set() actualizează input-urile, iar tastarea actualizează modelul — fără patchValue / valueChanges.
    • Regulile între câmpuri folosesc valueOf() în validate; cele condiționale, when, hidden, disabled (câmpurile hidden și disabled nu se validează); regulile reutilizabile, schema() + apply().
    • validateHttp trece prin HttpClient (deci se aplică interceptorii) și are opțiunea debounce în v22; validateAsync merge cu orice resource. pending() e true cât timp rulează.
    • Erorile de la server din submission.action pot fi întoarse cu un fieldTree, ca să ajungă pe câmpul potrivit.
    • Migrarea poate fi treptată cu SignalFormControl și compatForm; Reactive Forms tipizate (nonNullable, getRawValue()) rămân valide pentru codul existent.
    readonly model = signal({ username: '', password: '' });
    
    readonly signup = form(this.model, (p) => {
      required(p.username);
      minLength(p.password, 8, { message: 'At least 8 characters' });
      validateHttp(p.username, {
        request: ({ value }) => '/api/username-available?username=' + value(),
        debounce: 400,
        onSuccess: (res: { available: boolean }) =>
          res.available ? undefined : { kind: 'taken', message: 'Username taken' },
        onError: () => ({ kind: 'network', message: 'Could not verify' }),
      });
    });

    Semnal de alarmă

    Un validator async care trimite o cerere la fiecare tastă, fără debounce, sau să nu știi că între timp câmpul e pending.

  18. M-18Cum construiești un form control custom (de ex. un rating cu stele) care merge cu Signal Forms? Dar cu Reactive Forms? Formulare

    Spune asta întâi

    Pentru Signal Forms implementezi FormValueControl<T>: singurul membru obligatoriu e value = model(); apoi [formField] merge pe el exact ca pe un input. Pentru Reactive Forms, varianta clasică e ControlValueAccessor, înregistrat cu NG_VALUE_ACCESSOR.

    Apoi intră în detalii

    • Input-urile opționale ca disabled, invalid, touched sunt completate automat de formular — le declari doar pe cele pe care le folosești. În v22, un output touch anunță că utilizatorul a atins controlul.
    • Controalele boolean implementează FormCheckboxControl, cu checked în loc de value.
    • Un CVA are nevoie de patru metode (writeValue, registerOnChange, registerOnTouched, setDisabledState) și trebuie să apelezi tu onChange; dacă uiți onTouched, nu mai merge „arată erorile după touch”.
    • Interoperabilitate în v22: Signal Forms poate lega CVA-uri, iar formularele template/reactive suportă FormValueControl, deci un singur control le poate servi pe amândouă în timpul migrării.

    Semnal de alarmă

    Trimiterea unui FormControl ca input componentei copil și modificarea lui acolo.

  19. M-19Cum funcționează @defer, ce trigger-e ai folosi și ce face ca un bloc deferred să nu fie de fapt lazy? Performanță

    Spune asta întâi

    @defer mută componentele din bloc într-un chunk separat, încărcat la un trigger: on idle (implicit), viewport, interaction, hover, timer, immediate sau when o condiție. prefetch descarcă mai devreme decât randează. Rămâne lazy doar dacă dependențele sunt standalone și nu sunt folosite altundeva în fișier.

    Apoi intră în detalii

    • Dacă referi o componentă deferred în clasă (de ex. viewChild(HeavyChart)) sau altundeva în template, devine eager.
    • @placeholder apare înainte de trigger (și e elementul observat pentru viewport / interaction / hover fără țintă); @loading (after 100ms; minimum 500ms) evită pâlpâirea; @error tratează o descărcare de chunk eșuată.
    • when se declanșează o singură dată; mai multe trigger-e se combină cu SAU.
    • Noutăți: on viewport({ rootMargin: '200px' }) (v21), on idle(500) (v22). Cu SSR, trigger-ele hydrate on … dau hidratare incrementală, activă implicit în v22.
    @defer (on viewport; prefetch on idle) {
      <app-comments [postId]="post().id" />
    } @placeholder (minimum 300ms) {
      <div class="comments-skeleton"></div>
    } @loading (after 100ms; minimum 500ms) {
      <app-spinner />
    } @error {
      <p>We couldn't load the comments.</p>
    }
    
    @defer (on interaction; prefetch on hover) {
      <app-share-dialog />
    } @placeholder { <button>Share</button> }

    Semnal de alarmă

    Punerea conținutului vizibil imediat (above the fold) în @defer sau convingerea că @defer amână o cerere HTTP, nu încărcarea de cod.

  20. M-20Cum testezi o componentă cu input-uri semnal și un serviciu care face HTTP, folosind Vitest în Angular 21+? Testare

    Spune asta întâi

    Componente: TestBed.createComponent(Cmp, { bindings: [inputBinding('value', signal)] }), apoi await fixture.whenStable(), pentru că nu mai există zone. HTTP: provideHttpClient() + provideHttpClientTesting(), apoi HttpTestingController.expectOne(url).flush(data) și verify() în afterEach.

    Apoi intră în detalii

    • Vitest e runner-ul implicit din v21 (@angular/build:unit-test); Karma e deprecat. Folosești vi.fn() pentru spioni pe output-uri și vi.useFakeTimers() în loc de fakeAsync.
    • Schimbarea semnalului legat actualizează input-ul ca într-un părinte real — nu mai ai nevoie de setInput().
    • Store-urile cu semnale sunt cel mai ușor de testat: TestBed.inject(), apelezi metode, verifici computed()-urile. Efectele rulează asincron, deci le forțezi cu TestBed.tick().
    • Testezi comportamentul prin DOM sau prin harness-uri, nu câmpuri private. TestBed.createDirective() (v22.2) testează o directivă fără componentă-gazdă scrisă de mână.
    beforeEach(() => {
      TestBed.configureTestingModule({ providers: [provideHttpClient(), provideHttpClientTesting()] });
      api = TestBed.inject(ProductsApi);
      http = TestBed.inject(HttpTestingController);
    });
    
    afterEach(() => http.verify()); // no unexpected requests
    
    it('maps 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 });
    });

    Semnal de alarmă

    Apeluri către backend-ul real în testele unitare sau detectChanges() apelat de mai multe ori și verificări pe câmpuri private.

Senior: mecanisme interne, arhitectură, compromisuri

Interviurile de senior verifică dacă poți decide pentru o echipă: cum merge framework-ul pe dedesubt, cum rămâne o aplicație ușor de întreținut cu 50 de developeri, cât costă SSR, securitatea și performanța. Răspunde mereu cu o poziție, compromisul și cum ai introduce-o într-un proiect real.

0 din 20 marcate ca știute
  1. S-01Cum funcționează semnalele pe dinăuntru? Explică graful producător/consumator, push-pull și de ce sunt glitch-free. Signals

    Spune asta întâi

    Semnalele formează un graf reactiv construit din citiri: signal() e producător, computed() e ambele, iar effect-urile și template-urile sunt consumatori live. O scriere doar crește o versiune și împinge o notificare „poate e murdar”; valorile sunt trase și recalculate leneș când cineva citește, deci un computed vede mereu input-uri consistente și rulează o singură dată pe un set de scrieri.

    Apoi intră în detalii

    • Dependențele sunt dinamice: la fiecare rulare lista de producători se reconstruiește din ce s-a citit efectiv, deci un semnal citit doar într-o ramură if inactivă nu declanșează nimic.
    • Egalitatea oprește propagarea: versiunea crește doar dacă valoarea nouă nu e egală (implicit Object.is). Un computed al cărui rezultat nu se schimbă nu-și trezește consumatorii; { equal } personalizează asta pentru valori recreate de la server.
    • Consecință: mutația (items().push()) nu anunță pe nimeni, pentru că referința și versiunea rămân aceleași. Imutabilitatea nu e stil, e felul în care graful detectează schimbarea.
    • Effect-urile sunt asincrone și programate odată cu change detection (view effects înainte de verificarea componentei lor, root effects înaintea tuturor componentelor). Semnalele nu sunt un mecanism de evenimente și nu ordonează în timp efectele secundare — acolo e teritoriul RxJS.
    const a = signal(1);
    const b = signal(2);
    const sum = computed(() => a() + b());
    const product = computed(() => a() * b());
    const result = computed(() => sum() + product()); // diamond
    
    a.set(5);
    b.set(3);  // push: versions bump, nothing is recalculated yet
    result();  // pull: sum, product, result run once each, with consistent values
    
    // equality cutoff: a new array with the same content does not propagate
    const ids = signal<number[]>([], {
      equal: (x, y) => x.length === y.length && x.every((v, i) => v === y[i]),
    });

    Semnal de alarmă

    „Un semnal e doar un BehaviorSubject cu API mai drăguț” sau „set() re-randează componenta sincron” — arată că nu înțelegi lenea, versiunile și push-pull.

  2. S-02Într-o aplicație zoneless cu componente OnPush, ce programează de fapt change detection și ce sare Angular? Change detection

    Spune asta întâi

    Fără zone.js nimeni nu mai interceptează setTimeout sau promise-urile, deci Angular randează doar când i se spune: se schimbă un semnal citit în template, se declanșează un listener din template/host, pipe-ul async emite, markForCheck(), setInput() sau o navigare. Un subarbore OnPush e sărit dacă nu-l marchează unul dintre acestea, deci munca e proporțională cu ce s-a schimbat, nu cu mărimea arborelui.

    Apoi intră în detalii

    • Template-ul e un consumator reactiv: când se schimbă un semnal pe care l-a citit, Angular marchează acel view pentru refresh (ca markForCheck), iar change detection ajunge doar la componentele afectate.
    • OnPush compară input-urile prin referință (===): dacă muți un obiect primit printr-un input, copilul nu se actualizează — aceeași regulă de imutabilitate ca la semnale.
    • Bug-ul clasic: un câmp simplu setat în setTimeout, subscribe() sau un callback de bibliotecă externă (WebSocket, Maps) — „se actualizează doar după ce dau click undeva”. Soluția e un semnal sau toSignal(); markForCheck() e ieșirea de urgență pentru cod vechi.
    • detectChanges() e sincron și rareori necesar (măsurători, teste); markForCheck() e varianta asincronă recomandată.
    // ✗ worked with zone.js, does nothing zoneless + OnPush
    export class Clock {
      now = new Date();
      constructor() {
        setInterval(() => (this.now = new Date()), 1000);
      }
    }
    
    // ✓ the signal tells Angular what changed
    export class Clock {
      readonly now = signal(new Date());
      constructor() {
        const id = setInterval(() => this.now.set(new Date()), 1000);
        inject(DestroyRef).onDestroy(() => clearInterval(id));
      }
    }

    Semnal de alarmă

    Să presari detectChanges() peste tot până se actualizează ecranul, sau să crezi că zoneless înseamnă „Angular verifică mai rar, deci e mai buggy”.

  3. S-03Cum ai migra o aplicație mare, bazată pe zone.js, la zoneless fără să oprești dezvoltarea de feature-uri? Change detection

    Spune asta întâi

    Incremental: întâi faci componentele corecte sub OnPush, cu starea în semnale, cât timp zone.js e încă plasa de siguranță, apoi scoți zone.js când nimic nu mai depinde de el. Riscul nu e Angular, ci codul și bibliotecile care se bazau tacit pe zone.js ca să declanșeze randări.

    Apoi intră în detalii

    • Întâi inventarul: câmpuri simple setate în callback-uri asincrone, subscribe() în câmpuri, biblioteci care așteaptă NgZone.onStable. Angular 21 aduce un instrument de analiză (onpush_zoneless_migration, prin serverul MCP al CLI-ului, ng mcp).
    • Migrezi întâi componentele frunză, feature cu feature: scoți Eager, muți starea în semnale / toSignal(), review și teste. Fiecare PR e mic și reversibil.
    • Până la final păstrezi provideZoneChangeDetection({ eventCoalescing: true }) și polyfill-ul zone.js; apoi le scoți pe amândouă și urmărești bug-urile de tip „se actualizează doar după un click”.
    • Se schimbă și testele: fakeAsync are nevoie de zone.js, deci treci la await fixture.whenStable() și fake timers din Vitest.
    • În echipă: o regulă de lint/review ca tot codul nou să folosească semnale pentru starea din template și un checklist scurt în template-ul de PR, ca zona migrată să nu regreseze.

    Semnal de alarmă

    „Scoatem zone.js într-un singur PR mare și reparăm ce se strică în QA” — fără inventar, fără plasă de siguranță, fără plan pentru biblioteci și teste.

  4. S-04Cum proiectezi stratul de stare al unei aplicații mari: serviciu cu semnale, NgRx SignalStore sau NgRx Store clasic? Arhitectură

    Spune asta întâi

    Prima întrebare nu e „ce bibliotecă”, ci unde trăiește fiecare bucată de stare și cine o deține. Un serviciu cu semnale private, selectori computed() și metode cu nume clare acoperă majoritatea aplicațiilor; SignalStore când vrei o formă impusă, entități și features reutilizabile între multe echipe; NgRx Store clasic doar când chiar ai nevoie de jurnal de acțiuni, time-travel sau fluxuri bazate pe evenimente.

    Apoi intră în detalii

    • Ții starea la cel mai jos nivel care are nevoie de ea (componentă → feature → global) și o ridici doar când apare un consumator nou. Starea unei pagini e furnizată pe rută sau pe componentă, nu în root, ca să nu „rămână lipită” între vizite.
    • Datele de la server sunt un cache, nu starea ta: le ții în httpResource/resource și reîncarci după scrieri. Și URL-ul e stare (filtre, paginare).
    • Reguli care scalează: expui semnale read-only (asReadonly()), o singură sursă de adevăr cu restul derivat, fără store-uri care se injectează circular — de obicei semn că lipsește un facade sau că un concept e pus greșit.
    • Cu SignalStore, doar metodele apelează patchState, rxMethod se ocupă de fluxurile asincrone (cu catchError în stream-ul interior), iar signalStoreFeature elimină codul de loading/error copiat. Bibliotecile pot coexista cu un NgRx Store existent.

    Semnal de alarmă

    „Mereu NgRx pentru orice lucru serios” sau „un singur store global pentru toată aplicația” — unealta aleasă înainte să înțelegi cine deține starea și cât trăiește.

  5. S-05Cum structurezi un codebase mare în cod de feature și cod shared și cum impui granițele într-un monorepo? Arhitectură

    Spune asta întâi

    Organizezi pe domenii, nu pe tipuri de fișiere: feature-urile depind de core și shared, shared nu depinde de nimic din domeniu, iar feature-urile nu se importă direct între ele. Regulile care trăiesc doar într-un wiki se încalcă în prima săptămână, așa că le pun în lint — no-restricted-imports într-o singură aplicație, sau tag-uri Nx cu @nx/enforce-module-boundaries într-un monorepo.

    Apoi intră în detalii

    • Tag-uri pe două dimensiuni: scope (cine cu cine vorbește: scope:shop, scope:shared) × type (direcția dependențelor: feature → ui / data-access → util). Constrângerile din ambele se aplică cu AND.
    • Un singur API public per feature/bibliotecă (un barrel doar la graniță, niciodată în fiecare folder); importurile adânci și importurile statice ale bibliotecilor lazy sunt încălcări.
    • Granularitatea e un compromis: biblioteci mici pe domeniu și tip dau grafuri nx affected înguste și cache hit-uri; o bibliotecă shared uriașă face ca fiecare PR să afecteze tot repo-ul.
    • Muți în shared doar ce e generic, stabil și deja folosit în mai multe feature-uri („regula lui trei”); componentele de domeniu folosite și în alte părți rămân în feature-ul care le deține și sunt exportate prin API-ul lui public.
    '@nx/enforce-module-boundaries': ['error', {
      depConstraints: [
        // scope: who may talk to whom
        { sourceTag: 'scope:shop', onlyDependOnLibsWithTags: ['scope:shop', 'scope:shared'] },
        { sourceTag: 'scope:shared', onlyDependOnLibsWithTags: ['scope:shared'] },
        // type: which way dependencies flow
        { sourceTag: 'type:feature', onlyDependOnLibsWithTags: ['type:feature', 'type:ui', 'type:data-access', 'type:util'] },
        { sourceTag: 'type:ui', onlyDependOnLibsWithTags: ['type:ui', 'type:util'] },
        { sourceTag: 'type:util', onlyDependOnLibsWithTags: ['type:util'] },
        // ui never reaches data-access, not even transitively
        { sourceTag: 'type:ui', notDependOnLibsWithTags: ['type:data-access'] },
      ],
    }],

    Semnal de alarmă

    Foldere pe tipuri (components/, services/) și un shared pe post de sertar cu de toate, cu granițe „stabilite verbal”.

  6. S-06Cum ai aplica ports & adapters în Angular? Unde stă HTTP-ul și când nu merită? Arhitectură

    Spune asta întâi

    Domeniul e TypeScript simplu, use case-urile depind de porturi scrise ca clase abstracte (care sunt și token-uri DI), iar HTTP-ul stă doar în adaptoare, la margine, care mapează DTO-urile în entități. Singurul loc care leagă un port de un adaptor e composition root-ul — app.config.ts sau providerii unei rute. Pentru ecrane CRUD simple e prea mult.

    Apoi intră în detalii

    • Clasă abstractă în loc de interfață: o interfață dispare la compilare, deci ai avea nevoie de un InjectionToken separat; clasa abstractă e și tipul, și token-ul.
    • Erorile fac parte din contract: adaptorul traduce timeout-urile/503 într-o eroare de domeniu, deci nicio componentă nu vede vreodată HttpErrorResponse.
    • Câștigul se vede la testare: regulile de domeniu ca funcții pure, use case-urile cu un adaptor in-memory și o singură suită de teste de contract rulată pe fiecare adaptor.
    • Varianta pragmatică: un domeniu pur pentru regulile care contează, un port doar unde ai (sau vei avea) mai multe implementări, restul în structura obișnuită data-access/ui — iar straturile le impui prin lint.
    // application/task-repository.ts — the PORT (type + DI token)
    export abstract class TaskRepository {
      abstract list(): Promise<readonly Task[]>;
      abstract save(task: Task): Promise<void>;
    }
    
    // infrastructure/http-task-repository.ts — the ADAPTER (HttpClient + DTO mapping)
    @Injectable()
    export class HttpTaskRepository extends TaskRepository { /* … */ }
    
    // app.config.ts — the COMPOSITION ROOT
    providers: [{ provide: TaskRepository, useClass: HttpTaskRepository }]

    Semnal de alarmă

    Componente care injectează direct HttpClient sau adaptorul concret, DTO-uri care ajung în template-uri, sau patru straturi pentru două formulare „pentru că Clean Architecture”.

  7. S-07Când ai alege micro-frontend-uri cu Native Federation și când te-ai opune? Arhitectură

    Spune asta întâi

    Micro-frontend-urile rezolvă o problemă organizațională — echipe autonome care trebuie să livreze independent și azi se blochează reciproc la release — nu una tehnică. Dacă nu pot numi două echipe care se blochează, recomand o singură aplicație într-un monorepo, cu rute lazy și granițe impuse; MFE înrăutățește performanța și doar mută codul încâlcit între repository-uri.

    Apoi intră în detalii

    • Motive valide: multe echipe cu ritmuri diferite, organizații foarte mari, o migrare strangler fig dintr-o aplicație veche, produse separate sub aceeași experiență.
    • Angular trebuie să fie singleton cu strictVersion, ideal exact aceeași versiune peste tot: două runtime-uri înseamnă componente care nu pot intra în arborele shell-ului și servicii cu două instanțe. Un remote pe alt major se izolează ca Web Component sau iframe.
    • Tratezi exposes-urile fiecărui remote ca API public cu semver; comunici prin URL, apoi evenimente tipizate, apoi câteva servicii singleton partajate — niciodată un store global comun (asta e un monolit distribuit).
    • Planifici costurile din prima zi: timeout și fallback per remote, un manifest per mediu, numele și versiunea remote-ului în fiecare eroare logată, e2e pe combinația din producție.

    Semnal de alarmă

    „Avem nevoie de micro-frontend-uri pentru scalabilitate și performanță” sau setarea strictVersion: false ca să dispară avertismentele de versiune.

  8. S-08Când e Angular Elements unealta potrivită și care sunt capcanele când livrezi o componentă Angular ca element custom? Componente

    Spune asta întâi

    Când o componentă trebuie să trăiască în afara unei aplicații Angular: widget-uri pentru CMS, site-uri de clienți, un design system pentru alte framework-uri sau un remote care nu poate împărți versiunea de Angular. createCustomElement() transformă input-urile în atribute/proprietăți și output-urile în CustomEvent-uri, dar fiecare bundle își aduce propriul runtime (≈ 33 kB transferați) și nu există DI comun — în aceeași aplicație Angular e aproape mereu alegerea greșită.

    Apoi intră în detalii

    • Atributele sunt mereu string-uri: input-urile numerice/booleene au nevoie de numberAttribute / booleanAttribute, iar obiectele/array-urile merg doar prin proprietăți.
    • Evenimentele nu fac bubble și nu sunt composed: asculți direct pe element. Folosești nume specifice pentru output-uri (ratingChange, nu change).
    • Folosești ViewEncapsulation.ShadowDom ca CSS-ul paginii-gazdă să nu intre; tematizezi prin variabile CSS și ::part().
    • Grupezi widget-urile unei echipe într-un singur bundle (un createApplication(), mai multe apeluri define()), folosești un tag diferit de selectorul componentei și nu refolosești un element distrus — creezi altul.

    Semnal de alarmă

    Să folosești Angular Elements ca să refolosești componente între ecranele aceleiași aplicații Angular, sau pentru conținut critic pentru SEO care ar trebui randat pe server.

  9. S-09Cum decizi între CSR, SSR și prerender într-o aplicație Angular și ce costă operarea? SSR

    Spune asta întâi

    Pe rută, nu pe proiect: app.routes.server.ts stabilește RenderMode.Prerender, Server sau Client pentru fiecare cale. Prerender pentru paginile publice stabile, randare pe server pentru paginile publice cu date proaspete, iar zona din spatele login-ului rămâne pe client. SSR îmbunătățește LCP și SEO; nu micșorează JavaScript-ul și nu repară o aplicație lentă după hidratare.

    Apoi intră în detalii

    • Prerender cu parametri cere getPrerenderParams; PrerenderFallback.Server (implicit) îți permite să prerandezi top N produse, iar restul la cerere. Fiecare URL e o randare completă la build.
    • Modul Server înseamnă un proces Node: TTFB e cel puțin cât cel mai lent API apelat la randare, CPU per cerere și stare la nivel de modul partajată între utilizatori.
    • Codurile de status corecte contează pentru SEO: setezi 404 prin RESPONSE_INIT în loc de un soft 404; cache-ul e pe rută — public, s-maxage doar pentru conținut identic, private, no-store pentru HTML personal.
    • În v22 configurezi allowedHosts (sau NG_ALLOWED_HOSTS): un host necunoscut primește 400, ca protecție SSRF. Dacă nimeni nu poate opera un server Node, folosești Prerender cu outputMode: 'static'.

    Semnal de alarmă

    „Activăm SSR peste tot, face aplicația mai rapidă” — ignorând că TTI aproape nu se schimbă, costul serverului și paginile personale cache-uite public pe CDN.

  10. S-10Explică hidratarea incrementală și event replay în v22. Cum le folosești pe o pagină de produs? SSR

    Spune asta întâi

    În v22 un simplu provideClientHydration() activează hidratarea incrementală și, odată cu ea, event replay. Blocurile @defer (hydrate on …) sunt randate pe server, dar rămân dehidratate în browser — JS-ul lor nici nu e descărcat — până se declanșează trigger-ul; click-urile făcute înainte sunt puse în coadă și redate, iar un click într-un bloc dehidratat îl hidratează.

    Apoi intră în detalii

    • Spre deosebire de un @defer obișnuit, conținutul final e deja pe ecran, deci poți amâna și deasupra fold-ului fără layout shift.
    • hydrate never doar pentru conținut cu adevărat static: nu e adnotat, deci nimic nu e redat. Păstrezi @placeholder: trigger-ele hydrate sunt ignorate la navigarea pe client.
    • withIncrementalHydration() e deprecat, iar withEventReplay() e redundant față de implicit; opt-out-ul e withNoIncrementalHydration().
    • Nu împachetezi componente de câțiva KB: chunk-ul și cererea în plus costă mai mult decât economisesc. Folosești stări CSS (:active, :focus-visible) ca utilizatorul să vadă că click-ul s-a înregistrat.
    <app-product-header [product]="product()" />  <!-- hydrated at bootstrap -->
    
    @defer (hydrate on interaction) {
      <app-gallery [images]="product().images" />  <!-- JS only at the first click -->
    } @placeholder {
      <div class="gallery-skeleton"></div>         <!-- shown on client-side navigation -->
    }
    
    @defer (hydrate on viewport) {
      <app-reviews [productId]="id()" />           <!-- below the fold -->
    } @placeholder {
      <div class="reviews-skeleton"></div>
    }
    
    @defer (hydrate never) { <app-footer /> }      <!-- static HTML forever -->

    Semnal de alarmă

    Să pui hydrate never pe ceva cu listeneri sau să scoți @placeholder „pentru că oricum nu se vede”.

  11. S-11Ce cod nu e SSR-safe și cum previi hydration mismatch-urile într-o echipă? SSR

    Spune asta întâi

    Orice atinge window, localStorage, layout-ul sau observerii în constructori, inițializatori de câmpuri, ngOnInit sau effect() — acestea rulează și pe server. Munca strict de browser merge în afterNextRender / afterRenderEffect sau în spatele unui provider de platformă, iar output-ul randat trebuie să fie identic pe ambele platforme.

    Apoi intră în detalii

    • @if (isPlatformBrowser(…)) într-un template nu aruncă eroare, dar Angular aruncă DOM-ul de pe server pentru acel bloc și îl reconstruiește — un layout shift. Preferi @defer sau afterNextRender.
    • Cauze de mismatch: manipularea directă a DOM-ului (innerHTML, appendChild), HTML invalid (<div> în <p>), scripturi externe, un CDN care șterge comentariile (NG0507) și valori ca Date.now() care diferă între platforme.
    • Serverele zoneless nu văd async-ul tău (fetch, SDK-uri, timere): folosești resource() sau PendingTasks, altfel HTML-ul pleacă gol și browserul cere datele din nou.
    • Scurgeri de stare: variabilele la nivel de modul și obiectele din useValue sunt comune tuturor utilizatorilor procesului Node; folosești servicii sau useFactory.
    • În echipă: un build SSR în CI, un checklist de review și ngSkipHydration permis doar ca patch temporar și documentat.

    Semnal de alarmă

    if (typeof window !== 'undefined') presărat prin componente, sau tratarea lui effect() ca un „hook doar de browser”.

  12. S-12Preiei o aplicație Angular lentă. Care e strategia ta de performanță și cum previi regresiile? Performanță

    Spune asta întâi

    Întâi măsor, apoi repar cel mai mare blocaj: Core Web Vitals (LCP, INP, CLS) de la utilizatori reali și Profiler-ul din Angular DevTools îmi spun dacă problema e la încărcare, la randare sau un main thread ocupat. Apoi blochez câștigurile cu bundle budgets în CI.

    Apoi intră în detalii

    • Încărcare: rute lazy, @defer pentru conținutul greu de sub fold (niciodată elementul LCP), injectAsync() pentru servicii grele folosite rar, NgOptimizedImage cu priority pe imaginea LCP, importuri țintite verificate cu source-map-explorer.
    • Randare: OnPush + semnale, track pe un id stabil, derivări în computed() în loc de metode în template; pentru zeci de mii de rânduri, virtual scroll din CDK sau paginare — OnPush reduce cât de des randezi, nu cât de mult.
    • Main thread: calculele continue de peste ~50 ms (parsare, filtrări mari) merg într-un Web Worker; transferi ArrayBuffer-uri în loc să copiezi date uriașe.
    • Controlul regresiilor: budgets în angular.json care pică build-ul, o verificare de performanță pe paginile cheie la fiecare release și un responsabil pentru cifre.

    Semnal de alarmă

    Optimizare fără măsurare, sau „punem SSR și @defer peste tot” — inclusiv pe conținutul principal, ceea ce înrăutățește LCP-ul.

  13. S-13Cum protejează Angular împotriva XSS, unde sunt ușile laterale și care e a doua ta linie de apărare? Securitate

    Spune asta întâi

    Angular tratează orice valoare legată ca nesigură: interpolarea face escape, [innerHTML] e sanitizat, schemele URL periculoase devin unsafe:. XSS-ul în aplicațiile Angular vine aproape mereu din ocolirea acestor protecții — bypassSecurityTrust* pe date nesigure, nativeElement.innerHTML, eval — deci a doua linie e un CSP strict cu nonce, plus Trusted Types.

    Apoi intră în detalii

    • bypassSecurityTrust* e legitim doar pentru conținut pe care îl construiești și îl controlezi (un URL de embed dintr-un id validat). Un pipe generic „safeHtml” e un XSS reutilizabil; cauți aceste apeluri la fiecare review.
    • CSP: Angular are nevoie de un nonce pentru stilurile inline — ngCspNonce pe elementul rădăcină sau token-ul CSP_NONCE; security.autoCsp calculează hash-uri la build, dar acoperă doar scripturile și nu funcționează cu SSR.
    • Introduci CSP-ul întâi cu Content-Security-Policy-Report-Only, altfel chunk-urile lazy sau stilurile inline se strică în producție.
    • CSRF cu cookie de sesiune: HttpClient trimite cookie-ul XSRF-TOKEN ca X-XSRF-TOKEN pe cererile care modifică date, către aceeași origine — dar serverul trebuie să-l seteze și să-l verifice. Orice verificare din frontend se repetă pe server.

    Semnal de alarmă

    „Angular sanitizează tot, deci suntem în siguranță”, în timp ce codul are un pipe cu bypassSecurityTrustHtml aplicat pe conținut din CMS.

  14. S-14Proiectează un interceptor de autentificare cu refresh token. Ce se întâmplă când șase cereri primesc 401 simultan și unde păstrezi token-urile? HTTP

    Spune asta întâi

    Exact un refresh în desfășurare, partajat de toate cererile care așteaptă (un Observable păstrat cu shareReplay(1), curățat în finalize), apoi fiecare cerere originală e reluată cu token-ul nou. Access token-ul în memorie, refresh token-ul într-un cookie HttpOnly, Secure, SameSite — sau un BFF, unde browserul are doar un cookie de sesiune.

    Apoi intră în detalii

    • Varianta naivă „refresh la fiecare 401” face N refresh-uri; cu rotația refresh token-ului se invalidează reciproc, iar unele servere tratează refolosirea ca atac și închid sesiunea.
    • Marchezi login/refresh cu un HttpContextToken (SKIP_AUTH) ca interceptorul să nu se intercepteze singur, atașezi token-ul doar la URL-urile API-ului tău, iar dacă token-ul s-a schimbat deja de când a plecat cererea, doar reîncerci.
    • Doar 401 declanșează refresh — 403 înseamnă „nu ai voie”. Un refresh eșuat înseamnă logout, curățarea store-urilor și cache-urilor și propagarea logout-ului în celelalte tab-uri (BroadcastChannel).
    • La pornire, un provideAppInitializer încearcă un refresh înainte de prima rută. Cu un provider OIDC extern, folosești o bibliotecă certificată în loc să scrii singur PKCE și rotația.
    private refreshInFlight: Observable<string> | null = null;
    
    refresh(): Observable<string> {
      this.refreshInFlight ??= this.http
        .post<{ accessToken: string }>('/auth/refresh', null, {
          withCredentials: true,                            // HttpOnly cookie
          context: new HttpContext().set(SKIP_AUTH, true),  // not intercepted again
        })
        .pipe(
          map((r) => r.accessToken),
          tap((t) => this.token.set(t)),                    // access token: memory only
          finalize(() => (this.refreshInFlight = null)),
          shareReplay(1),                                   // every 401 waits for the same one
        );
      return this.refreshInFlight;
    }

    Semnal de alarmă

    JWT în localStorage fără să te gândești la XSS sau CSP, un refresh separat pentru fiecare 401, sau verificări de permisiuni bazate pe decodarea JWT-ului în frontend.

  15. S-15Care e strategia ta de tratare a erorilor într-o aplicație Angular? Arhitectură

    Spune asta întâi

    Pe straturi: erorile previzibile se tratează local, lângă date (un 404 sau 422 afișat de store/componentă), cele transversale central, într-un interceptor (401, 5xx), erorile de randare ale unei zone cu @boundary (v22.2, developer preview), iar restul ajunge la un ErrorHandler global care loghează în telemetrie.

    Apoi intră în detalii

    • provideBrowserGlobalErrorListeners() trimite window.onerror și promise-urile respinse netratate la ErrorHandler; ții handler-ul simplu și sigur — dacă aruncă, pierzi toate erorile.
    • @boundary prinde doar erorile de randare, nu pe cele din event handlers sau cod asincron, iar o eroare prinsă tot ajunge raportată la ErrorHandler.
    • Interceptorul aruncă din nou eroarea după tratare (throwError(() => err)), ca apelanții să poată reacționa; un catchError(() => of([])) fără logare face bug-urile invizibile.
    • UX și operare: centralizezi și deduplici toast-urile (nu cinci mesaje pentru o problemă), traduci erorile tehnice în erori de domeniu în adaptor și atașezi context (rută, versiune, remote) fiecărei erori logate.

    Semnal de alarmă

    Un try/catch sau catchError în fiecare componentă care înghite erorile și un toast pentru fiecare, fără logare centrală.

  16. S-16Cum alegi între switchMap, mergeMap, concatMap și exhaustMap și care sunt capcanele shareReplay și ale backpressure-ului? RxJS

    Spune asta întâi

    Alegerea e politica de suprapunere când vine o valoare nouă cât timp cererea interioară anterioară încă rulează: switchMap anulează (typeahead), mergeMap rulează în paralel (upload-uri independente), concatMap pune în coadă (autosave, comenzi ordonate), exhaustMap ignoră (submit, login). Alegerile greșite sunt race conditions care apar doar pe o rețea lentă.

    Apoi intră în detalii

    • switchMap pentru scrieri e periculos: un save anulat poate să fi ajuns sau nu pe server. Scrierile folosesc concatMap sau exhaustMap.
    • catchError merge în proiecția interioară; pus după switchMap, prima cerere eșuată închide tot stream-ul, iar căutarea moare în tăcere.
    • Un simplu shareReplay(1) are refCount: false: pe o sursă infinită (websocket, interval) nu se dezabonează niciodată. Folosești { bufferSize: 1, refCount: true } — știind că cache-ul rămâne doar dacă sursa s-a terminat.
    • RxJS e bazat pe push, fără backpressure încorporat, deci alegi o politică: renunți (switchMap, exhaustMap, throttleTime, auditTime, debounceTime), grupezi (bufferTime) sau limitezi concurența (mergeMap(fn, 3)). O coadă concatMap nelimitată pe o sursă rapidă umflă memoria.
    readonly results$ = this.query$.pipe(
      debounceTime(300),
      distinctUntilChanged(),
      switchMap((q) =>
        this.api.search(q).pipe(
          catchError((err) => {
            this.log.error(err);
            return of([]);        // inside: only this request fails, the stream lives on
          }),
        ),
      ),
      shareReplay({ bufferSize: 1, refCount: true }),
    );

    Semnal de alarmă

    „Folosesc mereu mergeMap” sau apeluri subscribe() imbricate — pierzi anularea, ordinea și tratarea erorilor.

  17. S-17Ce strategie de testare ai stabili pentru o echipă: ce intră în teste unit, de componentă și e2e și ce faci mock? Testare

    Spune asta întâi

    Testezi comportamentul, nu implementarea, și împingi fiecare verificare la cel mai ieftin nivel care o poate prinde: funcții pure și store-uri fără DOM, componente dumb prin input-uri și output-uri, pagini smart cu un store fals și HttpTestingController, plus câteva fluxuri e2e (Playwright/Cypress) pentru ce iese la iveală doar la integrare. Faci mock la granițe — HTTP și porturi — nu la propriile componente copil.

    Apoi intră în detalii

    • Din v21 runner-ul implicit e Vitest (@angular/build:unit-test); Karma e deprecat, iar suitele vechi pot fi mutate cu schematic-ul experimental refactor-jasmine-vitest.
    • Teste zoneless: await fixture.whenStable() în loc de fakeAsync, fake timers din Vitest pentru debounce, TestBed.tick() ca să ruleze effect-urile; input-uri ca semnale prin inputBinding.
    • Cu ports & adapters, rulezi o singură suită de teste de contract pe fiecare adaptor; harness-urile din CDK fac testele de componente independente de structura DOM-ului.
    • În CI: nx affected plus cache pentru viteză, fără cache pentru e2e nedeterministe pe un backend real, iar coverage-ul ca indicator, nu ca țintă.

    Semnal de alarmă

    „100% coverage cu totul mock-uit” — teste care verifică apelarea metodelor private, se strică la fiecare refactorizare și ratează bug-urile reale.

  18. S-18Ce strategie de i18n ai alege: i18n-ul la build din Angular sau o bibliotecă de traduceri la runtime? i18n

    Spune asta întâi

    @angular/localize încorporat, implicit: texte marcate cu i18n / $localize, extrase cu ng extract-i18n și un build optimizat pe fiecare limbă, fără căutare de chei la runtime. O bibliotecă la runtime doar când schimbarea limbii fără reload e cerință de produs sau traducerile vin dintr-un CMS fără deploy.

    Apoi intră în detalii

    • Schimbarea limbii cu i18n-ul încorporat înseamnă reload, chiar și cu loadTranslations() la runtime, pentru că textele din template sunt evaluate la crearea componentelor.
    • Pluralele cer ICU, nu n === 1 ? … : …: româna are one, few și other („20 de fișiere”). Nu concatenezi niciodată propoziții — ordinea cuvintelor diferă între limbi.
    • Id-uri custom (@@cart.title) ca traducerile să supraviețuiască modificărilor textului sursă; datele, numerele și moneda prin pipe-uri cu LOCALE_ID și registerLocaleData.
    • Se potrivește cu SSR/prerender (o variantă pe limbă, aleasă după URL), iar cu hidratarea adaugi withI18nSupport(). Începi din prima zi: i18n adăugat mai târziu înseamnă să vânezi fiecare text scris direct în cod.

    Semnal de alarmă

    Formate scrise direct, ca 'dd/MM/yyyy' sau toFixed(2) + ' lei', concatenare de string-uri pentru propoziții și fără id-uri custom pentru mesaje.

  19. S-19Compania voastră își construiește propriul design system în Angular. Cum faci accesibilitatea o proprietate a sistemului, nu a fiecărui ecran? Accesibilitate

    Spune asta întâi

    Construiești pattern-urile interactive pe Angular Aria (@angular/aria, din v21): directive headless precum Tabs, Menu, Combobox, Listbox, Tree sau Grid, care dau roluri ARIA corecte, navigare din tastatură și management al focusului, cu zero stiluri, iar echipa deține CSS-ul. Astfel fiecare echipă de produs primește accesibilitatea implicit, în loc să rescrie de mână tab-uri și dropdown-uri.

    Apoi intră în detalii

    • Alegerea stratului: Aria pentru un design system propriu, CDK pentru primitive low-level (overlay, drag & drop, utilitare a11y), Material când vrei rapid un UI gata făcut.
    • Stilizezi după starea ARIA pe care o setează directivele ([aria-selected='true']), ca starea vizuală și cea accesibilă să nu se poată desincroniza.
    • Compromisuri în alte locuri: virtual scroll scoate din DOM rândurile din afara ecranului (Ctrl+F și cititoarele de ecran nu le văd); preferi paginarea unde contează accesibilitatea completă.
    • Proces: verificări din tastatură și cu cititor de ecran în definition of done, verificări automate în CI și review de a11y ca parte din contractul design system-ului.

    Semnal de alarmă

    „Adăugăm atribute aria-* la final, înainte de release” — accesibilitatea tratată ca un checklist de final în loc de proprietate a componentelor.

  20. S-20Conduci upgrade-ul unei aplicații mari de la Angular 21 la 22. Ce breaking changes te îngrijorează și cum îl implementezi? Arhitectură

    Spune asta întâi

    Câte un major pe rând, cu ng update @angular/core@22 @angular/cli@22, lăsând migrările să ruleze — de exemplu adaugă ChangeDetectionStrategy.Eager unde componentele nu aveau strategie, deci comportamentul rămâne identic deși OnPush devine implicit. Riscul real e în schimbările de runtime pe care migrările nu le văd complet: backend-ul fetch, moștenirea parametrilor în router și semantica din template.

    Apoi intră în detalii

    • HTTP: fetch e backend-ul implicit, iar withFetch() e deprecat; progresul la upload cere withXhr(), iar reportProgress devine reportUploadProgress / reportDownloadProgress.
    • Router și template-uri: paramsInheritanceStrategy: 'always' (rutele copil moștenesc params și data de la toți părinții), ?. întoarce undefined ca în TypeScript, un compilator mai strict (input-uri legate de două ori, data-* nu mai leagă input-uri), CanMatchFn cere currentSnapshot.
    • Platformă și eliminări: TypeScript 6 și fără Node 20; ComponentFactoryResolver, ComponentFactory și provideRoutes au dispărut; pentru SSR configurezi allowedHosts și scoți withIncrementalHydration().
    • Implementare: un branch de upgrade ținut verde în CI, bibliotecile și Node/TS actualizate întâi, e2e pe fluxurile critice, apoi un plan ulterior de a scoate Eager feature cu feature, în loc să-l tratezi ca permanent.

    Semnal de alarmă

    Să modifici de mână versiunile din package.json și să sari peste ng update, sau să sari mai multe majore într-un singur pas fără să citești changelog-ul.

Cum răspunzi bine

Ce convinge
  • O propoziție clară întâi, detaliile doar dacă ți se cer.
  • Un exemplu concret dintr-un proiect: „aveam X, am ales Y pentru că Z”.
  • Numești compromisul și cazul în care ai alege altfel.
  • „Nu știu, dar așa aș afla” când chiar nu știi.
Ce te costă puncte
  • Definiții recitate fără să spui când le-ai folosi.
  • Răspunsuri vechi prezentate ca actuale (NgModule peste tot, Zone.js de la sine înțeles).
  • „Depinde”, fără să spui de ce depinde.
  • Un API inventat ca să nu rămâi tăcut.

Cap. 01 · 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. Ce e nou în Angular 21 & 22
  2. Prima ta aplicație, pas cu pas
  3. Decizii rapide
  4. Întrebări de interviu Angular
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