AngularAtlased. 22.2

Cap. 13Arhitectură la scară59 / 75

Monorepo, librării & granițe

Un workspace cu mai multe aplicații și librării: ng generate library, Nx, entry points secundare, publicare și reguli de dependență.

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

Un monorepo e un singur repository git care conține mai multe proiecte: de exemplu magazinul, panoul de administrare și librăriile pe care le împart. Nu e un monolit — fiecare aplicație se construiește și se deployează separat. Doar codul stă la un loc, cu o singură versiune de Angular și o singură istorie de commit-uri.

Câștigul: o schimbare care atinge o librărie și cele două aplicații care o folosesc intră într-un singur PR, testat împreună. Costul: fără unelte, fiecare PR reconstruiește și retestează tot; fără reguli, oricine importă orice. Pagina acoperă ambele jumătăți: workspace-ul cu Angular CLI și ng-packagr, apoi ce adaugă Nx peste el — graful de proiecte, affected, cache-ul și granițele verificate la lint.

Pe scurt
  • Un monorepo ține mai multe aplicații și librării în același repository, cu un singur set de versiuni pentru dependențe.
  • Angular CLI suportă nativ mai multe proiecte într-un workspace (ng generate library). Nx adaugă graful dependențelor, granițe și cache.
  • O librărie are un API public: ce nu e exportat din fișierul ei de intrare rămâne intern.
  • „Affected” rulează build-ul și testele doar pentru ce a fost atins de o schimbare.
  • De cele mai multe ori un folder ajunge. O librărie se justifică atunci când e publicată sau folosită de mai multe aplicații.
De ce contează

„Folder, librărie sau pachet npm” e o decizie de arhitectură, nu de stil. Ea stabilește cât de repede livrezi o schimbare transversală și cât de ușor strici ceva fără să observi. Documentația Angular o spune direct: o librărie te obligă să decuplezi codul de logica aplicației, dar cere timp de întreținere — se amortizează când o folosesc mai multe aplicații.

Monorepo, polyrepo, monolit

Cele trei modele diferă mai ales prin costul unei schimbări transversale. Să zicem că modelul Product primește un câmp nou, folosit de librăria de UI și de ambele aplicații:

ModelCum aratăSchimbarea „Product”Prețul
MonolitO aplicație, un deploy, foldere pe domenii1 PR, trivialNu servește două aplicații cu ritmuri diferite
Polyrepo + npmFiecare librărie în repo-ul ei, publicată cu versiune3+ PR-uri: lib → publish → bump în fiecare aplicațieVersiuni care diverg; un fix ajunge în producție când „își aduce aminte” fiecare echipă
MonorepoAplicații + librării în același repo, consumate din workspace1 PR atomic, testat cu toți consumatoriiCI-ul trebuie să fie inteligent (affected, cache) și granițele trebuie impuse

Un workspace Angular CLI are un singur package.json la rădăcină, deci toate proiectele folosesc aceeași versiune de Angular, RxJS sau CDK — ceea ce Nx numește single version policy. Un ng update actualizează tot repo-ul o dată. Prețul e coordonarea: nicio echipă nu rămâne în urmă, dar nici nu poate fugi înainte. Dacă echipele chiar trebuie să livreze independent, cu ritmuri și versiuni diferite, pasul următor sunt micro frontend-urile (care, ironic, trăiesc des tot într-un monorepo).

Workspace multi-proiect cu Angular CLI

ng new creează implicit o aplicație chiar la rădăcină. Pentru un monorepo pornești de la un workspace gol (--no-create-application), iar fiecare ng generate application sau ng generate library adaugă un proiect în projects/ și o intrare în angular.json. O librărie cu scope (@acme/ui) ajunge în projects/acme/ui, iar numele proiectului e chiar numele pachetului.

# 1. Workspace gol: fără aplicație la rădăcină (createApplication: false)
ng new acme --no-create-application
cd acme

# 2. Proiectele ajung în projects/ (newProjectRoot)
ng generate application shop
ng generate application admin
ng generate library @acme/ui          # → projects/acme/ui, prefix implicit „lib”

# 3. Fiecare comandă primește numele proiectului
ng serve shop
ng build admin
ng test @acme/ui
ng build @acme/ui --watch             # rebuild incremental cât lucrezi la librărie

Diferența importantă e în angular.json: aplicațiile folosesc builder-ul @angular/build:application (esbuild), librăriile @angular/build:ng-packagr, care produce un pachet în Angular Package Format și compilează mereu AOT. Pentru librării, configurația implicită e production.

