Search code examples
angularangular-signals

Angular Signals: programming semaphore


I want to run an action just once when a condition changes (to true), e.g.

areAllDetailsLoaded() => run a function

I was thinking to code it as a Signal like

areAllDetailsLoaded=signal(false);

and using it as

effect( () =>  {
   if (areAllDetailsLoaded()) {
          storage().getDetails() ....... run code

The problem I'm facing is that obviously the Signal storage is also taken into account and anytime the storage() changes the code runs. Which is not something I want. After areAllDetailsLoaded is changed to true the code should run only once.

Honestly I'm running out of ideas. Or perhaps my understanding of Angular Signals is not good enough. Therefore a boiler template pattern would be appreciated - perfered with Signals.


Solution

  • I found some very nice observations.

    1. Only the signal reads at the top level are triggering the effect, not sure if it's by design.

    2. The effect returns a reference, with a destroy property to destroy the effect and make sure the signal runs only once.

    Full code:

    import { Component, effect, signal } from '@angular/core';
    import { bootstrapApplication } from '@angular/platform-browser';
    import 'zone.js';
    
    @Component({
      selector: 'app-root',
      standalone: true,
      template: `
        <button (click)="areAllDetailsLoaded.set(true)">areAllDetailsLoaded set true</button>
      `,
    })
    export class App {
      areAllDetailsLoaded = signal(false);
      storage = signal(false);
      name = 'Angular';
    
      constructor() {
        const effectRef = effect(() => {
          console.log('effect runs');
          // const storage1 = this.storage(); // un comment this to see the effect running for each change of storage
          if (this.areAllDetailsLoaded()) {
            const storage = this.storage();
            console.log('code runs');
            effectRef?.destroy();
          }
        });
      }
    
      ngOnInit() {
        setInterval(() => {
          this.storage.set(!this.storage());
          console.log('interval', this.storage());
        }, 1000);
      }
    }
    
    bootstrapApplication(App);
    

    Stackblitz Demo