angulartypescriptsignalsangular-signalslinkedsignal

Angular linked signal dependent on two other signals


In new Angular (19+) can I create linkedSignal that depends on two other signals? In the example

import { signal, linkedSignal } from '@angular/core';

const sourceSignal = signal(0);
const updatedSignal = linkedSignal({
  source: sourceSignal,
  computation: () => sourceSignal() * 5,
});

updatedSignal depends on sourceSignal.

Can I make it dependent on sourceSignal1 and sourceSignal2?


Solution

  • What is purpose of source: in this case. Looks like signal is capable to detect input signals automatically?

    Ans: If you want to access the previous state of the linked signal, or to improve readability of the linked signal (if there are lot of source signals you can use this pattern of source and computation, else a plain signal will suffice. As per the documentation, the comments mention as follows:

    linked_signal.ts - source code

    Creates a writable signals whose value is initialized and reset by >the linked, reactive computation. This is an advanced API form where the computation has access to >the previous value of the signal and the computation result.

    @developerPreview


    Yes, you can specify either an array or an object as the return value of the source function.

    The most important information to type the linked signal to the below interface:

    export declare function linkedSignal<S, D>(options: {
        source: () => S;
        computation: (source: NoInfer<S>, previous?: {
            source: NoInfer<S>;
            value: NoInfer<D>;
        }) => D;
        equal?: ValueEqualityFn<NoInfer<D>>;
    }): WritableSignal<D>;
    

    and not:

    export declare function linkedSignal<D>(computation: () => D, options?: {
        equal?: ValueEqualityFn<NoInfer<D>>;
    }): WritableSignal<D>;
    

    is to add the typing to the linkedSignal, the first type is the interface of the object returned by the source callback. The second is the type of the value returned by the linked signal.

    updatedSignal = linkedSignal<TypedLinkedSignal, number>({ ... });
                                               ^       ^
                  interface of source callback |       | return type of linked signal     
    

    Using Object:

    Firstly we should define an interface, which contains the input signals that are used for the linked signal, this interface is necesssary so that we can strictly type the object that is used to trigger the reactivity of the linked signal.

    export interface TypedLinkedSignal {
      sourceSignal: number;
      sourceSignal2: number;
    }
    

    We can return an object whose properties are the individual items where the value is the executed signal.

    Notice, that we can use the first argument of the function of computation is the object we specified in source, but with the signals executed and the values ready for usage.

    I have named the first argument as response (you can name it anything) and use the values for the computation.

    In the below example. We have two model which are signals, we place then inside an object, which is returned by the source callback. Finally we use the returned values to compute the linked signal.

    import { Component } from '@angular/core';
    import { bootstrapApplication } from '@angular/platform-browser';
    import { model, linkedSignal } from '@angular/core';
    import { FormsModule } from '@angular/forms';
    
    export interface TypedLinkedSignal {
      sourceSignal: number;
      sourceSignal2: number;
    }
    
    @Component({
      selector: 'app-root',
      imports: [FormsModule],
      template: `
        <input [(ngModel)]="sourceSignal"/>
        <input [(ngModel)]="sourceSignal2"/>
        <input [ngModel]="updatedSignal()"/>
      `,
    })
    export class App {
      sourceSignal = model(0);
      sourceSignal2 = model(0);
      updatedSignal = linkedSignal<TypedLinkedSignal, number>({
        source: () => ({
          sourceSignal: this.sourceSignal(),
          sourceSignal2: this.sourceSignal2(),
        }),
        computation: (params: TypedLinkedSignal) => {
          return params.sourceSignal * params.sourceSignal2 * 5;
        },
      });
    }
    
    bootstrapApplication(App);
    

    Stackblitz Demo


    Using Array:

    The above explanation stays the same, only difference is we use a different data structure - array.

    So the way we access these properties is different.

    import { Component } from '@angular/core';
    import { bootstrapApplication } from '@angular/platform-browser';
    import { model, linkedSignal } from '@angular/core';
    import { FormsModule } from '@angular/forms';
    
    @Component({
      selector: 'app-root',
      imports: [FormsModule],
      template: `
        <input [(ngModel)]="sourceSignal"/>
        <input [(ngModel)]="sourceSignal2"/>
        <input [ngModel]="updatedSignal()"/>
      `,
    })
    export class App {
      sourceSignal = model(0);
      sourceSignal2 = model(0);
      updatedSignal = linkedSignal<Array<number>, number>({
        source: () => [this.sourceSignal(), this.sourceSignal2()],
        computation: ([sourceSignal, sourceSignal2]: Array<number>) => {
          return sourceSignal * sourceSignal2 * 5;
        },
      });
    }
    
    bootstrapApplication(App);
    

    Stackblitz Demo


    The advantage of linked signal is that we can change the value using the set or update method, in contrast to computed which was read only.

    When the source signals change. The value changed via set or update is reset.