un exemple clair en Angular la différence entre :
-
Un standalone component (sans NgModule)
-
Un component classique basé sur un NgModule
🔹 1. Standalone Component (Angular ≥ 14)
Depuis Angular 14, on peut créer des composants indépendants (sans être déclarés dans un NgModule).
Cela simplifie la structure et rend l’application plus modulaire.
👉 Exemple :
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-hello-standalone',
standalone: true, // ✅ Indique que le composant est autonome
imports: [CommonModule], // ✅ On importe directement ce dont on a besoin
template: `
<h2>Bonjour depuis un standalone component !</h2>
<p>Date actuelle : {{ today | date }}</p>
`
})
export class HelloStandaloneComponent {
today = new Date();
}
➡️ Ici, pas besoin de le déclarer dans un NgModule.
On peut l’utiliser directement dans le main.ts ou l’injecter dans un autre composant standalone.
main.ts (Angular 16/17/18)
import { bootstrapApplication } from '@angular/platform-browser';
import { HelloStandaloneComponent } from './app/hello-standalone.component';
bootstrapApplication(HelloStandaloneComponent);
🔹 2. Composant classique basé sur NgModule (avant Angular 14)
Avant, chaque composant devait être déclaré dans un NgModule (ex: AppModule).
👉 Exemple équivalent :
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { CommonModule } from '@angular/common';
import { HelloNgModuleComponent } from './hello-ngmodule.component';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent,
HelloNgModuleComponent // ✅ Obligatoire dans un module
],
imports: [
BrowserModule,
CommonModule
],
bootstrap: [AppComponent]
})
export class AppModule {}
🔹 Comparaison rapide
| Critère | Standalone Component ✅ | NgModule classique |
|---|---|---|
| Déclaration | standalone: true dans le décorateur |
Dans declarations du NgModule |
| Imports nécessaires | Directement dans le composant | Dans imports du NgModule |
| Bootstrap | bootstrapApplication |
bootstrapModule |
| Modularité | Plus simple, granulaire | Plus lourd, centralisé |
| Compatibilité | Angular ≥ 14 | Tous (y compris legacy) |
✅ En pratique aujourd’hui (Angular 17/18) :
-
On peut utiliser standalone components pour de nouvelles applis → structure plus simple.
-
Mais si un projet existe déjà en NgModules, ce n’est pas obligatoire de migrer : Angular supporte les deux approches.
=============================================
🔹 1. @Input()
-
Sert à passer des données d’un parent vers un composant enfant.
-
Le parent peut envoyer n’importe quelle valeur (string, object, array…).
Exemple :
composant enfant :
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-child',
template: `<p>Bonjour, je m'appelle {{ name }} !</p>`
})
export class ChildComponent {
@Input() name!: string; // On reçoit le nom depuis le parent
}
composant parent :
<app-child [name]="'Hung'"></app-child>
Résultat :
Bonjour, je m'appelle Hung !
🔹 2. @Output()
-
Sert à envoyer un événement de l’enfant vers le parent.
-
On utilise
EventEmitter.
Exemple :
composant enfant :
import { Component, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-child',
template: `<button (click)="notifyParent()">Cliquer</button>`
})
export class ChildComponent {
@Output() clicked = new EventEmitter<string>();
notifyParent() {
this.clicked.emit('Le bouton a été cliqué !');
}
}
composant parent :
<app-child (clicked)="onChildClicked($event)"></app-child>
onChildClicked(message: string) {
console.log('Message reçu de l’enfant :', message);
}
🔹 3. *ngFor
-
Sert à itérer sur un tableau et générer dynamiquement des éléments.
Exemple :
@Component({
selector: 'app-list',
template: `
<ul>
<li *ngFor="let item of items; let i = index">
{{ i + 1 }} - {{ item }}
</li>
</ul>
`
})
export class ListComponent {
items = ['Angular', 'TypeScript', 'C#', 'Python'];
}
Résultat :
1 - Angular
2 - TypeScript
3 - C#
4 - Python
🔹 4. *ngIf
-
Sert à afficher ou masquer un élément selon une condition.
Exemple :
@Component({
selector: 'app-condition',
template: `
<p *ngIf="isVisible">Ce texte est visible</p>
<button (click)="toggle()">Afficher / Masquer</button>
`
})
export class ConditionComponent {
isVisible = true;
toggle() {
this.isVisible = !this.isVisible;
}
}
Résultat :
-
Le paragraphe apparaît ou disparaît quand on clique sur le bouton.
🔹 5. Exemple combiné
On peut combiner @Input(), @Output(), *ngFor et *ngIf :
<div *ngFor="let user of users">
<app-child
[name]="user.name"
(clicked)="onChildClicked($event)">
</app-child>
<p *ngIf="user.active">Actif ✅</p>
</div>
users = [
{ name: 'Hung', active: true },
{ name: 'Alice', active: false },
];
-
Affiche tous les utilisateurs.
-
Chaque composant enfant reçoit un nom.
-
L’état actif est affiché conditionnellement.
-
Chaque bouton dans l’enfant peut envoyer un événement au parent.
===========================================
🔹 1. Qu’est-ce qu’un service Angular ?
-
Un service est une classe qui contient de la logique réutilisable (ex: appels API, gestion de données, authentification…).
-
On l’injecte ensuite dans un composant via le Dependency Injection (DI).
-
On utilise
@Injectable()pour que Angular sache qu’il peut l’injecter.
🔹 2. Création d’un service avec @Injectable()
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
// Déclarer que ce service est injectable dans toute l'application
@Injectable({
providedIn: 'root'
})
export class UserService {
private apiUrl = 'https://jsonplaceholder.typicode.com/users';
constructor(private http: HttpClient) { }
// Méthode pour récupérer tous les utilisateurs
getUsers(): Observable<any[]> {
return this.http.get<any[]>(this.apiUrl);
}
// Méthode pour récupérer un utilisateur par ID
getUserById(id: number): Observable<any> {
return this.http.get<any>(`${this.apiUrl}/${id}`);
}
}
-
providedIn: 'root'→ le service est singleton et disponible dans toute l’application. -
HttpClientest utilisé pour effectuer des requêtes HTTP.
🔹 3. Utilisation dans un composant
import { Component, OnInit } from '@angular/core';
import { UserService } from './user.service';
@Component({
selector: 'app-user-list',
template: `
<h2>Liste des utilisateurs</h2>
<ul>
<li *ngFor="let user of users">{{ user.name }} ({{ user.email }})</li>
</ul>
`
})
export class UserListComponent implements OnInit {
users: any[] = [];
constructor(private userService: UserService) { }
ngOnInit(): void {
this.userService.getUsers().subscribe({
next: data => this.users = data,
error: err => console.error('Erreur API', err)
});
}
}
-
Le composant injecte le service via le constructeur.
-
Dans
ngOnInit(), on appelle le service et on s’abonne à l’Observable pour récupérer les données. -
On peut ensuite utiliser
*ngForpour afficher la liste.
🔹 4. Points importants
-
Séparer la logique métier du composant → le composant devient simple et lisible.
-
RxJS / Observables → Angular
HttpClientretourne toujours unObservable. -
Gestion des erreurs → utiliser
catchError,tap, ousubscribe({ next, error }). -
Injection dans d’autres services ou composants → on peut partager le même service partout.
✅ Résumé :
-
@Injectable()→ permet à Angular de faire l’injection de dépendances. -
HttpClient→ sert à communiquer avec une API REST. -
Les services rendent le code réutilisable, testable et propre.
============================================
RxJS est au cœur d’Angular, surtout pour gérer les données asynchrones (API, événements, formulaires, timers…).
🔹 1. Qu’est-ce qu’un Observable ?
-
Un Observable émet des valeurs au fil du temps.
-
On s’abonne (
subscribe) pour recevoir ces valeurs. -
Très utilisé pour gérer HTTP, événements utilisateur, timers, WebSocket…
Exemple simple :
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
@Component({
selector: 'app-simple-observable',
template: `<p>{{ message }}</p>`
})
export class SimpleObservableComponent implements OnInit {
message = '';
ngOnInit(): void {
const obs = new Observable<string>(subscriber => {
subscriber.next('Hello');
subscriber.next('Angular');
subscriber.complete();
});
obs.subscribe({
next: value => this.message += value + ' ',
complete: () => console.log('Observable terminé')
});
}
}
Résultat affiché :
Hello Angular
🔹 2. Utiliser pipe et opérateurs (map, filter, etc.)
-
pipe→ permet de chaîner plusieurs opérateurs pour transformer l’Observable. -
map→ transforme chaque valeur. -
filter→ ne garde que certaines valeurs.
Exemple :
import { of } from 'rxjs';
import { map, filter } from 'rxjs/operators';
const numbers$ = of(1, 2, 3, 4, 5);
numbers$
.pipe(
filter(n => n % 2 === 0), // ne garder que les nombres pairs
map(n => n * 10) // multiplier chaque valeur par 10
)
.subscribe(value => console.log(value));
Sortie console :
20
40
🔹 3. switchMap (ou mergeMap) pour appels HTTP dépendants
-
switchMap→ annule l’Observable précédent si un nouveau arrive. -
Très utile pour les requêtes API dépendantes ou la recherche en type-ahead.
Exemple : recherche utilisateur via API
import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';
import { HttpClient } from '@angular/common/http';
import { debounceTime, switchMap } from 'rxjs/operators';
@Component({
selector: 'app-user-search',
template: `
<input [formControl]="searchControl" placeholder="Rechercher un utilisateur">
<ul>
<li *ngFor="let user of users">{{ user.name }}</li>
</ul>
`
})
export class UserSearchComponent {
searchControl = new FormControl('');
users: any[] = [];
constructor(private http: HttpClient) {
this.searchControl.valueChanges
.pipe(
debounceTime(300), // attendre 300ms avant de lancer la requête
switchMap(name => this.http.get<any[]>(`https://jsonplaceholder.typicode.com/users?name_like=${name}`))
)
.subscribe(data => this.users = data);
}
}
-
debounceTime→ évite d’envoyer une requête à chaque frappe. -
switchMap→ annule la requête précédente si l’utilisateur continue à taper.
🔹 4. Résumé des opérateurs RxJS souvent utilisés en Angular
| Opérateur | Usage |
|---|---|
map |
Transformer chaque valeur émise |
filter |
Filtrer les valeurs |
switchMap |
Chainer des Observables dépendants, annule le précédent |
mergeMap |
Chainer plusieurs Observables, garde tous les résultats |
debounceTime |
Retarder l’émission pour éviter trop de flux rapides |
tap |
Faire un effet secondaire (log, debug) sans modifier la valeur |
✅ En pratique Angular :
-
Observables = données HTTP, événements, formulaires réactifs.
-
Pipe + opérateurs = transformer et combiner les données proprement.
-
switchMap = idéal pour les recherches et interactions utilisateur asynchrones.
=================================================
les deux approches des formulaires en Angular : FormsModule (template-driven) et ReactiveFormsModule (reactive forms)
🔹 1. FormsModule (Template-Driven Forms)
-
On définit les formulaires dans le template HTML.
-
Angular crée automatiquement un
NgFormet gère la synchronisation entre le formulaire et le modèle. -
Simple et rapide pour des formulaires petits ou simples.
Exemple :
app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, FormsModule],
bootstrap: [AppComponent]
})
export class AppModule {}
app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<h2>Formulaire Template-Driven</h2>
<form #myForm="ngForm" (ngSubmit)="submit(myForm)">
<label>Nom:</label>
<input type="text" name="name" ngModel required>
<label>Email:</label>
<input type="email" name="email" ngModel required>
<button type="submit">Envoyer</button>
</form>
`
})
export class AppComponent {
submit(form: any) {
console.log('Valeurs du formulaire :', form.value);
}
}
✅ Points clés :
-
Utilisation de
ngModelpour la liaison deux-voies. -
Validation simple avec
required.
🔹 2. ReactiveFormsModule (Reactive Forms)
-
Les formulaires sont définis dans le composant TypeScript.
-
Plus flexible et robuste, idéal pour des formulaires complexes avec validation dynamique et logique métier avancée.
Exemple :
app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReactiveFormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, ReactiveFormsModule],
bootstrap: [AppComponent]
})
export class AppModule {}
app.component.ts
import { Component } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
@Component({
selector: 'app-root',
template: `
<h2>Formulaire Réactif</h2>
<form [formGroup]="myForm" (ngSubmit)="submit()">
<label>Nom:</label>
<input type="text" formControlName="name">
<div *ngIf="myForm.get('name')?.invalid && myForm.get('name')?.touched">
Nom requis
</div>
<label>Email:</label>
<input type="email" formControlName="email">
<div *ngIf="myForm.get('email')?.invalid && myForm.get('email')?.touched">
Email invalide
</div>
<button type="submit" [disabled]="myForm.invalid">Envoyer</button>
</form>
`
})
export class AppComponent {
myForm = new FormGroup({
name: new FormControl('', Validators.required),
email: new FormControl('', [Validators.required, Validators.email])
});
submit() {
console.log('Valeurs du formulaire :', this.myForm.value);
}
}
✅ Points clés :
-
Définition du formulaire et des validations dans TypeScript.
-
Contrôle total sur les valeurs et l’état du formulaire (
valid,touched,dirty). -
Adapté pour formulaires dynamiques ou complexes.
🔹 3. Comparaison rapide
| Caractéristique | FormsModule (template-driven) | ReactiveFormsModule |
|---|---|---|
| Définition | HTML | TypeScript + HTML |
| Validation | Simple, directive required |
Avancée, dynamique |
| Complexité | Petits formulaires | Formulaires complexes |
| Testabilité | Moins pratique | Très facile à tester |
| Dynamisme | Limitée | Très flexible |
=====================================================
le système de routing en Angular avec RouterModule, routerLink et quelques concepts clés
🔹 1. Qu’est-ce que le routing Angular ?
-
Angular utilise
RouterModulepour gérer la navigation entre les composants. -
On définit des routes dans un tableau
{ path, component }. -
Le HTML utilise
routerLinkpour naviguer sans recharger la page.
🔹 2. Configuration du RouterModule
app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule, Routes } from '@angular/router';
import { AppComponent } from './app.component';
import { HomeComponent } from './home.component';
import { AboutComponent } from './about.component';
const routes: Routes = [
{ path: '', component: HomeComponent }, // Page d’accueil
{ path: 'about', component: AboutComponent } // Page À propos
];
@NgModule({
declarations: [AppComponent, HomeComponent, AboutComponent],
imports: [
BrowserModule,
RouterModule.forRoot(routes) // Configuration des routes
],
bootstrap: [AppComponent]
})
export class AppModule {}
🔹 3. Définition des composants
home.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-home',
template: `<h2>Bienvenue sur la page d'accueil</h2>`
})
export class HomeComponent {}
about.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-about',
template: `<h2>À propos de notre application</h2>`
})
export class AboutComponent {}
🔹 4. Navigation avec routerLink
app.component.html
<nav>
<a routerLink="/">Accueil</a> |
<a routerLink="/about">À propos</a>
</nav>
<!-- Affichage du composant correspondant à la route -->
<router-outlet></router-outlet>
-
<router-outlet>→ emplacement où le composant correspondant à la route sera affiché. -
routerLink="/"etrouterLink="/about"→ navigation sans recharger la page.
🔹 5. Routes dynamiques / paramètres
Définition de la route :
{ path: 'user/:id', component: UserComponent }
Récupération du paramètre dans le composant :
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-user',
template: `<p>Utilisateur ID : {{ userId }}</p>`
})
export class UserComponent implements OnInit {
userId!: string;
constructor(private route: ActivatedRoute) {}
ngOnInit(): void {
this.userId = this.route.snapshot.paramMap.get('id')!;
}
}
Lien vers cette route :
<a [routerLink]="['/user', 123]">Voir utilisateur 123</a>
🔹 6. Redirection et route par défaut
const routes: Routes = [
{ path: '', redirectTo: '/home', pathMatch: 'full' }, // redirection
{ path: 'home', component: HomeComponent },
{ path: '**', component: PageNotFoundComponent } // 404
];
-
redirectTo→ redirige vers une autre route. -
**→ correspond à toutes les routes non définies (page 404).
🔹 7. Résumé des concepts clés
| Concept | Usage |
|---|---|
RouterModule.forRoot(routes) |
Déclaration des routes dans l’application principale |
routerLink |
Lien HTML pour naviguer sans recharger la page |
<router-outlet> |
Emplacement d’affichage du composant selon la route |
Paramètres (:id) |
Routes dynamiques, récupérées via ActivatedRoute |
redirectTo / ** |
Redirections et gestion 404 |
============================================
comment faire un peu de testing en Angular, en utilisant TestBed et HttpTestingController, pour tester à la fois les composants et les services qui font des requêtes HTTP.
🔹 1. Test d’un service avec HttpTestingController
-
HttpTestingControllerpermet de simuler et vérifier les requêtes HTTP sans appeler de vraie API. -
Idéal pour tester un service qui utilise
HttpClient.
Exemple : service UserService
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);
}
}
Exemple de test
import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { UserService } from './user.service';
describe('UserService', () => {
let service: UserService;
let httpMock: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [UserService]
});
service = TestBed.inject(UserService);
httpMock = TestBed.inject(HttpTestingController);
});
it('should fetch users', () => {
const dummyUsers = [{ id: 1, name: 'Hung' }, { id: 2, name: 'Alice' }];
service.getUsers().subscribe(users => {
expect(users.length).toBe(2);
expect(users).toEqual(dummyUsers);
});
const req = httpMock.expectOne('https://jsonplaceholder.typicode.com/users');
expect(req.request.method).toBe('GET');
req.flush(dummyUsers); // simulate HTTP response
});
afterEach(() => {
httpMock.verify(); // s'assure qu'il n'y a pas de requêtes en attente
});
});
✅ Ici :
-
HttpClientTestingModuleremplaceHttpClientModulepour les tests. -
expectOnepermet de vérifier que la requête a bien été faite. -
req.flush()simule la réponse HTTP.
🔹 2. Test d’un composant avec TestBed
-
TestBedpermet de créer un module de test Angular pour un composant.
Exemple : UserListComponent
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { UserListComponent } from './user-list.component';
import { UserService } from './user.service';
import { of } from 'rxjs';
describe('UserListComponent', () => {
let component: UserListComponent;
let fixture: ComponentFixture<UserListComponent>;
let userServiceMock: any;
beforeEach(() => {
userServiceMock = { getUsers: jasmine.createSpy('getUsers').and.returnValue(of([{ id:1, name:'Hung' }])) };
TestBed.configureTestingModule({
declarations: [UserListComponent],
providers: [{ provide: UserService, useValue: userServiceMock }]
}).compileComponents();
fixture = TestBed.createComponent(UserListComponent);
component = fixture.componentInstance;
});
it('should display users', () => {
fixture.detectChanges(); // lance ngOnInit
expect(component.users.length).toBe(1);
expect(component.users[0].name).toBe('Hung');
});
});
✅ Ici :
-
On mocke le service pour ne pas faire de vrai HTTP.
-
fixture.detectChanges()déclenchengOnInit. -
On peut tester l’état du composant (
users, affichage, etc.).
🔹 3. Bonnes pratiques de testing Angular
-
Tester le service séparément → logique métier et HTTP isolés.
-
Tester le composant avec mocks → ne dépend pas des vrais services.
-
Utiliser
HttpTestingControllerpour toutes les requêtes HTTP. -
Vérifier le DOM si nécessaire avec
fixture.nativeElement.
Aucun commentaire:
Enregistrer un commentaire