📧 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://www.easyangularkit.com/easyangularkit

📧 Reste informé(e) !

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

S'inscrire gratuitement

AngularKit

Suite d'outils pour développeurs Angular francophones. Apprends, modernise tes réflexes, audite ta codebase.

Produits

Contact

Légal

© 2026 AngularKit. Tous droits réservés.