📧 Reste informé(e) !

Reçois les derniers articles et conseils EasyAngularKit directement dans ta boîte mail.

S'inscrire gratuitement

~3 min de lecture

?? vs || : L'Opérateur Que 90% Des Devs Utilisent Mal

Tu utilises || partout dans ton code TypeScript ? Tu penses que c'est la même chose que ?? ?

Spoiler alert : Non. Et cette confusion peut créer des bugs subtils et difficiles à débugger.

Dans cet article, je vais te montrer la différence cruciale entre ces deux opérateurs et pourquoi ?? devrait devenir ton nouveau réflexe.


Le Problème Avec L'Opérateur OR (||)

❌ Ce que font les juniors :

Ils utilisent || pour définir des valeurs par défaut, sans se rendre compte des pièges.

interface UserConfig {
  readonly name: string;
  readonly age: number;
  readonly isAdmin: boolean;
  readonly score: number;
}

function displayUserInfo(config: UserConfig): void {
  // ❌ Problème : || considère 0, false, '' comme falsy
  const name = config.name || 'Anonyme';
  const age = config.age || 18;
  const isAdmin = config.isAdmin || false;
  const score = config.score || 0;

  console.log({ name, age, isAdmin, score });
}

// Test
displayUserInfo({
  name: '',
  age: 0,
  isAdmin: false,
  score: 0
});

// ❌ Résultat inattendu :
// { name: 'Anonyme', age: 18, isAdmin: false, score: 0 }
// Alors qu'on voulait garder les valeurs 0, false et ''

🚨 Pourquoi c'est problématique ?

L'opérateur || retourne la première valeur truthy (vraie). Il considère comme falsy :

  • false
  • 0
  • '' (chaîne vide)
  • null
  • undefined
  • NaN

Résultat : tu remplaces des valeurs légitimes par des valeurs par défaut !


La Solution Pro : L'Opérateur Nullish Coalescing (??)

✅ Ce que font les pros :

Ils utilisent ?? qui ne considère que null et undefined comme "absents".

interface UserConfig {
  readonly name: string;
  readonly age: number;
  readonly isAdmin: boolean;
  readonly score: number;
}

function displayUserInfo(config: UserConfig): void {
  // ✅ ?? ne remplace que null et undefined
  const name = config.name ?? 'Anonyme';
  const age = config.age ?? 18;
  const isAdmin = config.isAdmin ?? false;
  const score = config.score ?? 0;

  console.log({ name, age, isAdmin, score });
}

// Test
displayUserInfo({
  name: '',
  age: 0,
  isAdmin: false,
  score: 0
});

// ✅ Résultat correct :
// { name: '', age: 0, isAdmin: false, score: 0 }
// Les valeurs 0, false et '' sont conservées !

