Replay

Apprendre Angular en 1h

Je me donne un objectif: vous faire découvrir et apprendre Angular en 1 heure: composant, syntaxe dans les templates, les directives, les signaux, les routeurs, les services, l'injection de dépendances, les observables et les requêtes HTTP. Le nécessaire pour faire une application Angular !.

Skip to content

Vous souhaitez recevoir de l'aide sur ce sujet ? rejoignez la communauté Angular.fr sur Discord.

Maîtriser la fonction untracked() dans Angular

Les signaux sont au cœur de la réactivité moderne dans Angular. Mais parfois, vous avez besoin de lire un signal sans créer de dépendance. C'est exactement ce que permet la fonction untracked(). Dans ce tutoriel, nous allons voir comment cette fonction peut vous aider à optimiser vos applications Angular.

Qu'est-ce que untracked() ?

Imaginez que vous êtes dans une salle de contrôle avec plusieurs alarmes. Normalement, quand une alarme sonne, vous devez y répondre. Mais parfois, vous voulez juste vérifier l'état d'une alarme sans vous abonner à ses notifications. C'est exactement ce que fait untracked() - il vous permet de lire la valeur d'un signal sans créer une dépendance.

Dans le contexte des signaux Angular, lorsque vous lisez un signal dans un computed() ou un effect(), Angular enregistre cette dépendance et exécutera à nouveau la fonction lorsque le signal changera. Avec untracked(), vous pouvez lire un signal sans créer cette dépendance.

Cas d'utilisation courants

1. Éviter les dépendances inutiles dans les effets

Supposons que nous voulons journaliser un message quand l'utilisateur courant change, et inclure le compteur actuel dans ce message. Mais nous ne voulons pas que l'effet se déclenche quand le compteur change, seulement quand l'utilisateur change.

ts
import { Component, effect, signal, untracked, inject } from '@angular/core';
import { User } from './user.interface';
import { UserService } from '../../core/services/user.service';

@Component({
  selector: 'app-user-tracking',
  template: `
    <div>Current user: {{ currentUser()?.name }}</div>
    <div>Counter: {{ counter() }}</div>
    <button (click)="incrementCounter()">Increment Counter</button>
    <button (click)="changeUser()">Change User</button>
  `
})
export class UserTrackingComponent {
  private userService = inject(UserService);
  
  // Signaux pour notre composant
  currentUser = signal<User | null>(null);
  counter = signal(0);
  
  constructor() {
    this.currentUser.set({
      id: 1,
      name: 'John Doe',
      username: 'johndoe',
      email: '[email protected]'
    });
    
    /**
     * Effet qui trace les changements d'utilisateur sans être affecté par les changements du compteur
     * Utilise untracked() pour lire le compteur sans créer de dépendance
     */
    effect(() => {
      console.log(`User changed to ${this.currentUser()?.name} with counter at ${untracked(this.counter)}`);
    });
  }
  
  incrementCounter() {
    this.counter.update(value => value + 1);
  }
  
  changeUser() {
    const currentId = this.currentUser()?.id || 0;
    this.userService.getUser(currentId + 1).subscribe(user => {
      this.currentUser.set(user);
    });
  }
}

Dans cet exemple, l'effet se déclenchera uniquement lorsque currentUser change, même si nous lisons également la valeur de counter. C'est parce que nous avons utilisé untracked(this.counter).

OPTIMISATION

L'utilisation de untracked() peut réduire considérablement le nombre de calculs effectués dans votre application, ce qui améliore les performances, surtout si vous avez des effets ou des calculs coûteux.

2. Calculer une valeur dérivée une seule fois

Parfois, vous voulez calculer une valeur dérivée basée sur un signal, mais vous ne voulez pas qu'elle se recalcule à chaque changement du signal. Voici comment vous pouvez le faire :

ts
import { Component, computed, signal, untracked } from '@angular/core';
import { User } from './user.interface';

