Angular v17+
Commandes CLI, composants, directives, services, routing, formulaires et toute la puissance d'Angular.
Angular CLI est l'outil officiel pour créer, développer, tester et builder des applications Angular.
# Installer Angular CLI globalement
npm install -g @angular/cli
# Créer un nouveau projet
ng new mon-projet
ng new mon-projet --routing --style=css # avec routing et CSS
cd mon-projet
# Lancer le serveur de développement
ng serve
ng serve --open # ouvre le navigateur
ng serve --port 4200
# Build
ng build
ng build --configuration production # build de prod (optimisé)
# Tests
ng test # tests unitaires (Karma)
ng e2e # tests end-to-end
# Lint
ng lint
La commande ng generate (alias ng g) permet de créer rapidement tous les types de fichiers Angular.
| Commande | Alias | Description |
|---|---|---|
ng generate component nom | ng g c nom | Créer un composant |
ng generate service nom | ng g s nom | Créer un service |
ng generate module nom | ng g m nom | Créer un module |
ng generate pipe nom | ng g p nom | Créer un pipe |
ng generate directive nom | ng g d nom | Créer une directive |
ng generate guard nom | ng g g nom | Créer un guard |
ng generate interface nom | ng g i nom | Créer une interface |
ng generate enum nom | ng g e nom | Créer un enum |
ng generate class nom | ng g cl nom | Créer une classe |
ng g c user/user-list # dans un sous-dossier
ng g c user-card --inline-template --inline-style # tout dans le .ts
ng g c nav --skip-tests # sans spec.ts
ng g s auth --skip-tests
ng g m features/blog --routing # module avec routing
Un projet Angular suit une structure conventionnelle. Le dossier src/app/ contient toute la logique applicative.
mon-projet/
├── src/
│ ├── app/
│ │ ├── app.component.ts # Composant racine
│ │ ├── app.component.html
│ │ ├── app.component.css
│ │ ├── app.module.ts # Module principal
│ │ ├── app-routing.module.ts # Routes
│ │ ├── components/ # Composants réutilisables
│ │ ├── services/ # Services
│ │ ├── models/ # Interfaces/types
│ │ └── guards/ # Route guards
│ ├── assets/
│ ├── environments/
│ │ ├── environment.ts # Dev
│ │ └── environment.prod.ts # Prod
│ ├── index.html
│ └── styles.css # Styles globaux
├── angular.json
├── tsconfig.json
└── package.json
Un composant Angular est une classe TypeScript décorée avec @Component. @Input() reçoit des données du parent, @Output() émet des événements vers le parent.
// user-card.component.ts
import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
import { User } from '../models/user.model';
@Component({
selector: 'app-user-card',
templateUrl: './user-card.component.html',
styleUrls: ['./user-card.component.css'],
// Standalone (Angular 14+)
// standalone: true,
// imports: [CommonModule],
})
export class UserCardComponent implements OnInit {
@Input() user!: User; // Données reçues du parent
@Input() showActions = true;
@Output() deleted = new EventEmitter<number>(); // Émet vers le parent
titre: string = '';
ngOnInit(): void {
this.titre = `Profil de ${this.user.nom}`;
}
onDelete(): void {
this.deleted.emit(this.user.id);
}
}
<!-- user-card.component.html -->
<div class="card">
<h2>{{ titre }}</h2>
<p>{{ user.nom }} — {{ user.email }}</p>
<button *ngIf="showActions" (click)="onDelete()">Supprimer</button>
</div>
// Interface User
// models/user.model.ts
export interface User {
id: number;
nom: string;
email: string;
role: 'admin' | 'user';
actif: boolean;
}
Angular propose quatre types de data binding : interpolation {{ }}, property binding [], event binding () et two-way binding [()].
<!-- Interpolation {{ }} -->
<h1>{{ titre }}</h1>
<p>{{ user.nom | uppercase }}</p>
<p>{{ 2 + 2 }}</p>
<p>{{ isLoading ? 'Chargement...' : 'Prêt' }}</p>
<!-- Property binding [ ] -->
<img [src]="user.avatar" [alt]="user.nom" />
<button [disabled]="isLoading">Envoyer</button>
<div [class.active]="isActive"></div>
<div [class]="{'active': isActive, 'error': hasError}"></div>
<div [ngClass]="{'active': isActive, 'bold': isBold}"></div>
<div [style.color]="textColor"></div>
<div [ngStyle]="{'font-size': fontSize + 'px'}"></div>
<!-- Event binding ( ) -->
<button (click)="onClick()">Cliquer</button>
<button (click)="onClick($event)">Avec event</button>
<input (keyup.enter)="onEnter()">
<input (input)="onInput($event)">
<form (ngSubmit)="onSubmit()">
<!-- Two-way binding [( )] — nécessite FormsModule -->
<input [(ngModel)]="nom" />
<!-- Équivalent à : -->
<input [value]="nom" (input)="nom = $event.target.value" />
Les directives structurelles modifient le DOM en ajoutant ou supprimant des éléments. Elles sont préfixées par *.
<!-- *ngIf -->
<div *ngIf="user">{{ user.nom }}</div>
<div *ngIf="isLoading; else content">
<app-spinner></app-spinner>
</div>
<ng-template #content>
<app-user-list [users]="users"></app-user-list>
</ng-template>
<!-- *ngFor -->
<ul>
<li *ngFor="let user of users; let i = index; trackBy: trackById">
{{ i + 1 }}. {{ user.nom }}
</li>
</ul>
<!-- trackBy (optimisation) -->
<!-- dans le composant : -->
<!-- trackById(index: number, user: User) { return user.id; } -->
<!-- *ngSwitch -->
<div [ngSwitch]="statut">
<p *ngSwitchCase="'loading'">Chargement...</p>
<p *ngSwitchCase="'error'">Erreur</p>
<p *ngSwitchCase="'success'">Succès !</p>
<p *ngSwitchDefault>Inconnu</p>
</div>
<!-- ng-container (sans élément DOM) -->
<ng-container *ngIf="user">
<h2>{{ user.nom }}</h2>
<p>{{ user.email }}</p>
</ng-container>
<!-- ng-content (projection de contenu) -->
<!-- Dans le composant card : -->
<div class="card">
<ng-content select="[card-header]"></ng-content>
<ng-content></ng-content>
</div>
<!-- Utilisation : -->
<app-card>
<h2 card-header>Titre</h2>
<p>Contenu projeté</p>
</app-card>
Les services Angular sont des classes singleton injectables. providedIn: 'root' les rend disponibles dans toute l'application sans les déclarer dans un module.
// user.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { User } from '../models/user.model';
@Injectable({
providedIn: 'root', // singleton disponible dans toute l'app
})
export class UserService {
private apiUrl = 'https://api.exemple.fr';
constructor(private http: HttpClient) {}
getAll(): Observable<User[]> {
return this.http.get<User[]>(`${this.apiUrl}/users`);
}
getById(id: number): Observable<User> {
return this.http.get<User>(`${this.apiUrl}/users/${id}`);
}
create(user: Partial<User>): Observable<User> {
return this.http.post<User>(`${this.apiUrl}/users`, user);
}
update(id: number, user: Partial<User>): Observable<User> {
return this.http.put<User>(`${this.apiUrl}/users/${id}`, user);
}
delete(id: number): Observable<void> {
return this.http.delete<void>(`${this.apiUrl}/users/${id}`);
}
}
// Injection dans un composant
@Component({ ... })
export class UsersComponent implements OnInit {
users: User[] = [];
constructor(private userService: UserService) {}
ngOnInit(): void {
this.userService.getAll().subscribe({
next: (users) => this.users = users,
error: (err) => console.error(err),
});
}
deleteUser(id: number): void {
this.userService.delete(id).subscribe(() => {
this.users = this.users.filter(u => u.id !== id);
});
}
}
HttpClient retourne des Observable. Les opérateurs RxJS comme map, tap et catchError permettent de transformer et gérer les flux de données.
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { Observable, catchError, map, tap, throwError } from 'rxjs';
// GET avec paramètres
getUsers(page: number, limit: number): Observable<User[]> {
const params = new HttpParams()
.set('page', page)
.set('limit', limit);
return this.http.get<User[]>('/api/users', { params });
}
// POST avec headers
createUser(user: Partial<User>): Observable<User> {
const headers = new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.authToken}`,
});
return this.http.post<User>('/api/users', user, { headers });
}
// Opérateurs RxJS essentiels
getUsers(): Observable<User[]> {
return this.http.get<User[]>('/api/users').pipe(
map(users => users.filter(u => u.actif)), // transformer
tap(users => console.log('Reçu:', users)), // effet de bord
catchError(err => { // gestion d'erreur
console.error('Erreur API:', err);
return throwError(() => err);
}),
);
}
Le routeur Angular associe des chemins URL à des composants. Le lazy loading permet de charger les modules à la demande pour optimiser les performances.
// app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AuthGuard } from './guards/auth.guard';
const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'users', component: UsersComponent },
{ path: 'users/:id', component: UserDetailComponent },
{ path: 'admin', component: AdminComponent, canActivate: [AuthGuard] },
// Lazy loading
{
path: 'blog',
loadChildren: () => import('./blog/blog.module').then(m => m.BlogModule),
},
{ path: '**', component: NotFoundComponent }, // 404
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule {}
<!-- Navigation -->
<nav>
<a routerLink="/">Accueil</a>
<a routerLink="/users" routerLinkActive="active">Utilisateurs</a>
<a [routerLink]="['/users', user.id]">{{ user.nom }}</a>
</nav>
<!-- Vue principale -->
<router-outlet></router-outlet>
// Récupérer les paramètres de route
import { ActivatedRoute, Router } from '@angular/router';
@Component({ ... })
export class UserDetailComponent implements OnInit {
constructor(
private route: ActivatedRoute,
private router: Router,
) {}
ngOnInit(): void {
// Paramètre de route (:id)
const id = this.route.snapshot.paramMap.get('id');
// Observable (si l'id peut changer sans rechargement)
this.route.params.subscribe(params => {
this.loadUser(params['id']);
});
// Query params (?page=2)
const page = this.route.snapshot.queryParamMap.get('page');
}
goBack(): void {
this.router.navigate(['/users']);
}
goToUser(id: number): void {
this.router.navigate(['/users', id], { queryParams: { tab: 'profil' } });
}
}
Les pipes transforment les données dans les templates. Le pipe async souscrit automatiquement aux Observables et gère le désabonnement.
<!-- Pipes intégrés -->
{{ nom | uppercase }} <!-- ALICE -->
{{ nom | lowercase }} <!-- alice -->
{{ nom | titlecase }} <!-- Alice Dupont -->
{{ texte | slice:0:50 }} <!-- 50 premiers caractères -->
{{ texte | slice:0:50 }}...
{{ prix | currency:'EUR':'symbol':'1.2-2' }} <!-- 29,99 € -->
{{ prix | currency:'USD' }} <!-- $29.99 -->
{{ nombre | number:'1.0-2' }} <!-- 1 234.56 -->
{{ tauxTva | percent:'1.1-1' }} <!-- 20.0% -->
{{ date | date:'dd/MM/yyyy' }} <!-- 15/03/2024 -->
{{ date | date:'fullDate' }} <!-- vendredi 15 mars 2024 -->
{{ date | date:'medium' }} <!-- 15 mars 2024, 14:30:00 -->
{{ date | date:'shortTime' }} <!-- 14:30 -->
{{ objet | json }} <!-- pour déboguer -->
{{ liste | keyvalue }} <!-- itérer sur un objet -->
{{ observable$ | async }} <!-- souscription automatique -->
Créez vos propres pipes avec ng g p nom. Un pipe implémente PipeTransform et définit la méthode transform().
// Pipe personnalisé
// ng g p truncate
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({ name: 'truncate' })
export class TruncatePipe implements PipeTransform {
transform(value: string, limit = 100, suffix = '...'): string {
if (value.length <= limit) return value;
return value.substring(0, limit) + suffix;
}
}
// Usage : {{ texte | truncate:50:'…' }}
Les formulaires template-driven utilisent ngModel pour le two-way binding. La logique de validation est définie directement dans le template HTML.
// Nécessite FormsModule dans app.module.ts
// imports: [FormsModule]
@Component({ ... })
export class LoginComponent {
form = { email: '', password: '' };
loading = false;
onSubmit(ngForm: NgForm): void {
if (ngForm.invalid) return;
console.log(this.form);
}
}
<form #loginForm="ngForm" (ngSubmit)="onSubmit(loginForm)">
<label for="email">Email</label>
<input
id="email"
type="email"
name="email"
[(ngModel)]="form.email"
required
email
#emailRef="ngModel"
/>
<span *ngIf="emailRef.invalid && emailRef.touched">Email invalide</span>
<label for="password">Mot de passe</label>
<input
id="password"
type="password"
name="password"
[(ngModel)]="form.password"
required
minlength="8"
#pwdRef="ngModel"
/>
<span *ngIf="pwdRef.errors?.['minlength'] && pwdRef.touched">
8 caractères minimum
</span>
<button type="submit" [disabled]="loginForm.invalid || loading">
Connexion
</button>
</form>
Les formulaires réactifs définissent la structure et la validation dans la classe TypeScript. Ils sont plus puissants et testables que les formulaires template-driven.
// Nécessite ReactiveFormsModule dans app.module.ts
import { FormBuilder, FormGroup, Validators, AbstractControl } from '@angular/forms';
@Component({ ... })
export class RegisterComponent implements OnInit {
form!: FormGroup;
constructor(private fb: FormBuilder) {}
ngOnInit(): void {
this.form = this.fb.group({
nom: ['', [Validators.required, Validators.minLength(2)]],
email: ['', [Validators.required, Validators.email]],
password: ['', [Validators.required, Validators.minLength(8)]],
confirm: ['', Validators.required],
}, { validators: this.passwordMatch });
}
// Validateur personnalisé
passwordMatch(control: AbstractControl) {
const pass = control.get('password')?.value;
const conf = control.get('confirm')?.value;
return pass === conf ? null : { mismatch: true };
}
get nom() { return this.form.get('nom'); }
get email() { return this.form.get('email'); }
onSubmit(): void {
if (this.form.invalid) { this.form.markAllAsTouched(); return; }
console.log(this.form.value);
}
}
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<label>Nom</label>
<input formControlName="nom" />
<div *ngIf="nom?.invalid && nom?.touched">
<span *ngIf="nom?.errors?.['required']">Nom requis</span>
<span *ngIf="nom?.errors?.['minlength']">2 caractères minimum</span>
</div>
<label>Email</label>
<input formControlName="email" type="email" />
<span *ngIf="email?.invalid && email?.touched">Email invalide</span>
<button type="submit" [disabled]="form.invalid">S'inscrire</button>
</form>
Angular appelle des méthodes spéciales à chaque étape du cycle de vie d'un composant. Il est crucial de se désabonner dans ngOnDestroy pour éviter les fuites mémoire.
| Hook | Moment d'appel |
|---|---|
ngOnChanges | Avant ngOnInit, puis à chaque changement d'@Input |
ngOnInit | Une fois après le premier ngOnChanges |
ngDoCheck | Après chaque vérification de changement |
ngAfterContentInit | Après l'insertion du contenu (ng-content) |
ngAfterContentChecked | Après chaque vérification du contenu |
ngAfterViewInit | Après l'initialisation de la vue du composant |
ngAfterViewChecked | Après chaque vérification de la vue |
ngOnDestroy | Juste avant la destruction du composant |
import { Component, OnInit, OnChanges, OnDestroy, Input, SimpleChanges } from '@angular/core';
import { Subscription } from 'rxjs';
@Component({ ... })
export class ExempleComponent implements OnInit, OnChanges, OnDestroy {
@Input() userId!: number;
private subscription!: Subscription;
ngOnChanges(changes: SimpleChanges): void {
if (changes['userId']) {
console.log('userId changé:', changes['userId'].currentValue);
this.loadUser();
}
}
ngOnInit(): void {
this.subscription = this.userService.getAll()
.subscribe(users => this.users = users);
}
ngOnDestroy(): void {
// TOUJOURS se désabonner pour éviter les memory leaks
this.subscription?.unsubscribe();
}
}
Les modules Angular regroupent des composants, directives et pipes liés. Chaque déclaration (composant, directive, pipe) doit appartenir à exactement un module.
// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
@NgModule({
declarations: [ // Composants, directives, pipes de CE module
AppComponent,
UserCardComponent,
UserListComponent,
TruncatePipe,
],
imports: [ // Modules dont CE module dépend
BrowserModule,
HttpClientModule,
FormsModule,
ReactiveFormsModule,
AppRoutingModule,
],
providers: [ // Services (préférer providedIn: 'root')
UserService,
],
bootstrap: [AppComponent], // Composant racine (app.module.ts seulement)
})
export class AppModule {}
Aucun résultat pour votre recherche.