11

Lab 11 - Angular Frontend (Part 1)

Partea 1 din 4

Actualizat 2026-05-11Sursă pe GitHub ↗Cod start

CTI — Dezvoltarea Aplicațiilor Web — Laborator 11

Angular Frontend (Part 1) — setup, servicii, componente read-only, autentificare


Obiective

Laboratorul deschide o nouă etapă în proiectul News Portal: până acum tot UI-ul era în Razor (Views server-side), iar API-ul JSON din Lab 8 a fost testat doar din Swagger. Acum punem un frontend Angular peste backend, care consumă același API. Backend-ul rămâne neschimbat — adaugăm doar CORS și un endpoint nou pentru register.

Ce facem:

  • CORS în Program.cs — de ce browser-ul blochează request-urile cross-origin și cum le permitem din http://localhost:4200
  • Angular CLIng new, ng serve, structura unui workspace Angular
  • Environmentsenvironment.ts vs environment.prod.ts pentru apiUrl
  • Modele TypeScript — interfețe care reflectă DTO-urile backend (Article, Category, User)
  • ServiciiAuthService (login + register + state cu BehaviorSubject + JWT în localStorage), ArticleService read-only (getAll, getById)
  • HTTP Interceptor — atașează automat header-ul Authorization: Bearer <token> la fiecare request
  • AuthGuard — protejează rute (deocamdată îl pregătim, îl folosim efectiv în Lab 12)
  • Routing — rute simple /articles, /articles/:id, /login, /register
  • ComponenteLoginComponent cu reactive forms, ArticleListComponent read-only

Lab 11 e citire + autentificare. Toate operațiile de scriere (create, edit, delete articole, role-based UI) ajung în Lab 12.

Punctaj (4p)

  • 4 exerciții × 1p fiecare.
  • Ex 1 acoperă Partea 1 (setup + verificare CORS), Ex 2-3-4 acoperă Partea 3 (componente).
Exercițiu Subiect Punctaj
Ex 1 Setup proiect Angular + verificare backend 1p
Ex 2 RegisterComponent cu reactive form + apel API 1p
Ex 3 ArticleDetailComponent (parametru de rută) 1p
Ex 4 HeaderComponent cu navigare reactivă 1p

Recapitulare Lab 10

Din laboratoarele anterioare avem:

  • Lab 6: Repository Pattern + Service Layer + MVC + async/await
  • Lab 7: ASP.NET Core Identity, Roles (Admin, User), content ownership pe AuthorId
  • Lab 8: Web API RESTful cu DTOs, Swagger, JWT Bearer pe scrieri
  • Lab 9: Lab09.Tests/ — unit tests pe ArticleService (EF InMemory) + integration tests cu WebApplicationFactory<Program>
  • Lab 10: Many-to-Many (Tag + ArticleTag), middleware custom (logging + exception handling), Serilog cu sinks Console + File

Lab11_start continuă fără să strice nimic. Backend-ul rămâne MVC + API. Adăugăm:

  • CORS preconfigurat în Program.cs pentru http://localhost:4200
  • Rute API curate/api/articles și /api/auth (în loc de /api/articlesapi / /api/authapi din Lab 8)
  • Endpoint nouPOST /api/auth/register (creează user + atribuie rolul User + returnează JWT)

Structura aplicației Angular o construim de la zero cu Angular CLI — nu există un schelet pre-generat în Lab11_start. Cele două proiecte (backend .NET + frontend Angular) rulează în paralel, pe porturi diferite, și comunică prin HTTP.


De ce un SPA?

Până acum aplicația noastră a fost server-rendered: la fiecare click, browser-ul cere o pagină nouă, serverul rulează Razor, returnează HTML complet. Funcționează — dar are limite.

Aspect MVC + Razor (server) SPA (Angular)
Render inițial rapid (HTML gata din server) mai lent (descarcă bundle JS, apoi randează)
Navigare între pagini round-trip complet la server client-side, fără reload
State pe client minim (cookie + form) bogat (localStorage, RxJS streams)
Dezvoltare backend ↔ frontend împletite în același proiect proiecte separate, contract API
Echipe un dev face tot stack-ul frontend + backend pot lucra independent

Razor e ideal pentru aplicații simple cu trafic moderat. SPA-ul devine util când:

  • ai ecrane interactive (filtrare/sortare în timp real, notificări push, validări dinamice)
  • ai mai multe clienți pentru același API (web + mobile + desktop)
  • ai echipe separate (frontend / backend) cu cicluri de release diferite
  • vrei decuplare clară prin contract REST + DTOs