@Component({
  selector: 'app-user-stats',
  template: `
    <div>User count: {{ userCount() }}</div>
    <div>Initial average age: {{ initialAverageAge() }}</div>
    <button (click)="addUser()">Add User</button>
  `
})
export class UserStatsComponent {
  // Liste d'utilisateurs qui va changer
  users = signal<User[]>([
    { id: 1, name: 'John', username: 'john', email: '[email protected]', age: 25 },
    { id: 2, name: 'Jane', username: 'jane', email: '[email protected]', age: 30 }
  ]);
  
  // Compte simple des utilisateurs (réactif)
  userCount = computed(() => this.users().length);
  
  /**
   * Calcule l'âge moyen initial et ne se met pas à jour quand les utilisateurs changent
   * Utilise untracked() pour empêcher la réactivité sur les changements de users
   */
  initialAverageAge = computed(() => {
    // Utilisation d'untracked pour lire users sans créer de dépendance
    const userList = untracked(this.users);
    
    if (userList.length === 0) return 0;
    
    const totalAge = userList.reduce((sum, user) => {
      return sum + (user.age || 0);
    }, 0);
    
    return totalAge / userList.length;
  });
  
  addUser() {
    this.users.update(users => [
      ...users, 
      { 
        id: users.length + 1, 
        name: `User ${users.length + 1}`, 
        username: `user${users.length + 1}`, 
        email: `user${users.length + 1}@example.com`,
        age: 20 + Math.floor(Math.random() * 30)
      }
    ]);
  }
}

Dans cet exemple, initialAverageAge est calculé une seule fois et ne changera pas même si users change, car nous avons utilisé untracked(this.users).

3. Exécuter du code externe sans créer de dépendances

Quand vous voulez appeler du code externe (comme un service) depuis un effet sans que ce code externe crée des dépendances supplémentaires :

ts
import { Component, effect, inject, signal, untracked } from '@angular/core';
import { User } from './user.interface';
import { LoggingService } from '../../core/services/logging.service';

@Component({
  selector: 'app-user-logger',
  template: `<div>Current user: {{ currentUser()?.name }}</div>`
})
export class UserLoggerComponent {
  private loggingService = inject(LoggingService);
  currentUser = signal<User | null>(null);
  
  constructor() {
    /**
     * Effet qui journalise les changements d'utilisateur
     * Le service de journalisation est appelé dans un contexte untracked
     */
    effect(() => {
      const user = this.currentUser();
      
      // Utilisation d'untracked pour s'assurer que d'éventuels signaux lus
      // par loggingService ne créent pas de dépendances pour cet effet
      untracked(() => {
        this.loggingService.logUserChange(user);
      });
    });
  }
}

Peut-on modifier un signal dans untracked() ?

ATTENTION

Utiliser untracked() n'empêche pas un signal d'envoyer des notifications quand sa valeur change. Si vous appelez set() ou update() sur un signal à l'intérieur d'une fonction untracked(), toutes les dépendances de ce signal seront toujours notifiées.

ts
effect(() => {
  // Ceci NE FONCTIONNE PAS comme prévu. Les dépendances de counter
  // seront toujours notifiées même si on utilise untracked
  untracked(() => {
    this.counter.update(count => count + 1);
  });
});

Conseils d'utilisation

  1. Utilisez untracked() lorsque vous avez besoin de lire un signal sans créer de dépendance
  2. Particulièrement utile dans les effets qui ne devraient pas réagir à certains signaux
  3. Permet d'optimiser les performances en évitant des calculs inutiles
  4. Utile pour les calculs initiaux qui ne doivent pas être réactifs

Exemple complet

Voici un composant complet qui montre plusieurs utilisations de untracked() :

Conclusion

La fonction untracked() est un outil puissant dans l'écosystème des signaux d'Angular qui vous permet de contrôler avec précision quels signaux déclenchent quels calculs ou effets. En l'utilisant judicieusement, vous pouvez améliorer les performances de votre application et créer des patterns de réactivité plus sophistiqués.

Souvenez-vous des points clés :

  • untracked() permet de lire un signal sans créer de dépendance
  • Particulièrement utile dans les effets et les signaux calculés
  • Ne peut pas empêcher un signal de notifier ses dépendances lors de sa modification
  • Permet d'optimiser les performances en évitant des recalculs inutiles

Avec cette fonction, vous avez maintenant un contrôle encore plus précis sur la réactivité de votre application Angular.