💡 Pourquoi c'est mieux ?

  • Plus prévisible : seuls null et undefined sont remplacés
  • Moins de bugs : tu gardes les valeurs légitimes comme 0, false, ''
  • Plus explicite : ton intention est claire (gérer l'absence de valeur, pas les valeurs falsy)

Comparaison Détaillée : || vs ??

Tableau de Comparaison

Valeur value || 'default' value ?? 'default'
null 'default' 'default'
undefined 'default' 'default'
false 'default' false
0 'default' 0
'' 'default' ''
NaN 'default' NaN
'hello' 'hello' 'hello'

Exemples Concrets

// Exemple 1 : Score de jeu
const score = 0;

console.log(score || 100);  // ❌ 100 (mauvais !)
console.log(score ?? 100);  // ✅ 0 (correct !)

// Exemple 2 : Chaîne vide
const username = '';

console.log(username || 'Guest');  // ❌ 'Guest' (mauvais !)
console.log(username ?? 'Guest');  // ✅ '' (correct !)

// Exemple 3 : Boolean false
const isEnabled = false;

console.log(isEnabled || true);  // ❌ true (mauvais !)
console.log(isEnabled ?? true);  // ✅ false (correct !)

// Exemple 4 : null/undefined (même comportement)
const data = null;

console.log(data || 'default');  // ✅ 'default'
console.log(data ?? 'default');  // ✅ 'default'

Cas d'Usage Réels En Angular

Exemple 1 : Configuration de composant avec des valeurs par défaut


@Component({
  selector: 'app-button',
  standalone: true,
  template: `
    <button 
      [disabled]="disabled()" 
      [class]="'btn btn-' + variant()">
      {{ label() }}
    </button>
  `
})
export class ButtonComponent {
  // ❌ Mauvaise approche avec ||
  readonly label = input<string>();

  readonly variant = input<string>();

  readonly disabled = input<boolean>();

  readonly displayLabel = computed(() => this.label() || 'Click me');

  readonly displayVariant = computed(() => this.variant() || 'primary');

  readonly isDisabled = computed(() => this.disabled() || false);

  // Problème : si disabled=false est passé, il devient true !
}

// ✅ Bonne approche avec ??
@Component({
  selector: 'app-button',
  standalone: true,
  template: `
    <button 
      [disabled]="isDisabled()" 
      [class]="'btn btn-' + displayVariant()">
      {{ displayLabel() }}
    </button>
  `
})
export class ButtonComponent {
  readonly label = input<string>();

  readonly variant = input<string>();

  readonly disabled = input<boolean>();

  readonly displayLabel = computed(() => this.label() ?? 'Click me');

  readonly displayVariant = computed(() => this.variant() ?? 'primary');

  readonly isDisabled = computed(() => this.disabled() ?? false);
}

Exemple 2 : Gestion de données API avec valeurs nullables

interface ApiUser {
  readonly id: number;
  readonly name: string | null;
  readonly email: string | null;
  readonly age: number | null;
  readonly isActive: boolean | null;
}

@Component({
  selector: 'app-user-profile',
  standalone: true,
  template: `
    <div class="profile">
      <h2>{{ displayName() }}</h2>
      <p>Email: {{ displayEmail() }}</p>
      <p>Age: {{ displayAge() }}</p>
      <p>Status: {{ displayStatus() }}</p>
    </div>
  `
})
export class UserProfileComponent {
  private readonly userService = inject(UserService);

  readonly user = toSignal(this.userService.getUser());

  // ✅ Utilisation de ?? pour les valeurs par défaut
  readonly displayName = computed(() =>
  this.user()?.name ?? 'Nom non renseigné'
  );

  readonly displayEmail = computed(() =>
  this.user()?.email ?? 'Email non renseigné'
  );

  readonly displayAge = computed(() =>
  this.user()?.age ?? 'Âge non renseigné'
  );

  readonly displayStatus = computed(() =>
  (this.user()?.isActive ?? false) ? 'Actif' : 'Inactif'
  );
}

Exemple 3 : Formulaires avec valeurs par défaut


@Component({
  selector: 'app-user-form',
  standalone: true,
  imports: [ReactiveFormsModule]
})
export class UserFormComponent implements OnInit {
  private readonly fb = inject(FormBuilder);

  readonly form = this.fb.group({
    name: [''],
    age: [0],
    acceptTerms: [false],
    newsletter: [false]
  });

  ngOnInit(): void {
    // Charger les données utilisateur existantes
    const savedData = this.loadSavedData();

    // ✅ Utiliser ?? pour préserver les valeurs 0 et false
    this.form.patchValue({
      name: savedData.name ?? '',
      age: savedData.age ?? 0,
      acceptTerms: savedData.acceptTerms ?? false,
      newsletter: savedData.newsletter ?? false
    });
  }

  private loadSavedData(): Partial<{
    readonly name: string;
    readonly age: number;
    readonly acceptTerms: boolean;
    readonly newsletter: boolean;
  }> {
    // Simuler le chargement depuis localStorage ou API
    return {
      name: null as any,
      age: 0,  // ⚠️ Cette valeur doit être conservée
      acceptTerms: false,  // ⚠️ Cette valeur doit être conservée
      newsletter: undefined as any
    };
  }
}

L'Opérateur Nullish Assignment (??=)

Bonus : Une syntaxe encore plus courte

TypeScript offre aussi l'opérateur ??= pour assigner une valeur seulement si la variable est null ou undefined.

let count: number | null = null;
let name: string | undefined = undefined;
let isActive = false;
let score = 0;

// ✅ Assigne seulement si null ou undefined
count ??= 10;      // count = 10
name ??= 'John';   // name = 'John'
isActive ??= true; // isActive reste false (pas null/undefined)
score ??= 100;     // score reste 0 (pas null/undefined)

console.log({ count, name, isActive, score });
// { count: 10, name: 'John', isActive: false, score: 0 }

Exemple pratique en Angular


@Component({
  selector: 'app-settings',
  standalone: true
})
export class SettingsComponent implements OnInit {
  private readonly storageService = inject(StorageService);

  // Configuration avec valeurs par défaut
  readonly config = signal({
    theme: undefined as string | undefined,
    fontSize: null as number | null,
    autoSave: undefined as boolean | undefined,
    language: null as string | null
  });

  ngOnInit(): void {
    const savedConfig = this.storageService.getConfig();

    // ✅ Utiliser ??= pour définir les valeurs par défaut
    savedConfig.theme ??= 'light';
    savedConfig.fontSize ??= 14;
    savedConfig.autoSave ??= true;
    savedConfig.language ??= 'fr';

    this.config.set(savedConfig);
  }
}

Quand Utiliser || vs ?? ?

Utilise || quand :

✅ Tu veux remplacer toutes les valeurs falsy (y compris 0, false, '')

✅ Tu travailles avec des chaînes et veux éviter les chaînes vides

✅ Tu veux un comportement "tout ou rien"

// Cas légitime pour ||
const displayText = userInput || 'Texte par défaut';
const validEmail = email.trim() || null;
const searchQuery = query.trim() || undefined;

Utilise ?? quand :

✅ Tu veux remplacer uniquement null et undefined

✅ Tu travailles avec des nombres (où 0 est une valeur valide)

✅ Tu travailles avec des booleans (où false est une valeur valide)

✅ Tu veux des valeurs par défaut tout en gardant 0, false, ''

// Cas typiques pour ??
const age = user.age ?? 0;
const isEnabled = config.enabled ?? false;
const count = data.count ?? 0;
const score = game.score ?? 0;

Les Pièges À Éviter

Piège 1 : Mélanger || et ?? sans parenthèses

// ❌ Erreur de syntaxe TypeScript
const value = a || b ?? c;

// ✅ Utiliser des parenthèses
const value = (a || b) ?? c;
const value2 = a || (b ?? c);

Piège 2 : Utiliser ?? avec des types non-nullable

// ❌ TypeScript te prévient que c'est inutile
const name: string = 'John';
const result = name ?? 'Default';  // name ne peut jamais être null/undefined

// ✅ Utiliser ?? seulement avec des types nullable
const nullableName: string | null = 'John';
const finalResult = nullableName ?? 'Default';

Piège 3 : Oublier que ?? ne gère pas NaN

const value = NaN;

console.log(value || 0);   // ✅ 0 (NaN est falsy)
console.log(value ?? 0);   // ❌ NaN (NaN n'est ni null ni undefined)

// ✅ Pour gérer NaN, utilise isNaN()
const safeValue = isNaN(value) ? 0 : value;

Conclusion : ?? Est Ton Nouvel Ami

L'opérateur ?? n'est pas juste une alternative à ||, c'est un outil plus précis pour gérer les valeurs absentes.

Ce que tu dois retenir :

?? ne remplace que null et undefined

|| remplace toutes les valeurs falsy (y compris 0, false, '')

✅ Utilise ?? par défaut pour les valeurs par défaut

✅ Utilise || seulement quand tu veux vraiment filtrer les valeurs falsy

??= pour assigner des valeurs par défaut de manière concise

La règle d'or :

Si 0, false ou '' sont des valeurs valides dans ton contexte, utilise ?? plutôt que ||.


Envie de découvrir EasyAngularKit et son programme ?

Découvre EasyAngularKit : https://pim.ms/C31g7p2

📧 Reste informé(e) !

Reçois les derniers articles et conseils EasyAngularKit directement dans ta boîte mail.

S'inscrire gratuitement

EasyAngularKit

Formation complète pour maîtriser Angular et développer des applications web modernes.

Navigation

Contact

Légal

© 2026 Easy Angular Kit. Tous droits réservés.