{
  "newProjectRoot": "projects",
  "projects": {
    "shop": {
      "projectType": "application",
      "root": "projects/shop",
      "sourceRoot": "projects/shop/src",
      "architect": {
        "build": { "builder": "@angular/build:application" },
        "serve": { "builder": "@angular/build:dev-server" }
      }
    },
    "@acme/ui": {
      "projectType": "library",
      "root": "projects/acme/ui",
      "sourceRoot": "projects/acme/ui/src",
      "prefix": "lib",
      "architect": {
        "build": {
          "builder": "@angular/build:ng-packagr",
          "defaultConfiguration": "production",
          "configurations": {
            "production": { "tsConfig": "projects/acme/ui/tsconfig.lib.prod.json" },
            "development": { "tsConfig": "projects/acme/ui/tsconfig.lib.json" }
          }
        }
      }
    }
  }
}
Numele contează dacă publici

Evită prefixul ng- (e rezervat pentru framework). Pentru pachete publice convenția e ngx-; pentru cele interne, un scope de firmă (@acme/…) e cel mai clar și se potrivește cu registry-ul privat.

Anatomia unei librării

Patru fișiere definesc o librărie. Fiecare răspunde la o întrebare precisă:

  • ng-package.json — unde ajunge build-ul (dest) și care e fișierul cu API-ul public (lib.entryFile).
  • package.json — numele, versiunea și dependențele. @angular/* stau în peerDependencies: dacă ar fi în dependencies, aplicația ar putea primi o a doua copie de Angular și s-ar strica la runtime. "sideEffects": false îi spune bundler-ului că poate elimina tot ce nu importi.
  • public-api.ts — contractul. Ce nu e exportat aici e privat, deci îl poți refactoriza fără să anunți pe nimeni.
  • tsconfig.lib.prod.json — compilationMode: "partial": formatul partial-Ivy e stabil între versiuni de Angular, iar compilarea se termină în build-ul aplicației, cu versiunea ei de compilator. De aici regula: aplicația trebuie să aibă aceeași versiune de Angular sau una mai nouă decât cea cu care a fost construită librăria.
{
  "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
  "dest": "../../../dist/acme/ui",
  "lib": {
    "entryFile": "src/public-api.ts"
  }
}

Entry points secundare

Angular Package Format pune, în principal, un singur modul ES „plat” per entry point. Un bundler nu poate sparge un modul în mai multe chunk-uri, așa că entry point-ul e granularitatea minimă de lazy loading. De aceea Angular Material are câte un entry point per componentă (@angular/material/button, …/tabs). Al doilea motiv: izolezi API-uri speciale, cum e @acme/ui/testing, ca să nu ajungă din greșeală în producție.

projects/acme/ui/
├── ng-package.json          → @acme/ui             (entry point primar)
├── src/public-api.ts
├── button/
│   ├── ng-package.json      → @acme/ui/button      (entry point secundar)
│   └── src/public-api.ts
└── testing/
    ├── ng-package.json      → @acme/ui/testing     (harness-uri, mock-uri pentru teste)
    └── src/public-api.ts

ng-packagr descoperă singur fiecare ng-package.json; nu înregistrezi nimic altundeva.

O librărie cu un singur scop (un client HTTP, un set de utilitare) stă bine cu un singur entry point — și @angular/core are unul singur pentru runtime.

Consum, build și publicare

În același workspace nu trebuie să publici nimic, dar trebuie să construiești librăria înainte. ng generate library adaugă în tsconfig.json un alias care indică spre dist/, nu spre sursă. Motivul: aplicațiile și librăriile au build-uri diferite (esbuild vs ng-packagr), iar același TypeScript poate ieși ca JavaScript diferit. Documentația cere explicit ca alias-urile să indice spre librăria construită. În timp ce lucrezi, ng build @acme/ui --watch face rebuild incremental.

{
  "compilerOptions": {
    "paths": {
      // adăugat automat de ng generate library — indică spre BUILD, nu spre sursă
      "@acme/ui": ["./dist/acme/ui"],
      // adăugat de tine, pentru entry points secundare
      "@acme/ui/*": ["./dist/acme/ui/*"]
    }
  }
}

Publicarea înseamnă build în configurația production (implicită) și npm publish din dist — publici pachetul generat, nu folderul sursă.

# Build de distribuție: configurația production (implicită pentru librării) → partial-Ivy
ng build @acme/ui

# Publici CONȚINUTUL din dist, nu folderul sursă
cd dist/acme/ui
npm publish            # sau: npm publish --registry https://npm.firma.ro

# Consumatorul (alt repo) îl instalează ca pe orice pachet
npm install @acme/ui@^1.4.0
ConsumatorulCum primește codulVersiuni
Aplicație din același repoAlias în tsconfig, mereu ultima versiune din branchNu ai nevoie: toți sunt pe HEAD
Alt repo / alt clientnpm install @acme/ui din registrySemver strict, changelog, peerDependencies corecte
Nx face altfel, intenționat

Generatorul @nx/angular:lib pune alias-ul în tsconfig.base.json spre sursă (libs/…/src/index.ts): librăria se compilează împreună cu aplicația, fără pasul de build separat. Build-ul propriu (--buildable / --publishable) îl adaugi doar pentru librăriile pe care le publici sau pentru build-uri incrementale. Nu amesteca cele două stiluri în același workspace fără să știi de ce.

Nx: graful și granițele

Nx folosește aceleași builder-e Angular, dar ține configurația fiecărui proiect în propriul project.json și construiește un graf de proiecte din analiza importurilor. Pe graful ăsta se sprijină tot restul: nx graph ți-l arată, affected îl parcurge invers, iar regula de lint îl verifică la fiecare import. Într-un workspace Angular CLI existent pornești cu npx nx@latest init.

# Workspace Angular CLI existent → Nx (păstrează structura)
npx nx@latest init
# …sau mută proiectele în apps/ și libs/
npx nx@latest init --integrated

# Librării cu tag-uri din prima zi
nx g @nx/angular:lib libs/shop/feature-cart --tags=scope:shop,type:feature
nx g @nx/angular:lib libs/shared/ui --tags=scope:shared,type:ui
nx g @nx/angular:lib libs/shared/ui --publishable --importPath=@acme/ui   # doar dacă o publici

# Graful de proiecte, interactiv în browser
nx graph
nx graph --focus shop-feature-cart

# Rulează doar ce e afectat de schimbările față de main
nx affected -t lint test build
nx affected -t build --graph        # vezi graful de task-uri înainte să rulezi
nx run-many -t test                 # tot, fără filtru

Tipuri de librării

Ghidul Nx propune patru tipuri — aceleași roluri din smart vs dumb și din structura de foldere, doar că acum granița e o librărie cu API public:

TipCe conținePoate depinde de
featurePagini și componente container pentru un caz de utilizare; de obicei lazyorice tip
uiComponente prezentaționale; datele vin prin input-uri, fără servicii injectateui, util
data-accessComunicarea cu backend-ul și starea (store-uri)data-access, util
utilFuncții pure, adesea fără cod de frameworkutil
acme/
├── nx.json                     ← cache, targetDefaults, defaultBase
├── tsconfig.base.json          ← "@acme/shop/feature-cart": ["libs/shop/feature-cart/src/index.ts"]
├── apps/
│   ├── shop/project.json       tags: scope:shop,   type:app
│   └── admin/project.json      tags: scope:admin,  type:app
└── libs/
    ├── shop/
    │   ├── feature-catalog/    tags: scope:shop,   type:feature      (pagini smart, lazy)
    │   ├── feature-cart/       tags: scope:shop,   type:feature
    │   └── data-access/        tags: scope:shop,   type:data-access  (API + store)
    ├── admin/
    │   ├── feature-users/      tags: scope:admin,  type:feature
    │   └── data-access/        tags: scope:admin,  type:data-access
    └── shared/
        ├── ui/                 tags: scope:shared, type:ui           (componente dumb)
        ├── data-access/        tags: scope:shared, type:data-access  (auth, config HTTP)
        └── util/               tags: scope:shared, type:util         (funcții pure)

Tag-uri pe două dimensiuni

Fiecare proiect primește tag-uri în project.json. Combinația care funcționează în practică e scope (domeniul: scope:shop, scope:admin, scope:shared) × type (stratul: type:feature, type:ui…). Scope-ul răspunde la „cine vorbește cu cine”, tipul la „în ce direcție curg dependențele”.

{
  "name": "shop-feature-cart",
  "sourceRoot": "libs/shop/feature-cart/src",
  "projectType": "library",
  "tags": ["scope:shop", "type:feature"]
}

Regula @nx/enforce-module-boundaries

Regula de lint primește o listă depConstraints. Pentru un import, Nx ia toate constrângerile al căror sourceTag îl are proiectul sursă și le aplică cu ȘI: shop/feature-cart trebuie să satisfacă și regula de scope, și pe cea de tip. onlyDependOnLibsWithTags cere ca ținta să aibă cel puțin unul dintre tag-uri. Un proiect fără niciun tag potrivit nu are voie să depindă de nimic.

import nx from '@nx/eslint-plugin';

export default [
  ...nx.configs['flat/base'],
  ...nx.configs['flat/typescript'],
  ...nx.configs['flat/javascript'],
  {
    files: ['**/*.ts'],
    rules: {
      '@nx/enforce-module-boundaries': [
        'error',
        {
          allow: [],
          depConstraints: [
            // Dimensiunea 1: domeniul (cine cu cine are voie să vorbească)
            { sourceTag: 'scope:shop', onlyDependOnLibsWithTags: ['scope:shop', 'scope:shared'] },
            { sourceTag: 'scope:admin', onlyDependOnLibsWithTags: ['scope:admin', 'scope:shared'] },
            { sourceTag: 'scope:shared', onlyDependOnLibsWithTags: ['scope:shared'] },
            // Dimensiunea 2: tipul (în ce direcție curg dependențele)
            { sourceTag: 'type:app', onlyDependOnLibsWithTags: ['type:feature', 'type:ui', 'type:data-access', 'type:util'] },
            { sourceTag: 'type:feature', onlyDependOnLibsWithTags: ['type:feature', 'type:ui', 'type:data-access', 'type:util'] },
            { sourceTag: 'type:ui', onlyDependOnLibsWithTags: ['type:ui', 'type:util'] },
            { sourceTag: 'type:data-access', onlyDependOnLibsWithTags: ['type:data-access', 'type:util'] },
            { sourceTag: 'type:util', onlyDependOnLibsWithTags: ['type:util'] },
          ],
        },
      ],
    },
  },
];

