mardi 16 septembre 2025

Angular

un exemple clair en Angular la différence entre :

  1. Un standalone component (sans NgModule)

  2. 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.

  • HttpClient est 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 *ngFor pour afficher la liste.


🔹 4. Points importants

  1. Séparer la logique métier du composant → le composant devient simple et lisible.

  2. RxJS / Observables → Angular HttpClient retourne toujours un Observable.

  3. Gestion des erreurs → utiliser catchError, tap, ou subscribe({ next, error }).

  4. 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

  • switchMapannule 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 NgForm et 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 ngModel pour 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 RouterModule pour gérer la navigation entre les composants.

  • On définit des routes dans un tableau { path, component }.

  • Le HTML utilise routerLink pour 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="/" et routerLink="/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

  • HttpTestingController permet 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 :

  • HttpClientTestingModule remplace HttpClientModule pour les tests.

  • expectOne permet 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

  • TestBed permet 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éclenche ngOnInit.

  • On peut tester l’état du composant (users, affichage, etc.).


🔹 3. Bonnes pratiques de testing Angular

  1. Tester le service séparément → logique métier et HTTP isolés.

  2. Tester le composant avec mocks → ne dépend pas des vrais services.

  3. Utiliser HttpTestingController pour toutes les requêtes HTTP.

  4. Vérifier le DOM si nécessaire avec fixture.nativeElement.



Aucun commentaire:

Enregistrer un commentaire

Angular Lượt xem: