Logo
Published on

Angular Signals — Using the untracked() Function to Prevent Dependency Tracking

Authors
  • Name
    Twitter

Before going through this article, if you are not familiar with Angular signals, I’d recommend reading my previous post on signals — Angular Signals🚦: New Change detection strategy.

For every change in the signal, computed and effect functions that are interested in the signal, will be recomputed & executed respectively. There are cases when we don't want this recomputation to occur for the signal change. In Angular signals, all the dependencies will run inside a reactive context, to avoid this recomputation, it should be run inside a non-reactive context.

Angular provided untracked function which allows you to execute any function in a non-reactive context and sometimes it will return values.

Let’s see some of the use cases of the untracked function.

Calculate the computed signal property only once

In some cases, you might want to have a computed signal that will compute its value only once and it will not care about further change. In such cases, you can use untracked function inside the computed signal to read signal value without tracking dependencies.

export class AppComponent {
  count = signal(1);

  revenue = computed(() => untracked(() => this.count() * 5));
}

In this above code, the revenue computed signal will be computed only once(value would be 5) based on its dependency count. For subsequent set operations, revenue will not recalculate and will remain in the same value.

Reading without tracking dependencies in effect method

This is a very common use that was pointed out by the angular team. You can use this method to have an untracked reading of the signal values inside the effect method so that for further change in the signal, the effect will not be invoked.

export class AppComponent {
  count = signal(1);

  revenue = computed(() => untracked(() => this.count() * 5));

  constructor() {
    effect(() => {
      console.log("Count effect invoked " + untracked(this.count));
    });
  }
}

Unwrap signal value

Well, this is a very simple use case, you will probably not need it but I think it's worth mentioning. The untracked function will return a value that we can use. You can use this function to simply unwrap or execute any arbitrary function.

In the below code, nCount simply unwrap the signal value and factor will hold the result of the arbitrary function run inside the untracked function.

export class AppComponent {
  count = signal(1);
  users = signal(0);
  nCount = untracked(this.count);
  factor = untracked(() => {
    const count = this.count();
    const users = this.users() || 5;
    return count / users;
  });

  revenue = computed(() => untracked(() => this.count() * 5));

  constructor() {
    effect(() => {
      console.log("Count effect invoked " + untracked(this.count));
    });
  }
}

Is it possible to set/update/mutate any signal value without notifying the dependencies using an untracked function?

The answer is a big NO.

Setting or changing the signal values inside an untracked function will not prevent it from tracking and notifying the dependencies.

Setting the signal value inside the untracked function will track dependencies and notify the interested consumers.

In the below code, setting users signal inside the untracked function will not prevent it from notifying the consumers and the effect function that uses the users function will be called.

export class AppComponent {
  toggle = signal(true);
  count = signal(1);
  users = signal(0);
  nCount = untracked(this.count);
  factor = untracked(() => {
    const count = this.count();
    const users = this.users() || 5;

    return count / users;
  });

  revenue = computed(() => untracked(() => this.count() * 5));

  constructor() {
    effect(() => {
      console.log("Empty effect method");
      // SETTING THE users signal
      untracked(() => this.users.set(1));
    });

    effect(() => {
      // Will be called when
      // users signal is set in the previous effect method.
      console.log("User effect invoked " + this.users());
    });
  }
}

I hope you like reading this article. If you find it useful, please follow me on Medium or Twitter for content like this.

Happy coding…