🅰️ Angular ← Retour au cours
🅰️

Maîtriser Angular

Framework TypeScript de Google pour construire des applications web robustes. Composants, Signals, Services, Routing et plus.

10Concepts
30+Exemples
v19Version

🧱 Composants & Décorateurs

Fondamentaux

Un composant Angular est une classe TypeScript décorée par @Component. Chaque composant lie un template, un style et une logique. Depuis Angular 14+, le mode standalone simplifie tout.

Angular Composant standalone Classe + template + styles en un seul fichier
@Component : Décorateur qui définit le sélecteur HTML, le template et les styles.

standalone: true (Angular 14+) supprime le besoin de NgModules. Le composant s'auto-suffit.

@Input() : Reçoit des données du parent.
@Output() : Émet des événements vers le parent via EventEmitter.

Convention : Un fichier par composant, nom en kebab-case, classe en PascalCase.
// user-card.component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-user-card',
  standalone: true,
  template: `
    <div class="card">
      <img [src]="avatar" [alt]="name" />
      <h3>{{ name }}</h3>
      <span class="badge">{{ role }}</span>
      <button (click)="onSelect.emit(name)">Sélectionner</button>
    </div>
  `,
  styles: [`
    .card {
      background: rgba(255,255,255,0.05);
      border-radius: 12px;
      padding: 16px;
    }
  `]
})
export class UserCardComponent {
  @Input() name = '';
  @Input() role = 'Dev';
  @Input() avatar = '';
  @Output() onSelect = new EventEmitter<string>();
}
Angular Input transforms & required Angular 16+ : transform, required, model
required (v16+) : Rend un input obligatoire — erreur de compilation si absent.

transform (v16+) : Transforme automatiquement la valeur reçue.

model() (v17+) : Two-way binding simplifié.
import { Component, Input, numberAttribute, booleanAttribute, model } from '@angular/core';

@Component({
  selector: 'app-price-card',
  standalone: true,
  template: `
    <div [class.highlighted]="highlighted">
      <h3>{{ symbol }}</h3>
      <span>{{ price | currency }}</span>
    </div>
  `
})
export class PriceCardComponent {
  @Input({ required: true }) symbol!: string;
  @Input({ transform: numberAttribute }) price = 0;
  @Input({ transform: booleanAttribute }) highlighted = false;
  value = model(0);
}
Angular Content Projection ng-content, slots nommés, @ContentChild
ng-content : Permet d'injecter du contenu depuis le parent.

select : Slots nommés pour projeter du contenu à des endroits spécifiques.

@ContentChild : Accède à un élément projeté depuis le composant enfant.
@Component({
  selector: 'app-card-layout',
  standalone: true,
  template: `
    <div class="card">
      <header>
        <ng-content select="[card-header]"></ng-content>
      </header>
      <main>
        <ng-content></ng-content>
      </main>
      <footer>
        <ng-content select="[card-footer]"></ng-content>
      </footer>
    </div>
  `
})
export class CardLayoutComponent {}

Signals & État réactif

Fondamentaux

Depuis Angular 16, les Signals offrent une réactivité granulaire sans Zone.js. Plus performant et prévisible que le change detection classique.

Angular signal, computed, effect Primitives de réactivité Angular 16+
signal(val) : Crée un état réactif.

computed() : Valeur dérivée recalculée automatiquement.

effect() : Exécute un side-effect quand un signal change.
import { Component, signal, computed, effect } from '@angular/core';

@Component({
  selector: 'app-counter',
  standalone: true,
  template: `
    <p>Compteur : {{ count() }}</p>
    <p>Double : {{ doubled() }}</p>
    <button (click)="increment()">+1</button>
    <button (click)="count.set(0)">Reset</button>
  `
})
export class CounterComponent {
  count = signal(0);
  doubled = computed(() => this.count() * 2);

  increment() {
    this.count.update(c => c + 1);
  }

  constructor() {
    effect(() => {
      console.log('Count:', this.count());
    });
  }
}
Angular linkedSignal & resource Angular 19 : état dérivé et data fetching
linkedSignal : Signal dérivé modifiable.

resource : Charge des données asynchrones liées à un signal.
import { signal, linkedSignal, resource } from '@angular/core';

const items = signal(['BTC', 'ETH', 'SOL']);