Pe lângă tag-uri, regula are și verificări fixe, care rulează înaintea lor. Trei contează zilnic, în ordinea asta: dependențe circulare, importul unei aplicații (interzis) și importul static al unei librării încărcate lazy (ar trage chunk-ul în bundle-ul inițial). Le vezi pe toate în demo:

LiveGraful workspace-ului și regulile de graniță (simulare)
appsshopappsadminshopfeature-catalogshopfeature-cartadminfeature-usersshopdata-accessshareduiadmindata-accessshareddata-accesssharedutil

scope:shopscope:sharedscope:adminlazy (loadChildren)

scope:shop, type:feature → scope:admin, type:data-access
Încearcă:
Alege sursa și ținta, apoi „Rulează lint pe import”. Muchia apare verde sau roșie, iar dedesubt vezi mesajul regulii și fiecare constrângere aplicată. Motorul reproduce verificările și mesajele @nx/enforce-module-boundaries, în aceeași ordine; nu rulează ESLint real.

Aceeași regulă acceptă și forme mai fine:

// Alte forme utile de constrângeri (aceeași regulă, aceleași depConstraints):
[
  // „ui” nu are voie să ajungă la data-access nici măcar TRANZITIV (verifică tot lanțul)
  { sourceTag: 'type:ui', notDependOnLibsWithTags: ['type:data-access'] },

  // regula se aplică doar proiectelor care au AMBELE tag-uri
  { allSourceTags: ['scope:shop', 'type:ui'], onlyDependOnLibsWithTags: ['scope:shared'] },

  // pachete npm interzise într-un tip de proiect
  { sourceTag: 'type:util', bannedExternalImports: ['@angular/*'] },

  // glob și regex în tag-uri
  { sourceTag: 'scope:*', onlyDependOnLibsWithTags: ['/^scope:(shared|core)$/'] },
]

