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 dinhttp://localhost:4200 - Angular CLI —
ng new,ng serve, structura unui workspace Angular - Environments —
environment.tsvsenvironment.prod.tspentruapiUrl - Modele TypeScript — interfețe care reflectă DTO-urile backend (
Article,Category,User) - Servicii —
AuthService(login + register + state cuBehaviorSubject+ JWT înlocalStorage),ArticleServiceread-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 - Componente —
LoginComponentcu reactive forms,ArticleListComponentread-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 peAuthorId - Lab 8: Web API RESTful cu DTOs, Swagger, JWT Bearer pe scrieri
- Lab 9:
Lab09.Tests/— unit tests peArticleService(EF InMemory) + integration tests cuWebApplicationFactory<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.cspentruhttp://localhost:4200 - Rute API curate —
/api/articlesși/api/auth(în loc de/api/articlesapi//api/authapidin Lab 8) - Endpoint nou —
POST /api/auth/register(creează user + atribuie rolulUser+ 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?
Două procese independente, două porturi:
dotnet runîn folderulLab11_start/backend/→ backend pe5000(sau ce zicelaunchSettings.json)ng serveîn folderulnews-portal-app/→ frontend pe4200
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:4200nu poate face fetch cătrehttps://localhost:7001decât dacă serverul răspunde cu header-ulAccess-Control-Allow-Origin: http://localhost:4200. AddCorscuWithOrigins(...)î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 (inclusivAuthorizationșiContent-Typepentru JSON).AllowAnyMethod()— permite GET, POST, PUT, DELETE, OPTIONS.
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.
Notă: În producție înlocuiți
http://localhost:4200cu 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 e7001, dar template-urile mai vechi puneau7xxxrandom. Portul HTTP (5001) merge și el dacă comentațiapp.UseHttpsRedirection()înProgram.cs. Recomandăm HTTPS +dev-certs --trustca să fie ca în producție.
Diferență față de versiunile vechi: până la Angular 16, default-ul era invers (
environment.tscu dev,environment.prod.tscu producție). Versiunile noi inversează —environment.tse 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
-
Instalați
@angular/cliși creați workspace-ulnews-portal-app/înLab11_start/, ca sibling cubackend/. -
Porniți backend-ul cu
dotnet runînLab11_start/backend/și verificați căhttps://localhost:7001/swaggerse deschide. -
Trust dev cert:
dotnet dev-certs https --trust. -
Porniți Angular cu
ng serveînnews-portal-app/și deschidețihttp://localhost:4200. -
Generați environments cu
ng generate environmentsși setațiapiUrl: 'https://localhost:7001'înenvironment.development.ts. -
Verificare CORS: deschideți DevTools (F12) → tab
Consolepe 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 → UseAuthenticationdinProgram.cs.
Output așteptat
- Două terminale active: unul cu
dotnet run, unul cung serve. http://localhost:4200afișează pagina default Angular.- Apelul
fetchdin DevTools returnează lista de articole în consolă (fără erori CORS). src/environments/environment.development.tsexistă cuapiUrlpopulat.