11

Lab 11 - Angular Frontend (Part 1)

Partea 4 din 4

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

Parte 4 — Cheatsheet

Pagină de referință rapidă pentru tot ce ați văzut în lab.

Angular CLI — comenzi uzuale

# Setup global
npm install -g @angular/cli
ng version

# Workspace nou
ng new news-portal-app

# Dev server (hot reload pe localhost:4200)
ng serve

# Generate (g = generate, c = component, s = service)
ng generate component features/auth/login
ng g s core/services/article          # service
ng g g core/guards/auth                # guard

# Build pentru productie (output in dist/)
ng build --configuration production

CORS — pattern complet (ASP.NET Core)

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

// DUPA app:
app.UseRouting();
app.UseCors("AllowAngularDev");   // dupa UseRouting, inainte de Auth
app.UseAuthentication();
app.UseAuthorization();

Reguli:

  • Ordinea în pipeline: Routing → CORS → Authentication → Authorization
  • Niciodată AllowAnyOrigin() cu API autentificat în producție
  • WithOrigins acceptă mai multe URL-uri: WithOrigins("http://localhost:4200", "http://localhost:8080")

Structura unei componente

@Component({
  selector: 'app-my',                // <app-my></app-my> in template-uri parinte
  templateUrl: './my.component.html', // sau template: '<p>...</p>' inline
  styleUrls: ['./my.component.css']   // sau styles: ['p { color: red; }']
})
export class MyComponent implements OnInit {
  // proprietati = state
  data: string[] = [];

  // DI prin constructor
  constructor(private myService: MyService) {}

  // lifecycle hooks
  ngOnInit(): void { /* dupa ce dependintele sunt injectate */ }
  ngOnDestroy(): void { /* cleanup la distrugere */ }
}

Sintaxă template Angular

Sintaxă Tip Exemplu
{{ value }} interpolation {{ user.name }}
[prop]="value" property binding [disabled]="loading"
(event)="handler()" event binding (click)="save()"
[(ngModel)]="value" two-way binding [(ngModel)]="email"
*ngIf="cond" structural directive *ngIf="!loading"
*ngFor="let x of arr" structural directive *ngFor="let a of articles"
[class.foo]="cond" class binding [class.is-invalid]="hasError"
[ngClass]="{ foo: cond }" multiple class binding
| pipe:arg pipe {{ date | date:'short' }}
| async async pipe {{ user$ | async }}
routerLink="/x" router directive routerLink="/articles"
<router-outlet> placeholder rute

Reactive Forms — pattern

import { FormBuilder, FormGroup, Validators } from '@angular/forms';

myForm: FormGroup;

constructor(private fb: FormBuilder) {
  this.myForm = this.fb.group({
    email: ['', [Validators.required, Validators.email]],
    age: [0, [Validators.required, Validators.min(18)]]
  });
}

get f() { return this.myForm.controls; }   // shortcut

onSubmit() {
  if (this.myForm.invalid) return;
  console.log(this.myForm.value);          // { email: '...', age: 23 }
}
<form [formGroup]="myForm" (ngSubmit)="onSubmit()">
  <input formControlName="email"
         [class.is-invalid]="f['email'].invalid && f['email'].touched">
  <button type="submit" [disabled]="myForm.invalid">Submit</button>
</form>

Validatori built-in: required, email, min, max, minLength, maxLength, pattern, requiredTrue.

Validator custom (la nivel de grup):

this.fb.group({...}, { validators: myValidator });

function myValidator(g: AbstractControl): ValidationErrors | null {
  return g.get('a')?.value === g.get('b')?.value ? null : { mismatch: true };
}

HttpClient + Observable

import { HttpClient } from '@angular/common/http';

constructor(private http: HttpClient) {}

// GET
this.http.get<Article[]>('/api/articles').subscribe({
  next: data => this.articles = data,
  error: err => this.error = err.message
});

// POST cu body
this.http.post<Article>('/api/articles', dto).subscribe(...);

// PUT
this.http.put('/api/articles/5', dto).subscribe(...);

// DELETE
this.http.delete('/api/articles/5').subscribe(...);

// Cu query params
this.http.get<Article[]>('/api/articles', { params: { page: '1' } }).subscribe(...);