Atenție la diferență: notDependOnLibsWithTags verifică tot lanțul de dependențe al țintei, nu doar ținta directă. E unealta potrivită pentru „UI-ul nu ajunge niciodată la data-access, nici prin ocolișuri”. Pentru granițele dintre foldere într-o singură aplicație, fără Nx, vezi Granițe.

Affected și cache

nx affected -t test pornește de la fișierele schimbate între --base (implicit defaultBase, adică main) și --head, le mapează pe proiecte, apoi urcă în graf pe muchiile inverse: tot ce depinde, direct sau tranzitiv, de un proiect schimbat e afectat. În workspace-ul din demo, o schimbare în shop/data-access afectează 4 proiecte (librăria, cele două feature-uri shop și aplicația shop); una în shared/util le afectează pe toate 10.

Peste asta vine cache-ul: înainte să ruleze un task, Nx calculează un hash din inputs — sursele proiectului, ale dependențelor (după inputs), configurația, comanda. Dacă hash-ul a mai fost văzut, Nx reface rezultatul (output-ul din terminal și fișierele) în loc să ruleze. Cu un remote cache, un task rulat o dată în CI nu mai rulează pe laptopul nimănui.

Livenx affected -t test, cu cache (simulare)
appsshopappsadminshopfeature-catalogshopfeature-cartadminfeature-usersshopdata-accessshareduiadmindata-accessshareddata-accesssharedutil

modificat ✎afectatrulat ✓din cache

0/10
proiecte afectate
0s
test pe affected (vs 3m 51s tot)

Apasă pe un proiect din graf: îi modifici un fișier.

  • Terminalul CI apare aici.
Apasă pe shared/util: aproape tot repo-ul devine afectat. Rulează, apoi rulează din nou — a doua oară totul vine din cache. Modifică apoi shop/feature-cart și rulează: doar feature-cart și shop se execută, restul e cache hit. „merge în main” mută base-ul; „nx reset” golește cache-ul.
{
  "defaultBase": "main",
  "namedInputs": {
    "default": ["{projectRoot}/**/*"],
    "production": ["default", "!{projectRoot}/**/*.spec.ts"]
  },
  "targetDefaults": {
    "build": {
      "dependsOn": ["^build"],
      "inputs": ["production", "^production"],
      "cache": true
    },
    "test": {
      "inputs": ["default", "^production"],
      "cache": true
    },
    "lint": { "cache": true }
  }
}
Granularitatea face affected precis

