htmlangularangular-formsangular-signalsangular20

How to handed a relationship between two signals by linkedSignals in Angular v20?


I have to mange a relation for X*2=Y, there Y and Y are input fields.

In the moment, I trigger by a change of the input field the related variable as X changed => Y=X*2 Y changed => X=Y/2

import { Component, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';


@Component({
  selector: 'app-root',
   imports: [FormsModule],

  template: `
    <h1>{{ title }}</h1>
<input type="number" 
[(ngModel)]="X" 
(change)="FnX()"
/>
 *2 =  
<input type="number" 
[(ngModel)]="Y" 
(change)="FnY()"
>

    `,

})
export class App {
  protected title = 'Relationshiphandling:  2*X=Y'

  
X=signal(0);
Y=signal(0);

FnX() {
  this.Y.set(2 * this.X());
};

FnY() {
  this.X.set(this.Y() / 2);
};

}

Now question is, is it possible to solve such cycles with linkedSignal ? As this AI generated example will not run:

X: WritableSignal<number> = linkedSignal({
    source: () => this.Y(),
    computation: (_source: number, _prev?: { source: number; value: number }) => {
      const newValue = _source / 2;
      **if (this.X() == newValue) { return 0};** // Do nothing if the same value
     console.log( "this.X.set(newValue)")
      this.X.set(newValue);
      return  newValue;
    }
});


Y: WritableSignal<number> = linkedSignal({
    source: () => this.X(),
    computation: (_source: number, _prev?: { source: number; value: number }) => {
      const newValue = _source * 2;
      **if (this.Y() == newValue) { return 0};** // Do nothing if the same value

      this.Y.set(newValue)
       console.log( "this.Y.set(newValue)")
      return newValue;
    }
});

Solution

  • With effect:

    I think it is not possible with linkedSignal due to circular dependency issue.

    Instead go for effect along with a combination of untracked (Which can be used to ignore signal changes inside the callback).

    First we place the signal we want to track at the top of the effect and the signal update inside the untracked callback, this update will not retrigger the other effect and will evaluate only once.

    constructor() {
      effect(() => {
        const x = this.X();
        untracked(() => {
          this.Y.set(2 * x);
        });
      });
      effect(() => {
        const y = this.Y();
        untracked(() => {
          this.X.set(y / 2);
        });
      });
    }
    

    Full Code:

    import { Component, signal, effect, untracked } from '@angular/core';
    import { bootstrapApplication } from '@angular/platform-browser';
    import { FormsModule } from '@angular/forms';
    
    @Component({
      selector: 'app-root',
      imports: [FormsModule],
      template: `
        <h1>{{ title }}</h1>
        <input type="number" 
        [(ngModel)]="X" 
        />
        *2 =  
        <input type="number" 
        [(ngModel)]="Y" 
        >
      `,
    })
    export class App {
      protected title = 'Relationshiphandling:  2*X=Y';
    
      X = signal(0);
      Y = signal(0);
    
      constructor() {
        effect(() => {
          const x = this.X();
          untracked(() => {
            console.log('y set');
            this.Y.set(2 * x);
          });
        });
        effect(() => {
          const y = this.Y();
          untracked(() => {
            console.log('x set');
            this.X.set(y / 2);
          });
        });
      }
    }
    
    bootstrapApplication(App);
    

    Stackblitz Demo


    Without effect:

    If you are not a fan of effect, then go for (ngModelChange) and update the other signal manually.

    Full Code:

    import { Component, signal, effect, untracked } from '@angular/core';
    import { bootstrapApplication } from '@angular/platform-browser';
    import { FormsModule } from '@angular/forms';
    
    @Component({
      selector: 'app-root',
      imports: [FormsModule],
      template: `
        <h1>{{ title }}</h1>
        <input type="number" 
        [(ngModel)]="X" 
        (ngModelChange)="FnX()"
        />
        *2 =  
        <input type="number" 
        [(ngModel)]="Y" 
        (ngModelChange)="FnY()"
        >
      `,
    })
    export class App {
      protected title = 'Relationshiphandling:  2*X=Y';
    
      X = signal(0);
      Y = signal(0);
    
      FnX() {
        this.Y.set(2 * this.X());
      }
    
      FnY() {
        this.X.set(this.Y() / 2);
      }
    }
    
    bootstrapApplication(App);
    

    Stackblitz Demo