Bug Fixes Frontend
Vue d’ensemble
Cette page documente les principaux bugs fixes effectués sur le frontend Angular, leurs causes, leurs solutions et les leçons apprises.
Bug "Maximum call stack size exceeded" - Formulaire Projet (2025-01-25)
Description du problème
Symptômes :
- Erreur "RangeError: Maximum call stack size exceeded" lors de la création d’un projet
- Application frontend plantée lors de l’interaction avec les sliders de poids
- Erreur originant de MatSlider
dans la section configuration des poids
Stack trace typique :
ERROR RangeError: Maximum call stack size exceeded
at detectChangesInternal (core.mjs:14779:13)
at ViewRef$1.detectChanges (core.mjs:15416:9)
at set step (slider.mjs:1170:19)
at MatSliderThumb.initProps (slider.mjs:1274:18)
at MatSlider._initUINonRange (slider.mjs:596:16)
at MatSlider.ngAfterViewInit (slider.mjs:588:20)
Analyse de la cause racine
Le problème était causé par une boucle infinie de détection de changements dans Angular :
-
Problème Principal :
defaultWeights
défini comme un getter qui retourne un nouveau tableau à chaque appel -
Déclencheur : La méthode
onWeightChange()
modifiait directementthis.defaultWeights[index].weight
-
Boucle :
-
Modification du poids → Détection de changement Angular
-
Re-rendu du template → Appel du getter
defaultWeights
-
Nouveau tableau créé → MatSlider se réinitialise
-
MatSlider déclenche à nouveau
onWeightChange()
-
Boucle infinie
-
Code problématique :
// ❌ Problématique - Getter retournant un nouveau tableau
get defaultWeights() {
return [
{ criterion_name: 'ethical_score', weight: 0.4, /* ... */ },
// ...
];
}
// ❌ Problématique - Modification directe d'un getter
onWeightChange(index: number, value: any): void {
this.defaultWeights[index].weight = value; // Cause la boucle !
}
Solution implémentée
1. Transformation en propriété normale
// ✅ Solution - Propriété normale
defaultWeights: any[] = [];
private initializeDefaultWeights(): void {
this.defaultWeights = [
{ criterion_name: 'ethical_score', weight: 0.4, label: 'PROJECTS.CRITERIA.ETHICAL_SCORE', icon: 'security' },
{ criterion_name: 'technical_score', weight: 0.4, label: 'PROJECTS.CRITERIA.TECHNICAL_SCORE', icon: 'engineering' },
// ...
];
}
2. Amélioration de la gestion des événements
Template HTML :
<!-- ✅ Utilisation de valueChange au lieu de change -->
<input matSliderThumb
[value]="weight.weight"
(valueChange)="onWeightChange(i, $event)"
style="width: 100%;" />
3. Validation robuste dans onWeightChange
onWeightChange(index: number, value: any): void {
// Validation de l'index
if (index < 0 || index >= this.defaultWeights.length) {
console.warn('Index de poids invalide:', index);
return;
}
// Conversion et validation de la valeur
let numericWeight: number;
if (typeof value === 'string') {
numericWeight = parseFloat(value);
} else if (typeof value === 'number') {
numericWeight = value;
} else {
console.warn('Valeur de poids invalide:', value);
return;
}
// Validation de la plage
if (isNaN(numericWeight) || numericWeight < 0 || numericWeight > 1) {
console.warn('Valeur de poids hors plage:', numericWeight);
return;
}
// Éviter les modifications inutiles
if (Math.abs(this.defaultWeights[index].weight - numericWeight) < 0.001) {
return;
}
// Mettre à jour le poids
this.defaultWeights[index].weight = numericWeight;
// Recalculer les poids actifs
this.currentWeights = this.defaultWeights
.filter(w => w.weight > 0)
.map(w => ({
criterion_name: w.criterion_name,
weight: w.weight
}));
// Mettre à jour le preview avec un délai pour éviter les appels en cascade
setTimeout(() => this.updatePreview(), 100);
}
Fichiers modifiés
-
frontend/src/app/pages/projects/project-form.component.ts
-
frontend/src/app/pages/projects/project-form.component.html
Leçons apprises
Bonnes pratiques Angular
-
Éviter les getters complexes : Les getters sont recalculés à chaque détection de changement
-
Propriétés stables : Utiliser des propriétés normales pour les données manipulées par l’UI
-
Validation des événements : Toujours valider les données d’entrée des événements UI
-
Débouncing des updates : Utiliser
setTimeout()
oudebounceTime()
pour éviter les cascades d’appels
Tests de régression
Pour éviter la réapparition de ce bug :
-
Test d’interaction : Vérifier que les sliders peuvent être modifiés sans erreur
-
Test de performance : S’assurer qu’il n’y a pas de boucles de détection de changement
-
Test de validation : Vérifier que les valeurs invalides sont correctement gérées