Operatori RxJS uzuali (din rxjs/operators):

Operator Ce face
tap(x => ...) side effect (log, save) fără să modifice stream-ul
map(x => ...) transformă valoarea (sincron)
switchMap(x => obs) urmărește un alt observabil; anulează inner-ul precedent
catchError(err => ...) tratează erori
finalize(() => ...) rulează la complete sau error (echivalent finally)
this.http.get(...).pipe(
  tap(data => console.log('received', data)),
  map(data => data.filter(x => x.active)),
  catchError(err => { console.error(err); return of([]); })
).subscribe(...);

BehaviorSubject pentru state share

@Injectable({ providedIn: 'root' })
export class CounterService {
  private subject = new BehaviorSubject<number>(0);
  public count$ = this.subject.asObservable();

  increment() { this.subject.next(this.subject.value + 1); }
}

Consumul în template:

<p>Count: {{ counter.count$ | async }}</p>

BehaviorSubject reține ultima valoare; Subject simplu nu o reține (un consumer nou nu vede valoarea anterioară).

Routing

const routes: Routes = [
  { path: '', redirectTo: '/x', pathMatch: 'full' },
  { path: 'x', component: XComponent },
  { path: 'x/:id', component: XDetailComponent },
  { path: 'protected', component: PComponent, canActivate: [AuthGuard] },
  { path: '**', redirectTo: '/x' }
];

@NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] })
export class AppRoutingModule {}

Navigare programatică:

constructor(private router: Router) {}

this.router.navigate(['/articles', id]);                       // /articles/5
this.router.navigateByUrl('/articles?page=2');
this.router.navigate(['/login'], { queryParams: { returnUrl: '/x' } });

Citire parametri:

constructor(private route: ActivatedRoute) {}

ngOnInit() {
  const id = Number(this.route.snapshot.paramMap.get('id'));   // snapshot - o data
  this.route.paramMap.subscribe(map => /* la fiecare schimbare */);  // reactiv
  const q = this.route.snapshot.queryParams['returnUrl'];
}

HTTP Interceptor — pattern complet

@Injectable()
export class MyInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler) {
    const cloned = req.clone({ setHeaders: { 'X-Foo': 'bar' } });
    return next.handle(cloned).pipe(
      tap(event => /* sau catchError */),
    );
  }
}

// Inregistrare in AppModule.providers
{ provide: HTTP_INTERCEPTORS, useClass: MyInterceptor, multi: true }

Mai multe interceptors: ordinea înregistrării = ordinea aplicării. Toate sunt multi.

Probleme comune

Problema Cauza Fix
CORS error în consolă UseCors lipsește sau e după UseAuthentication recheck Program.cs
net::ERR_CERT_AUTHORITY_INVALID dev cert ne-trustuit dotnet dev-certs https --trust
404 la /api/articles din Angular port greșit în environment.ts verifică Properties/launchSettings.json
401 doar pe POST/PUT lipsește header Authorization verifică interceptor înregistrat în providers
NullInjectorError la consumul unui service service-ul nu e providedIn: 'root' și nu e listat în providers adaugă unul sau celălalt
Form-ul nu submit-ează niciodată lipsă [formGroup] în form bind explicit
*ngIf / *ngFor nu randează BrowserModule lipsă (pe AppModule) sau CommonModule (pe sub-module) adaugă import
JWT decoded nu are claim-uri așteptate atob direct pe base64url folosește decodeJwtPayload cu replace + padding

Convenții de proiect (recomandate)

src/app/
├── core/                    # singletons, guards, interceptors (importate o data)
├── features/                # grupare per-feature (auth, articles, ...)
├── shared/                  # componente reutilizabile, modele, pipes
├── app.module.ts            # root module
├── app-routing.module.ts    # rute root
└── app.component.*          # shell-ul aplicatiei

Reguli:

  • Servicii cu @Injectable({ providedIn: 'root' }) — nu trebuie listate în providers
  • Modelele sunt interface, nu class, dacă nu au logică
  • Constantele de URL în environment.ts (nu hard-coded în servicii)
  • Una și aceeași componentă nu apare în declarations la mai multe module — produce eroare la build

Linkuri rapide