Angular et WebAssembly : guide complet

Front-end 10/04/2026 22:00:00 AngularForAll.com
Angular Webassembly Rust Wasm-Pack Performance
Angular et WebAssembly : guide complet

Intégrez WebAssembly dans Angular avec Rust et wasm-pack : compilation, communication JS↔Wasm, Web Workers, benchmarks et cas d'usage enterprise.

WebAssembly : les fondamentaux

WebAssembly (Wasm) est un format d'instructions binaire conçu pour s'exécuter dans les navigateurs à des vitesses proches du natif. Contrairement à JavaScript, Wasm n'est pas interprété — il est compilé en bytecode que la machine virtuelle du navigateur exécute directement. Résultat : des performances 2x à 10x supérieures pour les calculs intensifs.

À retenir : WebAssembly n'est pas un remplacement de JavaScript. C'est un compagnon qui prend en charge les tâches CPU-intensives (traitement d'images, algorithmes complexes, moteurs physiques) pendant que JS gère le DOM et l'état applicatif.

WebAssembly est supporté par tous les navigateurs modernes depuis 2017 (Chrome, Firefox, Safari, Edge). Il peut être généré depuis plusieurs langages :

Langage source Toolchain Cas d'usage
Rust wasm-pack + wasm-bindgen Systèmes, crypto, parsing
C / C++ Emscripten Portage de libs existantes (OpenCV, ffmpeg)
AssemblyScript asc Développeurs TypeScript (syntaxe familière)
Go GOOS=js GOARCH=wasm Services backend portés côté client

Le flux de travail standard est le suivant : vous écrivez du code dans un langage compilé, vous le compilez en fichier .wasm, puis vous chargez ce fichier depuis JavaScript (ou TypeScript / Angular) via l'API WebAssembly.

// Vérifier le support WebAssembly dans le navigateur
if (typeof WebAssembly !== 'undefined') {
    console.log('WebAssembly supporté ✓');
} else {
    console.warn('WebAssembly non supporté — fallback JS requis');
}

WebAssembly fonctionne dans un environnement sandboxé : il n'a pas accès direct au DOM ni aux APIs du navigateur. Il communique avec JavaScript via une interface d'importation/exportation bien définie, ce qui le rend sécurisé par design.

Pourquoi WebAssembly avec Angular ?

Angular est un framework complet conçu pour des applications d'entreprise. Quand votre application Angular commence à traiter de larges volumes de données, des images, des simulations physiques ou des algorithmes cryptographiques, JavaScript montre ses limites. C'est là que WebAssembly devient indispensable.

Scénarios concrets où Wasm s'impose :

  • Traitement d'images côté client : redimensionnement, filtres, compression sans upload serveur
  • Calculs financiers complexes : Monte Carlo, options pricing en temps réel
  • Visualisation de données massives : tri, agrégation de millions de lignes dans le navigateur
  • Jeux et simulations 3D : moteurs physiques, rendering haute performance
  • Cryptographie : chiffrement/déchiffrement local sans dépendance serveur
  • Parsers et transpileurs : analyse syntaxique, formatage de code dans le navigateur
Benchmark réel : Le tri d'un tableau de 10 millions d'entiers prend ~800ms en JavaScript pur contre ~90ms en WebAssembly (Rust) — soit 9x plus rapide. Sur mobile, cet écart est encore plus marqué.

Comparaison JavaScript vs WebAssembly pour les calculs intensifs :

Critère JavaScript WebAssembly
Vitesse d'exécution (JIT optimisé) (compilé)
Accès au DOM Direct Via JS uniquement
Gestion mémoire Garbage collector Manuelle (linéaire)
Taille du bundle Légère (source) Plus lourde (binaire)
Débogage Facile (source maps) Complexe (DWARF)

La bonne stratégie est d'utiliser WebAssembly pour les hot paths de votre application Angular — les fonctions appelées des milliers de fois ou qui traitent de grands volumes de données — et de conserver JavaScript/TypeScript pour tout le reste.

Compiler Rust en WebAssembly avec wasm-pack

Rust est le langage le plus populaire pour cibler WebAssembly grâce à wasm-pack et wasm-bindgen. Ces outils génèrent automatiquement le code de liaison JavaScript/TypeScript et produisent un package npm prêt à l'emploi.

Étape 1 : Installer les outils

# Installer Rust (si pas déjà fait)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# Ajouter la cible WebAssembly
rustup target add wasm32-unknown-unknown

# Installer wasm-pack
cargo install wasm-pack

Étape 2 : Créer le projet Rust

# Créer une bibliothèque Rust
cargo new --lib wasm-utils
cd wasm-utils

Étape 3 : Écrire le code Rust avec wasm-bindgen

// src/lib.rs
use wasm_bindgen::prelude::*;

// Exporter une fonction vers JavaScript
#[wasm_bindgen]
pub fn fibonacci(n: u32) -> u64 {
    // Calcul récursif optimisé de Fibonacci
    match n {
        0 => 0,
        1 => 1,
        _ => {
            let mut a: u64 = 0;
            let mut b: u64 = 1;
            // Itératif pour éviter stack overflow
            for _ in 2..=n {
                let temp = a + b;
                a = b;
                b = temp;
            }
            b
        }
    }
}

// Traitement d'un tableau de nombres
#[wasm_bindgen]
pub fn sum_array(arr: &[f64]) -> f64 {
    // Itérer sur le slice passé depuis JS
    arr.iter().sum()
}

// Trier un vecteur (plus rapide que Array.sort en JS)
#[wasm_bindgen]
pub fn sort_numbers(mut arr: Vec) -> Vec {
    // Tri natif Rust : algorithme introsort O(n log n)
    arr.sort_by(|a, b| a.partial_cmp(b).unwrap());
    arr
}

Étape 4 : Configurer Cargo.toml

# Cargo.toml
[package]
name = "wasm-utils"
version = "0.1.0"
edition = "2021"

[lib]
# Nécessaire pour générer un .wasm
crate-type = ["cdylib"]

[dependencies]
# Génère les bindings JS automatiquement
wasm-bindgen = "0.2"

Étape 5 : Compiler et packager

# Compiler pour le web (génère un package npm dans ./pkg/)
wasm-pack build --target web

# Structure générée dans ./pkg/ :
# wasm_utils.js        → glue JS générée automatiquement
# wasm_utils.d.ts      → typings TypeScript !
# wasm_utils_bg.wasm   → le binaire WebAssembly
# package.json         → package npm prêt à publier
wasm-bindgen génère des typings TypeScript automatiquement depuis vos annotations Rust. Votre IDE Angular aura une autocomplétion complète sur les fonctions Wasm comme sur n'importe quel module TypeScript.

Intégrer un module Wasm dans Angular

Une fois le module Wasm compilé, il existe plusieurs façons de l'intégrer dans un projet Angular. Nous allons voir la méthode recommandée : via un service Angular dédié qui gère le chargement asynchrone et l'initialisation.

Copier le package Wasm dans le projet Angular :

# Copier le package compilé dans le projet Angular
cp -r wasm-utils/pkg src/wasm/wasm-utils

# Ou utiliser npm link pour le développement local
cd wasm-utils/pkg && npm link
cd ../angular-app && npm link wasm-utils

Créer un service Angular pour encapsuler le module Wasm :

// src/app/services/wasm.service.ts
import { Injectable } from '@angular/core';

// Import du module Wasm généré par wasm-pack
import type { InitOutput } from '../wasm/wasm-utils/wasm_utils';

@Injectable({
    providedIn: 'root'
})
export class WasmService {
    // Stocke l'instance du module chargé
    private wasmModule: InitOutput | null = null;
    // Promise pour éviter les chargements multiples (singleton pattern)
    private loadingPromise: Promise<void> | null = null;

    // Chargement lazy du module Wasm
    async loadModule(): Promise<void> {
        // Si déjà chargé, retourner immédiatement
        if (this.wasmModule) return;
        // Si en cours de chargement, attendre la même Promise
        if (this.loadingPromise) return this.loadingPromise;

        this.loadingPromise = (async () => {
            // Import dynamique pour le lazy loading
            const { default: init, fibonacci, sum_array, sort_numbers } =
                await import('../wasm/wasm-utils/wasm_utils.js');

            // Initialiser le module (charge le .wasm depuis le serveur)
            this.wasmModule = await init();

            // Exposer les fonctions sur le service
            this.fibonacci = fibonacci;
            this.sumArray = sum_array;
            this.sortNumbers = sort_numbers;
        })();

        return this.loadingPromise;
    }

    // Méthodes exposées (surchargées après init)
    fibonacci: (n: number) => bigint = () => { throw new Error('Wasm non initialisé'); };
    sumArray: (arr: Float64Array) => number = () => { throw new Error('Wasm non initialisé'); };
    sortNumbers: (arr: Float64Array) => Float64Array = () => { throw new Error('Wasm non initialisé'); };
}

Utiliser le service Wasm dans un composant Angular :

// src/app/components/calculator/calculator.component.ts
import { Component, OnInit, inject } from '@angular/core';
import { WasmService } from '../../services/wasm.service';

@Component({
    selector: 'app-calculator',
    standalone: true,
    template: `
        <div class="calculator">
            <h2>Fibonacci WebAssembly</h2>
            @if (isLoading) {
                <p>Chargement du module Wasm...</p>
            } @else {
                <input [(ngModel)]="inputN" type="number" min="0" max="90" />
                <button (click)="calculate()">Calculer</button>
                <p>Résultat : {{ result }}</p>
                <p>Temps : {{ elapsed }}ms</p>
            }
        </div>
    `
})
export class CalculatorComponent implements OnInit {
    private wasmService = inject(WasmService);

    inputN = 40;
    result = '';
    elapsed = 0;
    isLoading = true;

    async ngOnInit() {
        // Charger le module Wasm au démarrage du composant
        await this.wasmService.loadModule();
        this.isLoading = false;
    }

    calculate() {
        const start = performance.now(); // Mesurer la performance
        // Appel direct à la fonction Wasm (aussi simple qu'une fonction JS)
        const fib = this.wasmService.fibonacci(this.inputN);
        this.elapsed = Math.round(performance.now() - start);
        this.result = fib.toString();
    }
}

Configurer Angular pour servir le fichier .wasm :

// angular.json — ajouter les assets Wasm
{
    "projects": {
        "my-app": {
            "architect": {
                "build": {
                    "options": {
                        "assets": [
                            "src/favicon.ico",
                            "src/assets",
                            // Inclure les fichiers .wasm comme assets statiques
                            {
                                "glob": "**/*.wasm",
                                "input": "src/wasm",
                                "output": "/wasm/"
                            }
                        ]
                    }
                }
            }
        }
    }
}
Note : Le serveur doit envoyer le header MIME application/wasm pour les fichiers .wasm. Angular CLI Dev Server le fait automatiquement. En production, vérifiez la configuration Nginx/Apache.

Communication Angular ↔ WebAssembly

La communication entre JavaScript (Angular) et WebAssembly se fait via un modèle d'importation/exportation explicite. WebAssembly ne peut échanger que des types numériques de base (i32, i64, f32, f64). Pour les types complexes comme les chaînes et les tableaux, wasm-bindgen gère la sérialisation automatiquement.

Passage de tableaux (Float64Array ↔ Vec<f64>) :

// Angular → Wasm : passage d'un tableau de données
async processLargeDataset(data: number[]): Promise<number[]> {
    await this.wasmService.loadModule();

    // Convertir en Float64Array pour passage efficace (mémoire partagée)
    const input = new Float64Array(data);

    // Appel Wasm — wasm-bindgen gère la copie mémoire automatiquement
    const sorted = this.wasmService.sortNumbers(input);

    // Reconvertir le résultat Wasm en tableau JS standard
    return Array.from(sorted);
}

Passage de chaînes de caractères :

// Rust côté Wasm
#[wasm_bindgen]
pub fn process_json(input: &str) -> String {
    // Parsesr du JSON (bibliothèque serde_json)
    // wasm-bindgen encode/décode UTF-8 automatiquement
    let parsed: serde_json::Value = serde_json::from_str(input)
        .unwrap_or(serde_json::Value::Null);
    format!("Parsed: {} keys", parsed.as_object().map_or(0, |o| o.len()))
}
// Angular : appel avec une chaîne
const jsonData = JSON.stringify({ name: 'Angular', version: 17 });
const result = this.wasmService.processJson(jsonData); // "Parsed: 2 keys"

Utiliser la mémoire partagée (SharedArrayBuffer) pour zéro copie :

// Technique avancée : accéder directement à la mémoire Wasm
// Évite les copies coûteuses pour les gros tableaux (>10MB)
async processInPlace(data: Float64Array): Promise<void> {
    await this.wasmService.loadModule();

    // Obtenir la mémoire linéaire du module Wasm
    const memory = (this.wasmService as any).wasmModule.memory as WebAssembly.Memory;

    // Allouer dans la mémoire Wasm (fonction Rust exposée)
    const ptr = this.wasmService.allocate(data.length);

    // Écriture directe sans copie intermédiaire
    const wasmMemory = new Float64Array(memory.buffer, ptr, data.length);
    wasmMemory.set(data);

    // Traiter en place dans Wasm
    this.wasmService.processInPlace(ptr, data.length);

    // Lire les résultats directement depuis la mémoire Wasm
    // data est maintenant modifié directement
    data.set(wasmMemory);

    // Libérer la mémoire allouée
    this.wasmService.deallocate(ptr, data.length);
}
Performance : Pour des tableaux de moins d'1 Mo, laissez wasm-bindgen gérer les copies automatiquement. Au-delà, la technique SharedArrayBuffer évite des copies inutiles et peut doubler les performances de transfert.

Callbacks Wasm → Angular (fonctions importées) :

// Rust : importer une fonction depuis JavaScript
#[wasm_bindgen]
extern "C" {
    // Déclarer une fonction JS que Wasm peut appeler
    fn progress_callback(percent: f64);
}

#[wasm_bindgen]
pub fn long_computation(data: &[f64]) -> f64 {
    let total = data.len();
    let mut result = 0.0f64;
    for (i, &val) in data.iter().enumerate() {
        result += val.sqrt(); // Calcul intensif
        // Appeler le callback JS tous les 10%
        if i % (total / 10) == 0 {
            progress_callback((i as f64 / total as f64) * 100.0);
        }
    }
    result
}
// Angular : fournir la fonction callback à Wasm
// Le callback est injecté via les imports WebAssembly
const imports = {
    env: {
        progress_callback: (percent: number) => {
            // Mettre à jour un signal Angular depuis le callback Wasm
            this.progressSignal.set(Math.round(percent));
        }
    }
};

Optimiser les performances avec Wasm

Intégrer WebAssembly ne garantit pas automatiquement de meilleures performances. Il faut suivre quelques patterns clés pour maximiser les gains et éviter les pièges courants.

1. Charger Wasm en background avec un Web Worker :

// src/app/workers/wasm.worker.ts
/// <reference lib="webworker" />
import init, { sort_numbers } from '../wasm/wasm-utils/wasm_utils.js';

// Initialiser le module Wasm dans le Worker (pas le thread principal)
init().then(() => {
    // Signaler que le Worker est prêt
    postMessage({ type: 'ready' });
});

// Écouter les messages du thread principal
addEventListener('message', ({ data }) => {
    if (data.type === 'sort') {
        const input = new Float64Array(data.buffer);
        // Traiter dans le Worker — ne bloque pas l'UI
        const sorted = sort_numbers(input);
        // Retourner via transfert de buffer (zéro copie)
        postMessage({ type: 'result', buffer: sorted.buffer }, [sorted.buffer]);
    }
});
// Service Angular utilisant le Worker
@Injectable({ providedIn: 'root' })
export class WasmWorkerService {
    private worker = new Worker(new URL('../workers/wasm.worker', import.meta.url));

    sortAsync(data: number[]): Promise<number[]> {
        return new Promise((resolve) => {
            const buffer = new Float64Array(data).buffer;

            this.worker.onmessage = ({ data: response }) => {
                if (response.type === 'result') {
                    resolve(Array.from(new Float64Array(response.buffer)));
                }
            };

            // Envoyer avec transfert (zéro copie — le buffer change de propriétaire)
            this.worker.postMessage({ type: 'sort', buffer }, [buffer]);
        });
    }
}

2. Précharger le module Wasm au démarrage de l'app :

// app.config.ts — précharger Wasm avant bootstrap
import { ApplicationConfig } from '@angular/core';
import { WasmService } from './services/wasm.service';

export const appConfig: ApplicationConfig = {
    providers: [
        // Démarrer le chargement Wasm immédiatement
        {
            provide: APP_INITIALIZER,
            useFactory: (wasm: WasmService) => () => wasm.loadModule(),
            deps: [WasmService],
            multi: true
        }
    ]
};

3. Minimiser les traversées JS ↔ Wasm (boundary crossings) :

// ❌ Mauvais : traverser la frontière pour chaque élément
const results = data.map(x => wasmService.square(x)); // N appels Wasm

// ✅ Bon : traiter le tableau entier en un seul appel
const input = new Float64Array(data);
const results = wasmService.squareArray(input); // 1 seul appel Wasm
Règle d'or : Chaque appel JS → Wasm a un coût fixe (~1-5µs). Regroupez toujours les opérations pour travailler en batch plutôt qu'item par item.

4. Activer les optimisations Rust pour la production :

# Cargo.toml — profil release optimisé
[profile.release]
# Niveau d'optimisation maximum
opt-level = 3
# Link-Time Optimization (réduit la taille et améliore les perfs)
lto = true
# Panic sans stack trace (réduit la taille du binaire)
panic = "abort"

# Compiler en release pour la production
wasm-pack build --release --target web

Cas d'usage réels en entreprise

WebAssembly avec Angular brille dans des scénarios bien précis. Voici trois implémentations concrètes que j'ai rencontrées en production.

Cas 1 — Traitement d'images côté client

Un outil d'e-commerce nécessitait de redimensionner et compresser des images produit directement dans le navigateur, sans upload intermédiaire. WebAssembly a réduit le temps de traitement d'une image HD de 2.3s (Canvas API JS) à 180ms.

// Rust : redimensionner une image (bibliothèque image)
#[wasm_bindgen]
pub fn resize_image(data: &[u8], new_width: u32, new_height: u32) -> Vec<u8> {
    // Charger l'image depuis les bytes bruts (format RGBA)
    let img = image::load_from_memory(data).expect("Image invalide");
    // Redimensionner avec filtre Lanczos (qualité optimale)
    let resized = img.resize_exact(new_width, new_height, image::imageops::FilterType::Lanczos3);
    // Encoder en PNG et retourner les bytes
    let mut output = Vec::new();
    resized.write_to(&mut output, image::ImageOutputFormat::Png).unwrap();
    output
}
// Angular : utiliser le service de redimensionnement
async processImage(file: File): Promise<Blob> {
    // Lire le fichier comme ArrayBuffer
    const buffer = await file.arrayBuffer();
    const imageData = new Uint8Array(buffer);

    // Appel Wasm pour redimensionner à 800x600
    const resized = await this.wasmService.resizeImage(imageData, 800, 600);

    // Convertir le résultat en Blob pour affichage ou upload
    return new Blob([resized], { type: 'image/png' });
}

Cas 2 — Simulation Monte Carlo pour fintech

Un outil d'analyse financière devait calculer 100 000 simulations Monte Carlo pour évaluer le risque de portefeuille. En JavaScript : 12 secondes. En WebAssembly (Rust) avec SIMD : 0.8 secondes, en temps réel.

// Rust : simulation Monte Carlo avec nombres pseudo-aléatoires
#[wasm_bindgen]
pub fn monte_carlo_portfolio(
    prices: &[f64],    // Prix actuels du portefeuille
    weights: &[f64],   // Pondérations des actifs
    n_simulations: u32 // Nombre de simulations
) -> Vec<f64> {
    let mut results = Vec::with_capacity(n_simulations as usize);
    let mut rng = fastrand::Rng::new(); // RNG rapide

    for _ in 0..n_simulations {
        let mut portfolio_value = 0.0f64;
        for (price, weight) in prices.iter().zip(weights.iter()) {
            // Retour aléatoire simulé (distribution normale approximée)
            let random_return = (rng.f64() - 0.5) * 0.04;
            portfolio_value += price * weight * (1.0 + random_return);
        }
        results.push(portfolio_value);
    }
    results
}

Cas 3 — Validation de données CSV massifs

Un outil RH devait valider des fichiers CSV de 100 000+ lignes avec des règles métier complexes directement dans le navigateur. La validation JS prenait 8s et bloquait l'UI. Avec Wasm + Web Worker : 400ms en background.

// Service Angular avec progression temps réel
validateCsvInBackground(csvData: string): Observable<ValidationResult> {
    return new Observable(observer => {
        const worker = new Worker(new URL('./csv-validator.worker', import.meta.url));

        worker.postMessage({ csv: csvData });

        worker.onmessage = ({ data }) => {
            if (data.type === 'progress') {
                // Émettre la progression pour mettre à jour la barre Angular
                observer.next({ progress: data.percent, errors: [] });
            } else if (data.type === 'complete') {
                observer.next({ progress: 100, errors: data.errors });
                observer.complete();
                worker.terminate(); // Libérer le Worker
            }
        };

        // Cleanup si l'Observable est unsubscribed
        return () => worker.terminate();
    });
}
Retour d'expérience : Dans les trois cas, WebAssembly n'a pas remplacé Angular ou TypeScript — il a simplement pris en charge les parties CPU-intensives, laissant Angular gérer l'UX, l'état et le routing comme avant.

Tests, debugging et bonnes pratiques

Tester et déboguer du code WebAssembly présente des défis spécifiques. Voici les patterns éprouvés pour maintenir la qualité dans un projet Angular + Wasm.

Tester le service Wasm avec Jasmine/Jest

// wasm.service.spec.ts — tester l'intégration Wasm dans Angular
import { TestBed } from '@angular/core/testing';
import { WasmService } from './wasm.service';

describe('WasmService', () => {
    let service: WasmService;

    beforeEach(async () => {
        TestBed.configureTestingModule({});
        service = TestBed.inject(WasmService);
        // Attendre que le module Wasm soit chargé avant chaque test
        await service.loadModule();
    });

    it('devrait calculer fibonacci(10) = 55', () => {
        // Comparer avec la valeur connue
        expect(Number(service.fibonacci(10))).toBe(55);
    });

    it('devrait trier un tableau de nombres', () => {
        const unsorted = new Float64Array([3.0, 1.0, 4.0, 1.0, 5.0]);
        const sorted = service.sortNumbers(unsorted);
        // Vérifier l'ordre croissant
        expect(Array.from(sorted)).toEqual([1.0, 1.0, 3.0, 4.0, 5.0]);
    });

    it('devrait retourner 0 pour un tableau vide', () => {
        const empty = new Float64Array(0);
        expect(service.sumArray(empty)).toBe(0);
    });
});

Déboguer avec les source maps Wasm

# Compiler en mode debug avec source maps pour le navigateur
wasm-pack build --dev --target web

# angular.json : activer les source maps Wasm
{
    "configurations": {
        "development": {
            "sourceMap": {
                "scripts": true,
                "styles": true,
                "vendor": true
            }
        }
    }
}

Checklist avant mise en production

  • Module Wasm compilé en mode --release avec opt-level = 3
  • Fichier .wasm servi avec le MIME type application/wasm
  • Compression gzip/brotli activée sur le serveur pour les fichiers .wasm
  • Chargement asynchrone (await init()) et jamais bloquant
  • Fallback JavaScript prévu si WebAssembly n'est pas supporté
  • Web Worker utilisé pour les calculs longs (>100ms)
  • Tests d'intégration couvrant les boundary crossings JS ↔ Wasm
  • Monitoring des performances en production (performance.now())
  • Taille du bundle Wasm vérifiée (<500KB recommandé)
  • Headers CORS configurés si le .wasm est servi depuis un CDN

Fallback JavaScript pour la compatibilité

// Pattern défensif : toujours avoir un fallback JS
@Injectable({ providedIn: 'root' })
export class HybridComputeService {
    private wasmService = inject(WasmService);
    private wasmAvailable = false;

    async initialize(): Promise<void> {
        try {
            await this.wasmService.loadModule();
            this.wasmAvailable = true;
            console.log('Mode WebAssembly activé');
        } catch (e) {
            // Navigateur trop ancien ou erreur de chargement
            this.wasmAvailable = false;
            console.warn('Fallback JavaScript activé');
        }
    }

    fibonacci(n: number): bigint {
        if (this.wasmAvailable) {
            // Chemin rapide : WebAssembly
            return this.wasmService.fibonacci(n);
        }
        // Fallback : implémentation JavaScript pure
        if (n <= 1) return BigInt(n);
        let a = 0n, b = 1n;
        for (let i = 2; i <= n; i++) {
            [a, b] = [b, a + b];
        }
        return b;
    }
}
Conseil architecture : Ne pas exposer les fonctions Wasm directement dans les composants. Toujours passer par un service Angular intermédiaire qui gère l'initialisation, les erreurs, et le fallback. Cette couche d'abstraction facilite les tests et permet de changer l'implémentation sans toucher aux composants.

Partager