Parte 5 — Cheatsheet
Referință rapidă pentru ce-ați văzut în Lab 12. Pentru Angular CLI / CORS / template syntax / routing de bază, vedeți cheatsheet-ul Lab 11.
Pornire mediu (recapitulare)
# Terminal 1: backend
cd Lab12_start/backend
dotnet run
# Terminal 2: frontend
cd Lab12_start/news-portal-app
npm install # doar prima data
ng serve # http://localhost:4200
Login dev: admin@newsportal.com / Admin@123 → rolul Admin în JWT.
HttpClient — metode CRUD complete
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
private http = inject(HttpClient);
// GET — listă (200 OK + array JSON)
this.http.get<Article[]>(url): Observable<Article[]>
// GET — detaliu (200 OK + obiect JSON)
this.http.get<Article>(`${url}/${id}`): Observable<Article>
// POST — create (201 Created + resursa creată)
this.http.post<Article>(url, dto): Observable<Article>
// PUT — update (204 No Content)
this.http.put<void>(`${url}/${id}`, dto): Observable<void>
// DELETE — remove (204 No Content)
this.http.delete<void>(`${url}/${id}`): Observable<void>
Pattern de consum standard:
this.service.method(...).subscribe({
next: data => /* succes */,
error: err => /* HttpErrorResponse */
});
HttpErrorResponse are .status (cod HTTP), .error (body de la server), .message.
Reactive Forms — pattern complet
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
private fb = inject(FormBuilder);
form!: FormGroup;
ngOnInit(): void {
this.form = this.fb.group({
title: ['', [Validators.required, Validators.minLength(5)]],
content: ['', [Validators.required, Validators.minLength(20)]],
categoryId: [null, Validators.required]
});
}
onSubmit(): void {
if (this.form.invalid) {
this.form.markAllAsTouched();
return;
}
const dto = this.form.value;
this.service.create(dto).subscribe(...);
}
Template:
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<input formControlName="title" class="form-control">
<div *ngIf="form.get('title')?.touched && form.get('title')?.errors as e">
<span *ngIf="e['required']">Obligatoriu</span>
<span *ngIf="e['minlength']">Minim {{ e['minlength'].requiredLength }} caractere</span>
</div>
<button [disabled]="form.invalid">Submit</button>
</form>
Componenta standalone trebuie să aibă ReactiveFormsModule în array-ul imports al @Component.
Validatori built-in
| Validator | Eroare key | Note |
|---|---|---|
Validators.required |
required |
valoare ne-vidă |
Validators.requiredTrue |
required |
doar pentru checkbox-uri „terms accepted" |
Validators.email |
email |
regex RFC 5322 simplificat |
Validators.minLength(n) |
minlength |
string sau array |
Validators.maxLength(n) |
maxlength |
string sau array |
Validators.min(n) |
min |
numeric |
Validators.max(n) |
max |
numeric |
Validators.pattern(re) |
pattern |
string sau RegExp |
Combinare: [Validators.required, Validators.minLength(5)] — ambele se aplică, errors agregă.
Stări control + form
| Property | Sens |
|---|---|
valid / invalid |
trec / nu trec validarea |
pristine / dirty |
nu a fost modificat / a fost modificat |
touched / untouched |
s-a făcut blur / nu s-a interacționat |
pending |
validare async în curs |
value |
valoarea curentă |
errors |
obiect { keyValidator: details } sau null |
Tipic afișezi erori cu *ngIf="ctrl.touched && ctrl.invalid" — nu speria user-ul cu erori la load.
patchValue vs setValue
// patchValue - relaxat, accepta lipsa unor chei
this.form.patchValue({ title: 'X', content: 'Y' });
// categoryId ramane neschimbat
// setValue - strict, cere toate cheile
this.form.setValue({ title: 'X', content: 'Y', categoryId: 1 });
// fara una -> eroare runtime
Pentru load din API folosiți patchValue — DTO-ul poate avea câmpuri extra (id, publishedAt) care nu-s în form.
Routing pentru CRUD
// app.routes.ts
import { Routes } from '@angular/router';
import { authGuard } from './core/guards/auth-guard';
export const routes: Routes = [
{ path: 'articles', component: ArticleList },
{ path: 'articles/new', component: ArticleForm, canActivate: [authGuard] },
{ path: 'articles/:id/edit', component: ArticleForm, canActivate: [authGuard] },
{ path: 'articles/:id', component: ArticleDetail }
];
Reguli ordine:
articles/newînainte dearticles/:id— altfel:idprinde'new'.articles/:id/editșiarticles/:idse distinge prin segment count, ordinea lor nu contează.
ActivatedRoute — citire param
private route = inject(ActivatedRoute);
ngOnInit() {
// Snapshot - o data, la init
const id = this.route.snapshot.paramMap.get('id'); // string | null
// Reactiv - la fiecare schimbare (utile cand path e identic, doar param-ul difera)
this.route.paramMap.subscribe(map => {
const id = map.get('id');
});
// Query params: /x?returnUrl=/y
const ret = this.route.snapshot.queryParams['returnUrl'];
}
Pentru :id/edit în Lab 12 e suficient snapshot — componenta se distruge la navigate-out.
Role-based UI
currentUser = signal<CurrentUser | null>(null);
ngOnInit(): void {
this.authService.currentUser$.subscribe(u => this.currentUser.set(u));
}
canModify(article: Article): boolean {
const u = this.currentUser();
if (!u) return false;
return u.roles.includes('Admin') || article.authorId === u.id;
}
Template:
<ng-container *ngIf="canModify(article)">
<button (click)="editArticle(article.id, $event)">Edit</button>
<button (click)="deleteArticle(article.id, $event)">Delete</button>
</ng-container>
<ng-container> = wrapper logic fără DOM. Ideal pentru *ngIf peste mai multe elemente.
Delete cu confirm + refresh local
deleteArticle(id: number, event: Event): void {
event.stopPropagation();
if (!confirm('Sigur stergeti?')) return;
this.articleService.delete(id).subscribe({
next: () => this.articles.update(arr => arr.filter(a => a.id !== id)),
error: () => this.error.set('Eroare la stergere')
});
}
Status code-uri HTTP la CRUD
| Status | Cand | Ce face frontend |
|---|---|---|
200 OK |
GET success | citește body |
201 Created |
POST success | citește body (resursa nouă) |
204 No Content |
PUT / DELETE success | nu citește body |
400 Bad Request |
validare eșuată | afișează err.error.errors[] (ModelState) |
401 Unauthorized |
fără token / token invalid | redirect la /login (interceptor de erori) |
403 Forbidden |
autentificat dar fără permisiuni | mesaj „nu aveți acces" |
404 Not Found |
resursa nu există | mesaj „nu a fost găsit" |
500 Server Error |
excepție backend (prinsă de middleware) | mesaj generic, nu detaliile |
Refactor backend pe care l-ați primit (recapitulare)
Toate au fost pre-aplicate în Lab12_start/backend/ — nu trebuie să le scrieți, dar e bine să știți unde să vă uitați:
| Pattern | Fișier | Înlocuiește |
|---|---|---|
| Mapping centralizat Article → ViewModel | Mappings/ArticleViewModelMappings.cs |
mapping inline din HomeController + ArticlesController (5 acțiuni) |
| Ownership check refolosibil | Authorization/ClaimsPrincipalExtensions.cs (User.CanModifyArticle(article)) |
IsOwnerOrAdmin privat duplicat în 2 controllere |
| JWT generation extracted | Services/IJwtService.cs + JwtService.cs |
28 linii din AuthApiController.GenerateJwtAsync |
| Categories endpoint nou | Controllers/Api/CategoriesApiController.cs |
(lipsea — necesar pentru dropdown) |
Probleme comune Lab 12
| Problemă | Cauză | Fix |
|---|---|---|
TODO Lab 12: implementati ... aruncat la subscribe |
service-ul are stub, nu apel real | înlocuiți throw new Error(...) cu return this.http.<verb>(...) |
| Submit nu face nimic | form.invalid true, dar fără markAllAsTouched() |
adăugați call-ul în guard |
| Dropdown-ul de categorii e gol | categoryService.getAll() aruncă TODO sau nu se apelează în ngOnInit |
recheck Ex 1 + ngOnInit |
<select> arată valoarea ca string, dar form-ul cere number |
folosiți [ngValue]="c.id", nu [value]="c.id" |
înlocuiți |
| Buton Edit/Delete vizibil pentru oricine | canModify returnează true neglijent |
implementați conform Ex 4 |
403 Forbidden la DELETE chiar pentru Admin |
rolul Admin lipsește din JWT (cont creat fără seedRoles) |
recreate user via Register sau verificați seedul |
currentUser e null chiar după login |
subscribe la currentUser$ lipsește în ngOnInit |
adăugați-l |
cannot find module '...' la build |
path greșit la import (relative) | folosiți '../../../shared/...' din core/services/ |
Cannot read properties of null (reading 'errors') |
template accesează form.get('x') înainte ca form-ul să existe |
form!: FormGroup + construire în ngOnInit; folosiți ?. în template |
404 la /api/categories |
uitat să fie pornit backend-ul, sau port greșit în environment |
recheck apiUrl |
Linkuri rapide
- Reactive forms ·
FormBuilder· Validators ActivatedRoute· Route paramsHttpClientpost/put/deleteng-container·*ngIf- Async pipe (alternativă la
subscribeîn component) - ASP.NET Core JwtBearer · Status codes