În Lab 11 vom rula ambele: backend-ul Razor + MVC rămâne pe /Articles/..., iar Angular consumă /api/articles în paralel pe alt port. Asta arată exact că API-ul e independent de UI — orice client (Razor, Angular, mobile, Postman) îl poate folosi.


De ce Angular?

Cele trei framework-uri SPA dominante astăzi:

Framework Stil Cand alegi
Angular opinionated, full-framework (CLI, DI, modules, RxJS, forms, router — toate built-in) echipe enterprise, proiecte mari cu structură riguroasă
React bibliotecă (UI), restul îl alegi tu (router, state, forms) flexibilitate, ecosistem vast, învățare graduală
Vue mijlocul drumului, syntactic mai simplu proiecte medii, curbă scurtă de învățare

Angular se aliniază natural cu ecosistemul .NET pe care l-am construit:

  • TypeScript built-in (strict typing, interfețe — like C#)
  • Dependency Injection identic conceptual cu cel din ASP.NET Core
  • Angular CLI — generează componente, servicii, module cu structură consistentă (analog cu dotnet ef migrations add / template-uri Visual Studio)
  • RxJS Observables — modul nativ de gândit datele async (analog cu Task<T> din C#, dar cu flow reactiv)

Pentru lab folosim standalone Angular CLI (ng new) cu modulul clasic AppModule. Nu folosim standalone components (introdus în Angular 17+) ca să rămânem la patternul predabil cu NgModule.


Cum se leagă cele două proiecte?

Doua procese: ng serve (Node) pe portul 4200 livreaza bundle-ul JS Angular catre browser; Angular SPA face fetch /api/* spre dotnet run (.NET) pe 5000/7001 cu Authorization: Bearer JWT (cross-origin, gated de CORS middleware); backend-ul citeste/scrie din SQL Server LocalDB prin EF Core.

Două procese independente, două porturi:

  • dotnet run în folderul Lab11_start/backend/ → backend pe 5000 (sau ce zice launchSettings.json)
  • ng serve în folderul news-portal-app/ → frontend pe 4200

Browser-ul descarcă bundle-ul JS de la 4200 și de acolo face XHR/fetch către 5000. Browser-ul vede două origini diferite și aplică Same-Origin Policy — fără CORS configurat pe backend, request-urile API sunt blocate cu 403/401. De aia primul pas e CORS-ul pe Program.cs.

Notă: în producție toate astea pleacă în spatele aceluiași domeniu cu reverse proxy (nginx/IIS) — Angular bundle servit static din /, API pe /api/.... Atunci nu mai ai CORS (același origin). Dar în dev, cele două porturi diferite cer CORS explicit. Producția e domeniu de Lab 13 (Docker).

Parte 1 — Setup Angular peste backend-ul existent

Pornim de la Lab11_start/backend/ (proiectul .NET cu CORS deja configurat) și ne construim un workspace Angular alături, în interiorul aceluiași Lab11_start/.

CORS pe backend — ce e deja făcut

În Lab11_start/backend/Program.cs aveți deja:

// Inainte de var app = builder.Build();
builder.Services.AddCors(options =>
{
    options.AddPolicy("AllowAngularDev", policy =>
    {
        policy.WithOrigins("http://localhost:4200")
              .AllowAnyHeader()
              .AllowAnyMethod();
    });
});

// Dupa var app = builder.Build();
app.UseRouting();
app.UseCors("AllowAngularDev");   // dupa UseRouting, inainte de Authentication
app.UseAuthentication();
app.UseAuthorization();

Ce face WithOrigins("http://localhost:4200"):

  • Browser-ul aplică Same-Origin Policy: o pagină de pe http://localhost:4200 nu poate face fetch către https://localhost:7001 decât dacă serverul răspunde cu header-ul Access-Control-Allow-Origin: http://localhost:4200.
  • AddCors cu WithOrigins(...) îi spune ASP.NET Core să adauge header-ul ăla automat la response-uri pentru request-urile care vin din origin-ul listat.
  • AllowAnyHeader() — permite orice header (inclusiv Authorization și Content-Type pentru JSON).
  • AllowAnyMethod() — permite GET, POST, PUT, DELETE, OPTIONS.
Sequence diagram: browserul trimite OPTIONS preflight cu Origin si Access-Control-Request-Method; backend-ul raspunde 204 cu Access-Control-Allow-Origin, -Methods si -Headers; browserul trimite apoi cererea GET reala cu Authorization Bearer; backend raspunde 200 + JSON + ACAO; daca ordinea middleware e gresita, backend raspunde 401 fara ACAO si browserul raporteaza eronat ca eroare CORS.

Ordinea în pipeline: UseCors trebuie după UseRouting și înainte de UseAuthentication. Dacă inversați, browser-ul vede răspunsul 401 fără header-ul CORS și-l blochează ca eroare de CORS, nu de auth — debug confuz.

Pipeline orizontal ASP.NET Core: HTTP request -> UseRouting -> UseCors (evidentiat galben) -> UseAuthentication -> UseAuthorization -> MapControllers. Sub fiecare middleware: ce face. Avertisment rosu sub pipeline: daca inversezi UseCors si UseAuth, raspunsul 401 nu are header ACAO si browserul raporteaza eroare CORS in loc de eroare auth.

Notă: În producție înlocuiți http://localhost:4200 cu domeniul real (ex: https://newsportal.exemplu.ro). Nu folosiți niciodată AllowAnyOrigin() în producție când API-ul e autentificat — orice site poate face request-uri din browser-ul utilizatorului.

Endpoint-uri API disponibile

Lab11_start/backend/Controllers/Api/ are deja două controllere RESTful — Angular o să le consume.

Metoda Rută Auth DTO body Ce face
GET /api/articles public listă articole
GET /api/articles/{id} public detaliu articol
POST /api/articles JWT CreateArticleDto creează articol (Lab 12)
PUT /api/articles/{id} JWT UpdateArticleDto update — owner sau Admin (Lab 12)
DELETE /api/articles/{id} JWT delete — owner sau Admin (Lab 12)
POST /api/auth/login public LoginDto {Email, Password} returnează {token, expiresIn}
POST /api/auth/register public RegisterDto {Email, FullName, Password} creează user + atribuie rolul User + returnează JWT

În Lab 11 consumăm doar GET /api/articles* și ambele rute de auth. Restul rămân pentru Lab 12.

Angular CLI — instalare

Angular CLI e instrumentul oficial pentru creat și gestionat proiecte Angular. Necesită Node.js LTS (≥ 18). Verificare:

node --version    # v20.x sau v18.x
npm --version     # 10.x sau 9.x

Dacă nu aveți Node, instalați de la nodejs.org (LTS, nu Current).

Apoi CLI-ul, global:

npm install -g @angular/cli
ng version

ng version trebuie să afișeze ceva de forma Angular CLI: 17.x.x (sau mai nou). Dacă vedeți erori npm ERR! EACCES, pe Windows rulați PowerShell ca Administrator; pe Linux/Mac folosiți sudo sau configurați un prefix npm pentru user.

ng new news-portal-app

Creați workspace-ul Angular înăuntrul lui Lab11_start/, ca sibling cu backend/ — două proiecte separate care trăiesc unul lângă altul:

Lab11_start/
├── backend/                 ← .NET backend (din arhivă)
│   ├── Lab11.csproj
│   ├── Program.cs
│   └── ...
└── news-portal-app/         ← Angular frontend (creat acum)
    ├── angular.json
    ├── package.json
    └── src/

Comanda (din Lab11_start/, lângă backend/):

cd Lab11_start
ng new news-portal-app
cd news-portal-app

CLI-ul vă pune câteva întrebări — răspunsuri pentru lab:

Întrebare Răspuns
Which stylesheet format would you like to use? CSS
Do you want to enable Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering)? No
Which AI tools do you want to configure with Angular best practices? None (pentru lab; orice altă opțiune doar creează fișiere AGENTS.md / CLAUDE.md / etc. cu rule-uri Angular - poți reveni cu ng update mai târziu dacă vrei)

Routing e activ implicit (vechile versiuni întrebau separat — variantele noi îl includ default). Generarea durează 1-2 minute (instalează ~1000 de pachete npm).

ng serve

ng serve

Output:

Local:   http://localhost:4200/

Deschideți http://localhost:4200 — pagina default Angular cu logo și link-uri către documentație. Lăsați ng serve pornit — recompilează și reîncarcă automat la fiecare salvare de fișier (hot reload).

Pornire backend în paralel

Într-un al doilea terminal, în Lab11_start/backend/:

cd Lab11_start/backend
dotnet run

Output:

Now listening on: https://localhost:7001
Now listening on: http://localhost:5001

(Porturile vin din Properties/launchSettings.json.)

Trust dev certificate (o singură dată per mașină):

dotnet dev-certs https --trust

Fără asta, browser-ul respinge certificatul self-signed la apelurile HTTPS din Angular și veți vedea net::ERR_CERT_AUTHORITY_INVALID în consolă — cu un mesaj de CORS care e doar simptomul.

Structura proiectului Angular

În news-portal-app/:

news-portal-app/
├── angular.json              configuratia workspace-ului (build, serve, test)
├── package.json              dependinte npm + script-uri (start, build, test)
├── tsconfig.json             configurare TypeScript
└── src/
    ├── main.ts               punctul de pornire (bootstrap)
    ├── index.html            shell-ul HTML (un singur element <app-root>)
    ├── styles.css            stiluri globale
    ├── app/
    │   ├── app.module.ts     modulul root
    │   ├── app.component.*   componenta root (TS, HTML, CSS, Spec)
    │   └── app-routing.module.ts
    └── environments/
        ├── environment.ts        config dev (default)
        └── environment.prod.ts   config prod

Pe parcursul laboratorului adăugăm sub src/app/ o structură organizatorică:

src/app/
├── core/
│   ├── services/             singletons reutilizabile
│   │   ├── auth.ts
│   │   ├── article.ts
│   │   └── http-config.interceptor.ts
│   └── guards/
│       └── auth-guard.ts
├── features/
│   ├── articles/
│   │   ├── article-list/
│   │   └── article-detail/   ← exercitiu
│   └── auth/
│       ├── login/
│       └── register/         ← exercitiu
├── shared/
│   ├── components/
│   │   └── header/           ← exercitiu
│   └── models/
│       └── article.ts
├── app.module.ts
├── app-routing.module.ts
└── app.component.*
Folder Conține Convenție
core/ servicii singleton, guards, interceptors importat o singură dată, în AppModule
features/ grupuri de componente per-funcționalitate de obicei un sub-modul per feature (în lab rămâne flat)
shared/ modele, componente reutilizabile, pipes imports / exports liberi

Convenția nu e impusă de Angular — e larg adoptată de comunitate și ușor de navigat. Cu Lab11_start/backend/ care folosește deja patternul Repository / Service Layer, regăsiți aceeași idee de stratificare.

environments/apiUrl configurabil

Angular face build replacement: la ng serve / ng build --configuration development, conținutul lui environment.ts e înlocuit cu environment.development.ts. Așa schimbați URL-uri / chei fără să modificați cod aplicativ.

CLI 17+ nu mai generează folderul environments/ default. Îl creați cu:

ng generate environments

Asta produce două fișiere și actualizează angular.json cu regula de înlocuire.

src/environments/environment.ts (default, folosit la build de producție):

export const environment = {
  production: true,
  apiUrl: 'https://newsportal.exemplu.ro'
};

src/environments/environment.development.ts (folosit de ng serve și ng build --configuration development):

export const environment = {
  production: false,
  apiUrl: 'https://localhost:7001'
};

Notă: Verificați portul HTTPS din Lab11_start/backend/Properties/launchSettings.json — la noi e 7001, dar template-urile mai vechi puneau 7xxx random. Portul HTTP (5001) merge și el dacă comentați app.UseHttpsRedirection() în Program.cs. Recomandăm HTTPS + dev-certs --trust ca să fie ca în producție.

Diferență față de versiunile vechi: până la Angular 16, default-ul era invers (environment.ts cu dev, environment.prod.ts cu producție). Versiunile noi inversează — environment.ts e producție, override-ul de development e separat. Practic e același mecanism, doar cu fișiere redenumite.


Exercițiul 1 (1p) — setup proiect și verificare backend

Cerințe

  1. Instalați @angular/cli și creați workspace-ul news-portal-app/ în Lab11_start/, ca sibling cu backend/.

  2. Porniți backend-ul cu dotnet run în Lab11_start/backend/ și verificați că https://localhost:7001/swagger se deschide.

  3. Trust dev cert: dotnet dev-certs https --trust.

  4. Porniți Angular cu ng serve în news-portal-app/ și deschideți http://localhost:4200.

  5. Generați environments cu ng generate environments și setați apiUrl: 'https://localhost:7001' în environment.development.ts.

  6. Verificare CORS: deschideți DevTools (F12) → tab Console pe pagina Angular și rulați:

    fetch('https://localhost:7001/api/articles')
      .then(r => r.json())
      .then(console.log)
    

    Trebuie să vedeți un array de articole. Dacă vedeți eroare CORS, verificați ordinea UseRouting → UseCors → UseAuthentication din Program.cs.

Output așteptat

  • Două terminale active: unul cu dotnet run, unul cu ng serve.
  • http://localhost:4200 afișează pagina default Angular.
  • Apelul fetch din DevTools returnează lista de articole în consolă (fără erori CORS).
  • src/environments/environment.development.ts există cu apiUrl populat.