const selected = linkedSignal({
  source: items,
  computation: (list) => list[0]
});

const coinId = signal('bitcoin');

const coinData = resource({
  request: coinId,
  loader: async ({ request: id }) => {
    const res = await fetch(`https://api.coingecko.com/api/v3/coins/${id}`);
    return res.json();
  }
});

📝 Templates & Directives

Fondamentaux

Nouvelle syntaxe @if, @for, @switch depuis Angular 17. Plus lisible que *ngIf et *ngFor.

Angular @if, @for, @switch Control flow moderne Angular 17+
@if / @else : Remplacement de *ngIf.

@for / @empty : Remplacement de *ngFor.

@switch : Plus lisible que l'ancien système de directives.
@if (isLoggedIn()) {
  <h2>Bienvenue {{ userName() }}</h2>
} @else {
  <p>Connectez-vous</p>
}

@for (coin of filteredCoins(); track coin.id) {
  <div class="row">
    <span>{{ coin.name }}</span>
    <span>{{ coin.price | currency:'USD' }}</span>
  </div>
} @empty {
  <p>Aucun résultat</p>
}
Angular @defer & lazy loading Chargement différé de blocs template
@defer : Charge paresseusement un bloc de template.

@placeholder : Affiché avant le chargement.
@loading : Pendant le chargement.
@error : Si le chargement échoue.
@defer (on viewport) {
  <app-heavy-chart [data]="chartData()" />
} @placeholder {
  <div class="skeleton">Graphique...</div>
} @loading (minimum 300ms) {
  <app-spinner />
} @error {
  <p>Erreur de chargement</p>
}

🔧 Services & Dependency Injection

Architecture

Les services encapsulent la logique métier et l'état partagé. Angular les injecte automatiquement via son système de DI.

Angular @Injectable & inject() Créer et consommer des services
@Injectable : Décore une classe comme service injectable.

providedIn: 'root' : Singleton global.

inject() : Méthode moderne pour récupérer un service.
import { Injectable, inject, signal } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable({ providedIn: 'root' })
export class CryptoService {
  private http = inject(HttpClient);

  coins = signal([]);
  loading = signal(false);

  fetchCoins() {
    this.loading.set(true);
    this.http.get('/api/coins').subscribe({
      next: (data) => {
        this.coins.set(data);
        this.loading.set(false);
      },
      error: (err) => {
        console.error(err);
        this.loading.set(false);
      }
    });
  }
}
Angular Injection hiérarchique Scopes : root, component, module
Root : Singleton global.

Component : Nouvelle instance par composant.

InjectionToken : Pour injecter des valeurs non-classes.
import { InjectionToken, inject } from '@angular/core';

export const API_URL = new InjectionToken('API_URL');

bootstrapApplication(AppComponent, {
  providers: [
    { provide: API_URL, useValue: 'https://api.example.com' }
  ]
});

@Component({
  providers: [CartService]
})
export class ShopComponent {
  private cart = inject(CartService);
  private apiUrl = inject(API_URL);
}

🛤 Routing & Navigation

Architecture

Routeur intégré avec lazy loading, guards fonctionnels et paramètres dynamiques.

Angular Routes & lazy loading loadComponent, paramètres, wildcard
loadComponent : Lazy loading par composant.

:id : Paramètre dynamique.

canActivate : Protège l'accès.
export const routes = [
  { path: '', component: HomeComponent },
  {
    path: 'dashboard',
    loadComponent: () =>
      import('./dashboard/dashboard.component')
        .then(m => m.DashboardComponent),
    canActivate: [authGuard]
  },
  {
    path: 'coin/:id',
    loadComponent: () =>
      import('./coin-detail/coin-detail.component')
        .then(m => m.CoinDetailComponent)
  },
  { path: '**', component: NotFoundComponent }
];
Angular Paramètres & resolvers Lire les params, précharger les données
withComponentInputBinding : Lie automatiquement les paramètres de route aux @Input().

Resolver : Précharge les données avant d'afficher la route.
provideRouter(routes, withComponentInputBinding());

@Component({})
export class CoinDetailComponent {
  @Input() id!: string;
}

export const coinResolver = (route) => {
  return inject(CryptoService).fetchCoin(route.params['id']);
};

📋 Formulaires

Architecture