Dacă toate aplicațiile depind de o librărie shared uriașă, orice schimbare în ea afectează tot repo-ul și affected nu mai economisește nimic. Librării mai mici, pe domenii și tipuri, înseamnă grafuri mai înguste și mai multe cache hit-uri.

Cache-ul presupune task-uri fără efecte secundare: aceleași inputs dau același rezultat. Un e2e care lovește un backend real nu e cacheabil — rezultatul depinde de ceva ce Nx nu vede.

Folder sau librărie?

Ghidul Nx despre dimensiunea proiectelor rezumă bine compromisul. Spargi în librării când testele te încetinesc (affected și cache încep să sară peste bucăți), când codul are alt proprietar (o echipă, un CODEOWNERS) sau când vrei granițe verificate. Nu spargi ce se schimbă mereu împreună — granița costă navigare și configurare fără să cumpere nimic. Iar cât timp designul e în explorare, un singur proiect e mai ieftin; extragi librăria după ce API-ul s-a liniștit.

LiveFolder, librărie internă sau pachet publicabil?
Scor: -3
Un folder e suficient

Un folder în feature (sau în shared/) cu un index.ts ca API public îți dă 80% din beneficii fără configurare. Extragi librăria când apare al doilea consumator.

src/app/features/cart/ui/ + regulă de lint pe importuri
Bifează ce e adevărat pentru codul tău. Scorul e o euristică construită pe ghidul Nx despre dimensiunea proiectelor, nu o lege. Aceeași întrebare la nivel de folder: pagina Shared vs feature.

Pentru varianta „folder”, regulile din Shared vs feature și un index.ts ca API public îți dau majoritatea beneficiilor, fără configurare suplimentară.

Când DA, când NU

Monorepo cu librării
  • Două sau mai multe aplicații împart cod de domeniu sau un design system intern.
  • Schimbări transversale dese (modele API, auth, temă) pe care vrei să le livrezi atomic, într-un PR.
  • Mai multe echipe în același cod: tag-urile și regula de lint fac granițele explicite.
  • CI lent: affected + cache reduc timpul proporțional cu ce ai atins.
  • Un design system consumat și din afara repo-ului: librărie publicabilă cu ng-packagr.
Rămâi la o aplicație cu foldere
  • O aplicație, o echipă: foldere pe domenii (Structura de foldere) și o regulă no-restricted-imports îți dau aceleași granițe.
  • Echipe care trebuie să livreze complet independent, chiar cu versiuni diferite de Angular: polyrepo sau micro frontends.
  • O librărie per componentă: configurare fără beneficiu. O librărie per domeniu și tip, nu per fișier.
  • Publicare pe npm pentru cod folosit doar în repo: versiunile adaugă ceremonie fără valoare.

Capcane frecvente

Alias spre sursa librăriei într-un workspace Angular CLI

"@acme/ui": ["projects/acme/ui/src/public-api.ts"] pare comod (nu mai construiești librăria), dar aplicația compilează codul cu esbuild, iar consumatorii externi primesc ce produce ng-packagr. Bug-urile apar doar la ei. Fix: alias spre dist și ng build @acme/ui --watch — sau treci pe Nx, gândit să compileze din sursă.

@angular/core în dependencies

