Le framework Angular
Angular : Le Framework TypeScript pour construire des applications web
Objectifs du cours
- Comprendre l'architecture Angular et le modèle MVC/MVVM
- Maîtriser les composants et le templating Angular
- Implémenter le data binding et la gestion d'événements
- Créer et utiliser des services pour la logique métier
- Configurer le routage pour les applications complexes
- Découvrir l'écosystème Angular et ses outils
Introduction à Angular
Historique et fondamentaux
- Origine: Développé par Google, successeur d'AngularJS (2010)
- Réécriture complète: Angular 2+ (2016) est une refonte totale d'AngularJS
- Langage: TypeScript par défaut (JavaScript également supporté)
- Version actuelle: Angular 17+ avec mises à jour semestrielles
- Architecture: Framework complet orienté applications d'entreprise

Positionnement sur le marché
Angular se positionne comme un framework complet et opinionné, idéal pour les applications d'entreprise de grande envergure.
Comparaison avec d'autres frameworks:
- Angular: Framework complet, TypeScript, structure imposée
- React: Bibliothèque, JavaScript/TypeScript, flexibilité maximale
- Vue.js: Framework progressif, courbe d'apprentissage douce
Adoption par l'industrie
Angular est massivement adopté dans l'écosystème entreprise :
- Google (Gmail, Google Cloud Console)
- Microsoft (Office 365, Azure Portal)
- Samsung (interface SmartTV)
- Deutsche Bank, ING, Santander
Ressources essentielles
Documentation officielle
- Portail principal: angular.dev
- Guide officiel: angular.dev/guide
- Tutoriel Tour of Heroes: angular.dev/tutorial
- CLI Documentation: angular.dev/cli
Architecture Angular
Concepts fondamentaux
Angular suit une architecture modulaire basée sur plusieurs concepts clés :