Deux approches : Template-driven et Reactive Forms.

Angular Reactive Forms FormGroup, FormControl, validateurs
FormGroup : Groupe de contrôles liés.

Validators : Validation intégrée.

Typed Forms : Les formulaires sont typés.
import { FormGroup, FormControl, Validators, ReactiveFormsModule } from '@angular/forms';

@Component({
  standalone: true,
  imports: [ReactiveFormsModule],
  template: `
    <form [formGroup]="form" (ngSubmit)="onSubmit()">
      <input formControlName="email" placeholder="Email" />
      <input formControlName="amount" type="number" />
      <button [disabled]="form.invalid">Envoyer</button>
    </form>
  `
})
export class TransferComponent {
  form = new FormGroup({
    email: new FormControl('', [Validators.required, Validators.email]),
    amount: new FormControl(0, [Validators.required, Validators.min(1)])
  });

  onSubmit() {
    const { email, amount } = this.form.value;
  }
}
Angular Validateurs custom & async Validation métier et côté serveur
Validateur custom : Retourne null ou un objet d'erreur.

Validateur async : Retourne un Observable ou une Promise.
function strongPassword(control) {
  const val = control.value;
  const hasUpper = /[A-Z]/.test(val);
  const hasNumber = /[0-9]/.test(val);
  if (hasUpper && hasNumber) return null;
  return { weakPassword: true };
}

🔄 RxJS & Observables

Avancé

RxJS gère les flux de données asynchrones dans Angular.

RxJS Opérateurs essentiels map, switchMap, debounceTime, combineLatest
switchMap : Annule la requête précédente quand une nouvelle arrive.

debounceTime : Attend X ms avant d'émettre.

combineLatest : Combine plusieurs flux.
results$ = this.searchControl.valueChanges.pipe(
  debounceTime(300),
  distinctUntilChanged(),
  switchMap(query => this.http.get(`/api/search?q=${query}`))
);
RxJS toSignal & toObservable Pont entre Signals et Observables
toSignal() : Convertit un Observable en Signal.

toObservable() : Convertit un Signal en Observable.
import { toSignal, toObservable } from '@angular/core/rxjs-interop';

🔍 Pipes

Avancé

Les pipes transforment les données dans le template.

Angular Pipes intégrés date, currency, uppercase, async, json
| date : Formate une date.
| currency : Affiche un montant en devise.
| async : Souscrit à un Observable automatiquement.
{{ today | date:'dd/MM/yyyy' }}
{{ 95000 | currency:'USD':'symbol':'1.0-0' }}
Angular Pipe custom Créer ses propres transformations
@Pipe : Enregistre le pipe avec un nom.

pure : Recalcule seulement si l'input change.
@Pipe({ name: 'shortNumber', standalone: true })
export class ShortNumberPipe implements PipeTransform {
  transform(value: number): string {
    if (value >= 1000000) return (value / 1000000).toFixed(1) + 'M';
    if (value >= 1000) return (value / 1000).toFixed(1) + 'K';
    return value.toString();
  }
}

Lifecycle & Hooks

Avancé

Hooks du cycle de vie des composants et API modernes Angular.

Angular Hooks classiques ngOnInit, ngOnChanges, ngOnDestroy
ngOnInit : Initialisation.
ngOnChanges : Quand un input change.
ngOnDestroy : Nettoyage.
ngOnInit() {
  this.loadChart(this.coinId);
}

ngOnDestroy() {
  this.sub?.unsubscribe();
  this.chart?.destroy();
}
Angular DestroyRef & afterRender API modernes Angular 16+/17+
DestroyRef : Enregistre des callbacks de nettoyage.

afterNextRender : Exécute du code après le prochain render.
import { DestroyRef, afterNextRender, inject } from '@angular/core';

@Component({})
export class ModernComponent {
  private destroyRef = inject(DestroyRef);

  constructor() {
    afterNextRender(() => {
      console.log('DOM prêt');
    });
  }
}

🧪 Testing

Avancé

Angular intègre un environnement de test avec TestBed.

Angular TestBed & unit tests Composants, services, mocks
TestBed : Configure un module de test.

ComponentFixture : Permet d'interagir avec le DOM du composant.
describe('CoinListComponent', () => {
  it('should display coins', () => {
    expect(true).toBe(true);
  });
});