Librăria își aduce propria copie de Angular, iar aplicația ajunge cu două instanțe: DI-ul și detecția de schimbări se strică în moduri greu de diagnosticat. Fix: tot ce e @angular/* merge în peerDependencies.

  • Importuri adânci (@acme/ui/src/lib/button/button) care ocolesc public-api.ts: primul refactor intern sparge consumatorii. Fix: exportă din API-ul public sau creează un entry point secundar; interzice tiparul cu no-restricted-imports.
  • Importuri relative între entry points ale aceleiași librării: ng-packagr construiește fiecare entry point separat și build-ul eșuează. Fix: importă prin numele pachetului (@acme/ui).
  • Proiecte fără tag-uri, „rezolvate” cu sourceTag: '*' → '*': regula nu mai verifică nimic. Fix: --tags la generare și tag-urile verificate la review.
  • O librărie shared „ladă de vechituri” de care depinde tot: affected = tot repo-ul la fiecare PR. Fix: sparge-o pe domenii și tipuri.
  • Cache pe task-uri nedeterministe (e2e pe un backend real, teste care citesc ora): Nx reface un rezultat care nu mai e adevărat. Fix: fără cache: true pe acele target-uri sau inputs complete.
  • Import static peste un loadChildren: chunk-ul lazy dispare în bundle-ul inițial. Regula Nx îl prinde; fără Nx, verifică bundle-ul (vezi Performanță).

Fișă rapidă

ANGULAR CLI (multi-proiect)
  ng new acme --no-create-application        workspace gol
  ng g application shop | ng g library @acme/ui
  ng build @acme/ui [--watch]                 librăriile se construiesc ÎNAINTE de consum
  tsconfig paths → ./dist/acme/ui             (+ "@acme/ui/*" pentru entry points secundare)
  cd dist/acme/ui && npm publish              doar ce iese din repo se publică

LIBRĂRIE
  ng-package.json        dest + lib.entryFile          (secundar: doar "lib")
  public-api.ts          API-ul public — restul e privat
  package.json           peerDependencies pentru @angular/*, "sideEffects": false
  compilationMode        "partial" pentru npm

NX
  nx graph                         graful de proiecte
  nx affected -t lint test build   doar proiectele afectate (dependenți tranzitivi incluși)
  "cache": true în targetDefaults  hash pe inputs → replay la a doua rulare
  tags + @nx/enforce-module-boundaries → granițe verificate la lint
  tipuri: feature → ui, data-access, util · ui → ui, util · data-access → data-access, util · util → util

Cap. 13 · 0 din 4 terminate

Ai terminat lecția?

Marcheaz-o și apare pe harta ta: în cuprins și pe prima pagină.

Se marchează singură după ce citești toată lecția.

  1. Micro frontends
  2. Web Components cu Angular Elements
  3. Monorepo, librării & granițe
  4. Arhitectură hexagonală (ports & adapters)
Vezi toată harta ta

Toate cele 75 de pagini · scrie ca să filtrezi

  • 01Ce e nou în Angular 21 & 22Toate schimbările importante din v21 și v22, pe scurt, cu link către pagina care le explică.Start
  • 01Prima ta aplicație, pas cu pasO listă de lecturi construită de la o componentă goală la o aplicație terminată — signals, @for, input-uri și output-uri, un serviciu și un formular, pas cu pas.Start
  • 01Decizii rapide„Unde pun asta?”, „Signal sau Observable?”, „Shared sau feature?” — răspunsuri scurte la întrebările de zi cu zi.Start
  • 01Întrebări de interviu Angular60 de întrebări reale de interviu pentru junior, mid și senior, cu răspunsul de dat, întrebarea care urmează și greșelile de evitat. Plus o simulare de interviu.Start
  • 02Standalone & bootstrapCum pornește o aplicație fără NgModule: bootstrapApplication, app.config.ts, imports în componentă.Fundamente moderne
  • 02Data binding și evenimenteInterpolare, binding pe proprietăți și pe atribute, evenimente cu $event și filtre de taste și cum e construit [( )] — ce intră în DOM și ce se întoarce.Fundamente moderne
  • 02Control flow: @if, @for, @switchSintaxa de blocuri care înlocuiește *ngIf/*ngFor/ngSwitch, cu track, @empty și verificare exhaustivă.Fundamente moderne
  • 02@let și expresiile noi din templateVariabile locale cu @let, arrow functions, spread, regex, instanceof, comentarii în tag-uri.Fundamente moderne
  • 02@defer — lazy loading în templateÎncarci componente grele doar când e nevoie: triggere, prefetch, placeholder, loading, error.Fundamente moderne
  • 02Class & style bindingsDe ce [class.x] și [style.x] au înlocuit ngClass și ngStyle (cu migrare automată din v21).Fundamente moderne
  • 02Stilurile componentelor și încapsularea:host, cele trei moduri de încapsulare, teme cu variabile CSS și de ce ::ng-deep e ultima soluție.Fundamente moderne
  • 02Pipes: când și cumPipe pur vs impur, pipe custom și cum alegi între pipe, computed() și o metodă.Fundamente moderne
  • 03signal() și computed()Primitivele reactive: stare scriibilă, stare derivată, egalitate, untracked și reguli de imutabilitate.Signals & reactivitate
  • 03effect() — când DA, când NUEfecte secundare sincronizate cu semnale: localStorage, logging, API-uri imperative. Și capcanele lor.Signals & reactivitate
  • 03linkedSignal() — stare derivată editabilăUn semnal care se resetează când se schimbă sursa, dar pe care îl poți și seta manual.Signals & reactivitate
  • 03resource, rxResource, httpResourceDate asincrone ca semnale: status, value, error, reload, anulare automată. Stabile din v22.Signals & reactivitate
  • 03debounced() — semnale cu întârziereVarianta „debounce” a unui semnal, fără RxJS. Perfect pentru căutare.Signals & reactivitate
  • 03Liste: căutare, filtrare, sortare, paginareUn lanț de computed-uri care recalculează doar ce s-a schimbat, cu filtrele ținute în URL, ca un link să redeschidă aceeași vedere.Signals & reactivitate
  • 03Observables de la zeroCe e un Observable, subscribe și unsubscribe, operatorii pe care îi folosești în fiecare săptămână, cold vs hot — și când ajunge un signal.Signals & reactivitate
  • 03Signals ↔ RxJStoSignal, toObservable, takeUntilDestroyed, outputFromObservable — și când RxJS rămâne alegerea bună.Signals & reactivitate
  • 04input() și output()Inputuri ca semnale (required, transform, alias) și outputuri tipizate. Data down, events up.Componente: comunicare
  • 04model() — two-way bindingBanana-in-a-box [( )] cu un singur API. Când îl folosești și când e mai bine input + output.Componente: comunicare
  • 04viewChild & contentChildInterogări ca semnale: viewChild, viewChildren, contentChild, contentChildren, opțiunea read.Componente: comunicare
  • 04Lifecycle & DestroyRefCe hook-uri mai contează în era signals, afterNextRender, DestroyRef și ngOnChanges tipizat.Componente: comunicare
  • 04Content projectionng-content cu select, sloturi multiple, conținut implicit și capcana proiecției condiționale.Componente: comunicare
  • 04ng-template & ngTemplateOutletTemplate-uri ca valori: render props în Angular, context tipizat și personalizare de componente.Componente: comunicare
  • 05Smart vs DumbContainer vs presentational: cine ține starea, cine doar afișează, și cum le împarți corect.Design de componente
  • 05Componente generice <T>Un DataTable<T> sau Select<T> tipizat corect, cu template-uri pentru celule.Design de componente
  • 05Componente dinamiceNgComponentOutlet, createComponent cu inputBinding/outputBinding și un dashboard construit din config.Design de componente
  • 05Directive & host compositionDirective atribut, metadata host: {} și Directive Composition API (hostDirectives).Design de componente
  • 05Controale custom de formularFormValueControl (Signal Forms) vs ControlValueAccessor: un rating cu stele, în ambele variante.Design de componente
  • 05Change detectionOnPush e implicit din v22, zone.js lipsește din v21. Ce declanșează randarea și cum să nu te arzi.Design de componente
  • 06inject() & @Service()Noul decorator @Service() din v22, inject() vs constructor și contextul de injecție.Dependency Injection
  • 06Providers & ierarhia de injectoriRoot vs rută vs componentă, useClass/useValue/useFactory/useExisting și modificatorii optional/self/skipSelf.Dependency Injection
  • 06InjectionToken & provideX()Configurare tipizată, funcții provideFeature(), makeEnvironmentProviders și inițializatori.Dependency Injection
  • 06injectAsync() — servicii lazyÎncarci un serviciu greu abia când ai nevoie de el, cu prefetch la idle.Dependency Injection
  • 07Structura de folderecore / shared / features, ce intră în fiecare și cum arată un feature pe dinăuntru.Arhitectura aplicației
  • 07Shared vs featureCând muți o componentă în shared și (mai ales) când NU. Checklist concret.Arhitectura aplicației
  • 07Interfețe & modeleInterfață inline sau fișier separat? DTO vs model de domeniu, type vs interface, unde le pui.Arhitectura aplicației
  • 07State management cu signalsStore-uri simple pe semnale, facade, stare locală vs globală și când îți trebuie NgRx.Arhitectura aplicației
  • 07Style guide & namingConvențiile oficiale actuale: nume de fișiere fără sufixe, protected, readonly, ordinea membrilor.Arhitectura aplicației
  • 07Barrels, aliasuri & granițeindex.ts, path aliases, API public pe feature și reguli de dependență care se pot verifica automat.Arhitectura aplicației
  • 07Erori: @boundary & ErrorHandlerNoul bloc @boundary din v22.2, ErrorHandler global, erori HTTP și erori din resource.Arhitectura aplicației
  • 08Routing modern & lazy loadingloadComponent, rute copil, parametri ca input-uri și moștenirea parametrilor (default nou în v22).Routing
  • 08Guards & resolverscanActivate, canMatch, canDeactivate și resolve ca funcții, cu redirect prin UrlTree / RedirectCommand.Routing
  • 09Signal Forms — bazeleFormulare construite pe semnale: form(), [formField], validare prin schemă. Stabile din v22.Formulare
  • 09Signal Forms — avansatValidare custom și cross-field, async/HTTP cu debounce, liste dinamice, disabled/hidden, scheme reutilizabile.Formulare
  • 09Reactive Forms tipizateFormGroup tipizat, nonNullable, FormArray — și cum conviețuiesc cu Signal Forms.Formulare
  • 10HttpClient, interceptors & API layerHttpClient fără setup (v21), Fetch implicit (v22), interceptori funcționali și stratul de API cu mapare DTO.HTTP & date
  • 11Performanță — checklistCe chiar contează: OnPush + signals, track, @defer, lazy routes, imagini, bundle și hidratare.Calitate & performanță
  • 11Testare cu VitestVitest e runner-ul implicit din v21. TestBed, componente cu semnale, servicii, HTTP și harness-uri.Calitate & performanță
  • 11DevTools: cum te uiți în aplicațieChrome DevTools și Angular DevTools pas cu pas: ce arată fiecare panou, cu exerciții care merg chiar pe site-ul ăsta.Calitate & performanță
  • 11Angular AriaComponente headless, accesibile, pe care le stilizezi cum vrei: tabs, accordion, menu, listbox...Calitate & performanță
  • 12SSR, prerender & render modesCând randezi pe server, când la build și când doar în browser: RenderMode pe rută, app.routes.server.ts și costurile reale.SSR & hidratare
  • 12Hidratare: event replay & incrementalCum preia browserul HTML-ul de pe server fără să-l refacă: hidratare completă, event replay, @defer (hydrate on …) și erorile de nepotrivire.SSR & hidratare
  • 12Cod compatibil cu serverulwindow nu există pe server: PLATFORM_ID, afterNextRender, DOCUMENT, TransferState, cache-ul HTTP și PendingTasks.SSR & hidratare
  • 13Micro frontendsCând merită să spargi o aplicație în mai multe deploy-uri și când e doar complexitate: Native Federation, Module Federation, iframes, Web Components.Arhitectură la scară
  • 13Web Components cu Angular ElementscreateCustomElement: o componentă Angular folosită ca tag HTML oriunde, plus comunicarea între aplicații prin evenimente și BroadcastChannel.Arhitectură la scară
  • 13Monorepo, librării & granițeUn workspace cu mai multe aplicații și librării: ng generate library, Nx, entry points secundare, publicare și reguli de dependență.Arhitectură la scară
  • 13Arhitectură hexagonală (ports & adapters)Domeniul fără Angular, porturi ca clase abstracte și adaptoare interschimbabile: HTTP, localStorage sau in-memory, fără să atingi UI-ul.Arhitectură la scară
  • 14Semnalele pe dinăuntruGraful reactiv: producători și consumatori, push-pull, de ce computed e lazy și glitch-free, problema diamantului și când rulează efectele.Reactivitate avansată
  • 14RxJS avansat: operatori de ordin superiorswitchMap, mergeMap, concatMap și exhaustMap pe o diagramă live, plus retry cu backoff, shareReplay și cum eviți scurgerile.Reactivitate avansată
  • 14NgRx SignalStoreState management la scară pe semnale: withState, withComputed, withMethods, rxMethod, entități și hooks — și când un store simplu e suficient.Reactivitate avansată
  • 14Mașini de stări cu signalsFără stări imposibile: o mașină tipizată pe signals, cu tranziții, guards și diagrama stărilor, live.Reactivitate avansată
  • 15WebSockets: timp real care rezistă rețeleiUn socket tipizat ca signals: reconectare cu backoff și jitter, heartbeat, coadă de mesaje cât ești offline și siguranță la SSR.Timp real și offline
  • 15Actualizări optimiste și rollbackInterfață instantanee, dar onestă: snapshot și rollback, răspunsuri sosite în altă ordine, chei de idempotență.Timp real și offline
  • 15Offline-first: Service Worker și cacheService Worker-ul Angular: strategii de cache, anunțarea versiunilor noi și o coadă pentru modificările făcute offline.Timp real și offline
  • 16Web WorkersCalcule grele fără să blochezi interfața: un worker real în pagină, comunicare prin mesaje și când nu merită.UI & performanță avansată
  • 16Liste uriașe: virtual scroll100.000 de rânduri fără lag: CDK virtual scroll, itemSize, trackBy și alternativele (paginare, infinite scroll).UI & performanță avansată
  • 16Animații moderne & View Transitionsanimate.enter și animate.leave în locul @angular/animations (depreciat), tranziții CSS și View Transitions la navigare.UI & performanță avansată
  • 16Memory leaks: cum le găsești și le repariSubscripții, timere și listeneri care trăiesc mai mult decât componenta lor — cum îi vezi în DevTools și cum îi închizi.UI & performanță avansată
  • 17Securitate: XSS, sanitizare & CSPCe face Angular automat împotriva XSS, când devine periculos bypassSecurityTrust, plus CSP cu nonce, Trusted Types și XSRF.Securitate & globalizare
  • 17Autentificare: token-uri & refreshUn interceptor care atașează token-ul, reîmprospătează o singură dată la 401 și reia cererile din coadă — plus unde ții token-ul.Securitate & globalizare
  • 17OAuth 2.1 și OpenID Connect cu PKCEFluxul authorization code cu PKCE, pas cu pas: verifier și challenge, state și nonce, ID token-ul și de ce un BFF e mai sigur.Securitate & globalizare
  • 17Internaționalizare (i18n)$localize, atributul i18n, ICU plural/select, build per limbă vs. traduceri la runtime și formatarea după locale.Securitate & globalizare