1. Modules (NgModule)
Les modules organisent l'application en blocs fonctionnels cohérents.
// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent], // Composants, directives, pipes
imports: [BrowserModule], // Autres modules
providers: [], // Services
bootstrap: [AppComponent] // Composant racine
})
export class AppModule { }
2. Composants
Les composants contrôlent une portion de l'écran (la vue).
// hello.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-hello',
template: `
<h1>{{title}}</h1>
<input [(ngModel)]="name" placeholder="Votre nom">
<p>Bonjour {{name}} !</p>
`,
styles: [`
h1 { color: #369; }
input { margin: 10px 0; padding: 5px; }
`]
})
export class HelloComponent {
title = 'Application Angular';
name = '';
}
3. Services et injection de dépendances
Les services contiennent la logique métier réutilisable.
// user.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class UserService {
private apiUrl = 'https://jsonplaceholder.typicode.com/users';
constructor(private http: HttpClient) { }
getUsers(): Observable<any[]> {
return this.http.get<any[]>(this.apiUrl);
}
getUserById(id: number): Observable<any> {
return this.http.get<any>(`${this.apiUrl}/${id}`);
}
}
4. Directives
Les directives étendent le comportement des éléments HTML.
// highlight.directive.ts
import { Directive, ElementRef, HostListener, Input } from '@angular/core';
@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
@Input() appHighlight = '';
constructor(private el: ElementRef) { }
@HostListener('mouseenter') onMouseEnter() {
this.highlight(this.appHighlight || 'yellow');
}
@HostListener('mouseleave') onMouseLeave() {
this.highlight('');
}
private highlight(color: string) {
this.el.nativeElement.style.backgroundColor = color;
}
}
Premiers pas avec Angular
Installation et setup
# Installation du CLI Angular
npm install -g @angular/cli
# Création d'un nouveau projet
ng new my-angular-app
cd my-angular-app
# Lancement du serveur de développement
ng serve
Première application : Counter App
// counter.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-counter',
template: `
<div class="counter-container">
<h2>Compteur : {{count}}</h2>
<button (click)="increment()" [disabled]="count >= 10">+</button>
<button (click)="decrement()" [disabled]="count <= 0">-</button>
<button (click)="reset()">Reset</button>
<div class="status" [ngClass]="getStatusClass()">
{{getStatusMessage()}}
</div>
</div>
`,
styles: [`
.counter-container {
text-align: center;
padding: 20px;
border: 2px solid #ddd;
border-radius: 10px;
max-width: 300px;
margin: 20px auto;
}
button {
margin: 0 5px;
padding: 10px 15px;
font-size: 16px;
border: none;
border-radius: 5px;
cursor: pointer;
background-color: #007bff;
color: white;
}
button:disabled {
background-color: #6c757d;
cursor: not-allowed;
}
.status {
margin-top: 15px;
padding: 10px;
border-radius: 5px;
font-weight: bold;
}
.low { background-color: #f8d7da; color: #721c24; }
.medium { background-color: #fff3cd; color: #856404; }
.high { background-color: #d4edda; color: #155724; }
`]
})
export class CounterComponent {
count = 0;
increment(): void {
if (this.count < 10) {
this.count++;
}
}
decrement(): void {
if (this.count > 0) {
this.count--;
}
}
reset(): void {
this.count = 0;
}
getStatusMessage(): string {
if (this.count <= 3) return 'Niveau bas';
if (this.count <= 7) return 'Niveau moyen';
return 'Niveau élevé';
}
getStatusClass(): string {
if (this.count <= 3) return 'low';
if (this.count <= 7) return 'medium';
return 'high';
}
}
Data Binding et templating
Angular propose quatre types de data binding :
1. Interpolation {{}}
<h1>{{title}}</h1>
<p>Utilisateur : {{user.name | uppercase}}</p>
<span>{{2 + 2}}</span>
2. Property Binding [property]
<img [src]="imageUrl" [alt]="imageAlt">
<button [disabled]="isDisabled">Cliquez-moi</button>
<div [className]="cssClass">Contenu stylé</div>
3. Event Binding (event)
<button (click)="onClick($event)">Clic</button>
<input (keyup)="onKeyUp($event)" (blur)="onBlur()">
<form (ngSubmit)="onSubmit()">...</form>
4. Two-way Binding [(ngModel)]
<input [(ngModel)]="username" placeholder="Nom d'utilisateur">
<p>Bonjour {{username}}</p>
Exemple complet : Formulaire de contact
// contact-form.component.ts
import { Component } from '@angular/core';
interface Contact {
name: string;
email: string;
message: string;
}
@Component({
selector: 'app-contact-form',
template: `
<div class="form-container">
<h2>Formulaire de Contact</h2>
<form (ngSubmit)="onSubmit()" #contactForm="ngForm">
<div class="form-group">
<label for="name">Nom :</label>
<input
type="text"
id="name"
[(ngModel)]="contact.name"
name="name"
required
#nameInput="ngModel"
[class.error]="nameInput.invalid && nameInput.touched">
<div *ngIf="nameInput.invalid && nameInput.touched" class="error-message">
Le nom est requis
</div>
</div>
<div class="form-group">
<label for="email">Email :</label>
<input
type="email"
id="email"
[(ngModel)]="contact.email"
name="email"
required
email
#emailInput="ngModel"
[class.error]="emailInput.invalid && emailInput.touched">
<div *ngIf="emailInput.invalid && emailInput.touched" class="error-message">
<span *ngIf="emailInput.errors?.['required']">L'email est requis</span>
<span *ngIf="emailInput.errors?.['email']">Format d'email invalide</span>
</div>
</div>
<div class="form-group">
<label for="message">Message :</label>
<textarea
id="message"
[(ngModel)]="contact.message"
name="message"
required
rows="4"
#messageInput="ngModel"
[class.error]="messageInput.invalid && messageInput.touched">
</textarea>
<div *ngIf="messageInput.invalid && messageInput.touched" class="error-message">
Le message est requis
</div>
</div>
<button
type="submit"
[disabled]="contactForm.invalid"
class="submit-btn">
Envoyer
</button>
</form>
<div *ngIf="isSubmitted" class="success-message">
Merci {{contact.name}}, votre message a été envoyé !
</div>
</div>
`,
styles: [`
.form-container {
max-width: 500px;
margin: 20px auto;
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input, textarea {
width: 100%;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 14px;
}
input.error, textarea.error {
border-color: #dc3545;
}
.error-message {
color: #dc3545;
font-size: 12px;
margin-top: 5px;
}
.submit-btn {
background-color: #007bff;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
.submit-btn:disabled {
background-color: #6c757d;
cursor: not-allowed;
}
.success-message {
margin-top: 20px;
padding: 10px;
background-color: #d4edda;
color: #155724;
border-radius: 4px;
}
`]
})
export class ContactFormComponent {
contact: Contact = {
name: '',
email: '',
message: ''
};
isSubmitted = false;
onSubmit(): void {
console.log('Formulaire soumis:', this.contact);
this.isSubmitted = true;
// Reset après 3 secondes
setTimeout(() => {
this.isSubmitted = false;
this.contact = { name: '', email: '', message: '' };
}, 3000);
}
}
Directives structurelles
*ngIf - Affichage conditionnel
<div *ngIf="isLoggedIn">Bienvenue, utilisateur connecté !</div>
<div *ngIf="!isLoggedIn">Veuillez vous connecter</div>
<!-- Avec else -->
<div *ngIf="isLoggedIn; else loginTemplate">
Contenu pour utilisateur connecté
</div>
<ng-template #loginTemplate>
<div>Veuillez vous connecter</div>
</ng-template>
*ngFor - Rendu de listes
<ul>
<li *ngFor="let item of items; let i = index; let first = first; let last = last"
[class.first]="first"
[class.last]="last">
{{i + 1}}. {{item.name}}
</li>
</ul>
*ngSwitch - Conditions multiples
<div [ngSwitch]="userRole">
<admin-panel *ngSwitchCase="'admin'"></admin-panel>
<user-dashboard *ngSwitchCase="'user'"></user-dashboard>
<guest-content *ngSwitchDefault></guest-content>
</div>
Services et injection de dépendances
Création d'un service de données
// product.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, BehaviorSubject } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
export interface Product {
id: number;
name: string;
price: number;
category: string;
inStock: boolean;
}
@Injectable({
providedIn: 'root'
})
export class ProductService {
private apiUrl = 'https://fakestoreapi.com/products';
private cartSubject = new BehaviorSubject<Product[]>([]);
public cart$ = this.cartSubject.asObservable();
constructor(private http: HttpClient) { }
getProducts(): Observable<Product[]> {
return this.http.get<any[]>(this.apiUrl).pipe(
map(products => products.map(p => ({
id: p.id,
name: p.title,
price: p.price,
category: p.category,
inStock: true
}))),
catchError(error => {
console.error('Erreur lors du chargement des produits:', error);
return [];
})
);
}
addToCart(product: Product): void {
const currentCart = this.cartSubject.value;
const existingProduct = currentCart.find(p => p.id === product.id);
if (!existingProduct) {
this.cartSubject.next([...currentCart, product]);
}
}
removeFromCart(productId: number): void {
const currentCart = this.cartSubject.value;
const updatedCart = currentCart.filter(p => p.id !== productId);
this.cartSubject.next(updatedCart);
}
getCartTotal(): Observable<number> {
return this.cart$.pipe(
map(products => products.reduce((total, product) => total + product.price, 0))
);
}
}
Utilisation du service dans un composant
// product-list.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { ProductService, Product } from './product.service';
@Component({
selector: 'app-product-list',
template: `
<div class="product-container">
<h2>Liste des Produits</h2>
<div class="cart-info">
<p>Panier : {{cartTotal | currency:'EUR':'symbol':'1.2-2'}}</p>
<p>Articles : {{cartItems.length}}</p>
</div>
<div class="filters">
<select [(ngModel)]="selectedCategory" (change)="filterProducts()">
<option value="">Toutes les catégories</option>
<option *ngFor="let category of categories" [value]="category">
{{category | titlecase}}
</option>
</select>
</div>
<div class="product-grid">
<div *ngFor="let product of filteredProducts" class="product-card">
<h3>{{product.name}}</h3>
<p class="price">{{product.price | currency:'EUR':'symbol':'1.2-2'}}</p>
<p class="category">{{product.category | titlecase}}</p>
<button
(click)="addToCart(product)"
[disabled]="isInCart(product.id)"
class="add-btn">
{{isInCart(product.id) ? 'Dans le panier' : 'Ajouter'}}
</button>
</div>
</div>
<div *ngIf="loading" class="loading">Chargement...</div>
<div *ngIf="error" class="error">{{error}}</div>
</div>
`,
styles: [`
.product-container {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
.cart-info {
background-color: #f8f9fa;
padding: 15px;
border-radius: 5px;
margin-bottom: 20px;
}
.filters {
margin-bottom: 20px;
}
.filters select {
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
.product-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
}
.product-card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 15px;
text-align: center;
transition: transform 0.2s;
}
.product-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.price {
font-size: 18px;
font-weight: bold;
color: #007bff;
}
.category {
color: #6c757d;
font-size: 14px;
}
.add-btn {
background-color: #28a745;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
}
.add-btn:disabled {
background-color: #6c757d;
cursor: not-allowed;
}
.loading, .error {
text-align: center;
padding: 20px;
}
.error {
color: #dc3545;
}
`]
})
export class ProductListComponent implements OnInit, OnDestroy {
products: Product[] = [];
filteredProducts: Product[] = [];
cartItems: Product[] = [];
cartTotal = 0;
categories: string[] = [];
selectedCategory = '';
loading = false;
error = '';
private destroy$ = new Subject<void>();
constructor(private productService: ProductService) { }
ngOnInit(): void {
this.loadProducts();
this.subscribeToCart();
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
loadProducts(): void {
this.loading = true;
this.productService.getProducts()
.pipe(takeUntil(this.destroy$))
.subscribe({
next: (products) => {
this.products = products;
this.filteredProducts = products;
this.categories = [...new Set(products.map(p => p.category))];
this.loading = false;
},
error: (error) => {
this.error = 'Erreur lors du chargement des produits';
this.loading = false;
}
});
}
subscribeToCart(): void {
this.productService.cart$
.pipe(takeUntil(this.destroy$))
.subscribe(items => {
this.cartItems = items;
});
this.productService.getCartTotal()
.pipe(takeUntil(this.destroy$))
.subscribe(total => {
this.cartTotal = total;
});
}
addToCart(product: Product): void {
this.productService.addToCart(product);
}
isInCart(productId: number): boolean {
return this.cartItems.some(item => item.id === productId);
}
filterProducts(): void {
if (this.selectedCategory) {
this.filteredProducts = this.products.filter(
product => product.category === this.selectedCategory
);
} else {
this.filteredProducts = this.products;
}
}
}
Routage Angular
Configuration du routage
// app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { ProductListComponent } from './products/product-list.component';
import { ProductDetailComponent } from './products/product-detail.component';
import { CartComponent } from './cart/cart.component';
import { NotFoundComponent } from './not-found/not-found.component';
const routes: Routes = [
{ path: '', redirectTo: '/home', pathMatch: 'full' },
{ path: 'home', component: HomeComponent },
{ path: 'products', component: ProductListComponent },
{ path: 'products/:id', component: ProductDetailComponent },
{ path: 'cart', component: CartComponent },
{ path: '404', component: NotFoundComponent },
{ path: '**', redirectTo: '/404' }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
Navigation et paramètres
// product-detail.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ProductService, Product } from '../product.service';
@Component({
selector: 'app-product-detail',
template: `
<div class="product-detail" *ngIf="product">
<button (click)="goBack()" class="back-btn">← Retour</button>
<h1>{{product.name}}</h1>
<div class="product-info">
<p class="price">{{product.price | currency:'EUR':'symbol':'1.2-2'}}</p>
<p class="category">Catégorie : {{product.category | titlecase}}</p>
<p class="stock" [class.in-stock]="product.inStock" [class.out-of-stock]="!product.inStock">
{{product.inStock ? 'En stock' : 'Rupture de stock'}}
</p>
</div>
<button
(click)="addToCart()"
[disabled]="!product.inStock"
class="add-to-cart-btn">
Ajouter au panier
</button>
</div>
<div *ngIf="!product && !loading" class="not-found">
Produit non trouvé
</div>
<div *ngIf="loading" class="loading">
Chargement...
</div>
`
})
export class ProductDetailComponent implements OnInit {
product: Product | null = null;
loading = true;
constructor(
private route: ActivatedRoute,
private router: Router,
private productService: ProductService
) { }
ngOnInit(): void {
const id = Number(this.route.snapshot.paramMap.get('id'));
this.loadProduct(id);
}
loadProduct(id: number): void {
this.productService.getProducts().subscribe(products => {
this.product = products.find(p => p.id === id) || null;
this.loading = false;
});
}
addToCart(): void {
if (this.product) {
this.productService.addToCart(this.product);
// Navigation vers le panier
this.router.navigate(['/cart']);
}
}
goBack(): void {
this.router.navigate(['/products']);
}
}
Pipes et transformations
Pipes intégrés
<!-- Date -->
<p>{{today | date:'dd/MM/yyyy'}}</p>
<!-- Currency -->
<p>{{price | currency:'EUR':'symbol':'1.2-2'}}</p>
<!-- Uppercase/Lowercase -->
<p>{{name | uppercase}}</p>
<!-- JSON (débogage) -->
<pre>{{user | json}}</pre>
<!-- Slice -->
<p>{{longText | slice:0:100}}...</p>
Pipe personnalisé
// truncate.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'truncate'
})
export class TruncatePipe implements PipeTransform {
transform(value: string, limit: number = 50, trail: string = '...'): string {
if (!value) return '';
return value.length > limit
? value.substring(0, limit) + trail
: value;
}
}
<!-- Utilisation -->
<p>{{description | truncate:100:'... (lire plus)'}}</p>
Cycle de vie des composants
import { Component, OnInit, OnDestroy, AfterViewInit } from '@angular/core';
@Component({
selector: 'app-lifecycle-demo',
template: `<div>{{message}}</div>`
})
export class LifecycleDemoComponent implements OnInit, OnDestroy, AfterViewInit {
message = '';
constructor() {
console.log('Constructor: Composant créé');
}
ngOnInit(): void {
console.log('ngOnInit: Initialisation terminée');
this.message = 'Composant initialisé';
// Ici : initialiser les données, abonnements
}
ngAfterViewInit(): void {
console.log('ngAfterViewInit: Vue initialisée');
// Ici : manipulation du DOM, initialisation de bibliothèques tierces
}
ngOnDestroy(): void {
console.log('ngOnDestroy: Composant détruit');
// Ici : nettoyage (désabonnements, timers, etc.)
}
}
Écosystème Angular
Outils officiels
- Angular CLI: Outil de ligne de commande complet
- Angular DevTools: Extension de débogage
- Angular Universal: Rendu côté serveur (SSR)
- Angular Material: Bibliothèque de composants Material Design
- Angular Flex Layout: Système de mise en page responsive
Bibliothèques populaires
- NgRx: Gestion d'état complexe (Redux pattern)
- PrimeNG: Suite de composants UI riches
- Ionic: Applications mobiles hybrides
- Nx: Outils pour monorepos et micro-frontends
Commandes CLI essentielles
# Génération
ng generate component my-component
ng generate service my-service
ng generate module my-module
ng generate directive my-directive
ng generate pipe my-pipe
# Build et déploiement
ng build --prod
ng test
ng e2e
ng lint
# Mise à jour
ng update
ng add @angular/material
Exemple d'application complète : Todo App
Voir le TP du cours
Tests avec Angular
Test unitaire d'un composant
// todo-app.component.spec.ts
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FormsModule } from '@angular/forms';
import { TodoAppComponent } from './todo-app.component';
import { TodoService } from './todo.service';
describe('TodoAppComponent', () => {
let component: TodoAppComponent;
let fixture: ComponentFixture<TodoAppComponent>;
let mockTodoService: jasmine.SpyObj<TodoService>;
beforeEach(async () => {
const spy = jasmine.createSpyObj('TodoService', [
'addTodo', 'toggleTodo', 'deleteTodo', 'updateTodo',
'clearCompleted', 'getCompletedCount', 'getPendingCount'
]);
await TestBed.configureTestingModule({
declarations: [TodoAppComponent],
imports: [FormsModule],
providers: [
{ provide: TodoService, useValue: spy }
]
}).compileComponents();
fixture = TestBed.createComponent(TodoAppComponent);
component = fixture.componentInstance;
mockTodoService = TestBed.inject(TodoService) as jasmine.SpyObj<TodoService>;
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should add a new todo', () => {
component.newTodoTitle = 'Test Todo';
component.addTodo();
expect(mockTodoService.addTodo).toHaveBeenCalledWith('Test Todo');
expect(component.newTodoTitle).toBe('');
});
it('should not add empty todo', () => {
component.newTodoTitle = ' ';
component.addTodo();
expect(mockTodoService.addTodo).not.toHaveBeenCalled();
});
});
Test d'intégration (E2E)
// e2e/todo-app.e2e-spec.ts
import { browser, by, element } from 'protractor';
describe('Todo App E2E', () => {
beforeEach(() => {
browser.get('/todo');
});
it('should add a new todo', async () => {
const input = element(by.css('.todo-input'));
const addButton = element(by.css('.add-btn'));
await input.sendKeys('Learn Angular');
await addButton.click();
const todoItems = element.all(by.css('.todo-item'));
expect(await todoItems.count()).toBe(1);
const todoTitle = element(by.css('.todo-title'));
expect(await todoTitle.getText()).toBe('Learn Angular');
});
it('should mark todo as completed', async () => {
// Ajouter un todo d'abord
const input = element(by.css('.todo-input'));
await input.sendKeys('Test Todo');
await element(by.css('.add-btn')).click();
// Marquer comme terminé
const checkbox = element(by.css('.todo-checkbox'));
await checkbox.click();
const todoItem = element(by.css('.todo-item'));
expect(await todoItem.getAttribute('class')).toContain('completed');
});
});
Conclusion
Angular est un framework puissant et complet qui excelle dans le développement d'applications d'entreprise complexes. Ses points forts incluent :
Avantages d'Angular
- Architecture robuste: Structure claire avec modules, composants, services
- TypeScript natif: Typage fort, meilleure maintenance du code
- Écosystème riche: CLI, DevTools, Material Design, Universal
- Performances: Change detection optimisée, lazy loading, AOT compilation
- Testabilité: Outils de test intégrés (Jasmine, Karma, Protractor)
- Support entreprise: Développé par Google, cycle de vie prévisible
Cas d'usage idéaux
- Applications d'entreprise de grande envergure
- Dashboards et interfaces d'administration
- Applications avec logique métier complexe
- Projets nécessitant une architecture stricte
- Équipes importantes avec besoin de conventions
Prochaines étapes pour approfondir
- Modules avancés: Lazy loading, feature modules
- State Management: NgRx pour les applications complexes
- PWA: Service Workers, Application Shell
- Performance: Bundle analysis, code splitting
- Testing: Tests unitaires et E2E avancés
- Déploiement: CI/CD, optimisations de build
Ressources pour continuer
- Documentation officielle Angular
- Angular Material
- NgRx Documentation
- Angular Blog
- Communauté Angular France
Angular vous donne tous les outils pour construire des applications web modernes, robustes et maintenables. La courbe d'apprentissage peut être plus raide que d'autres frameworks, mais les bénéfices en termes de structure, performance et maintenabilité en valent largement l'investissement !