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 WithOriginsacceptă 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 înproviders - Modelele sunt
interface, nuclass, dacă nu au logică - Constantele de URL în
environment.ts(nu hard-coded în servicii) - Una și aceeași componentă nu apare în
declarationsla mai multe module — produce eroare la build