javascripthtmlangulartypescriptangular-signals

How to Update an Immutable Signal Object which is itself a AppComponent Property?


I am struggling with a simple plain object, which is itself a property of AppComponent. This object "x" is wrapped as a writable signal. I want to implement a method which updates the own property of the signal object "x".

Further the object "x" itself should be immutable.

Here is the code:

import { bootstrapApplication } from '@angular/platform-browser';
import { Component, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';

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

   <h1> {{title}}<h1>
    <hr>
   <p> Value of the X Property X: {{x().Value}} <p>
  `,

  styles: ['html { line-height: 1.15;} input{border: 0.1em solid lightgreen;}'],
})
export class AppComponent {
   protected title = 'How to update an Immutable Signal Object which is itsels a AppComponent Proberty? ';

 readonly  x = signal<IVariableBuilder> ({
        variableName: 'X',
        Value: 0,
        unit: ''
});
 
upDateMethod(){
this.x.update(f => f.Value= 1234 )  // Does not work
 }
}

interface IVariableBuilder {
  readonly variableName: string; // to get information, which variable was changed by <input>
  readonly Value: number,
  readonly  unit: string;  
}

bootstrapApplication(AppComponent); 

Also refer to myStackbiltzCode


Solution

  • Without Mutation (Messy and not maintainable for large objects):


    We should use the equal property and write a custom check which determines when the signal should be updated, this will help prevent the mutation.

    We check if each individual value is changed and return the equal value, this will trigger change detection, this is kind of messy and the method below should be preferred, since for complex objects, maintaining this callback is a pain, not to mention the extra lines of code, which is mostly not needed.

    readonly x = signal<IVariableBuilder>(
      {
        variableName: 'X',
        Value: 0,
        unit: '',
      },
      {
        equal: (prevObj: any, newObj: any) => {
          return (
            prevObj.variableName === newObj.variableName ||
            prevObj.Value === newObj.Value ||
            prevObj.unit === newObj.unit
          );
        },
      }
    );
    

    Full Code:

    import { bootstrapApplication } from '@angular/platform-browser';
    import { Component, signal } from '@angular/core';
    import { FormsModule } from '@angular/forms';
    
    @Component({
      selector: 'app-root',
      imports: [FormsModule],
      template: `
    
       <h1> {{title}}<h1>
        <hr>
       <p> Value of the X Property X: {{x().Value}} <p>
         <button (click)="upDateMethod();">just update no mutate</button>
      `,
    
      styles: ['html { line-height: 1.15;} input{border: 0.1em solid lightgreen;}'],
    })
    export class AppComponent {
      protected title =
        'How to Update an Immutable Signal Object which is itself a AppComponent Property?';
    
      readonly x = signal<IVariableBuilder>(
        {
          variableName: 'X',
          Value: 0,
          unit: '',
        },
        {
          equal: (prevObj: any, newObj: any) => {
            return (
              prevObj.variableName === newObj.variableName ||
              prevObj.Value === newObj.Value ||
              prevObj.unit === newObj.unit
            );
          },
        }
      );
    
      upDateMethod() {
        this.x.update((f: any) => {
          f.Value = 1234;
          return f; // return the same memory reference
        }); // Does not work
      }
    }
    
    interface IVariableBuilder {
      readonly variableName: string; // to get information, which variable was changed by <input>
      readonly Value: number;
      readonly unit: string;
    }
    
    bootstrapApplication(AppComponent);
    
    

    Stackblitz Demo


    With Mutation (Less code and easily maintainable):


    You need to update the memory reference using Object Destructuring for the signal to detect the value has changed, because updating the inner property of the signal, is not going to change the memory reference (arrays and objects are stored as memory references).

    The signals detect change when the actual value is changed, so we need to use destructuring to create a new memory reference, the signal will compare the old and the new memory reference, detect a change has been made and trigger change detection which updates the view.

    upDateMethod() {
      this.x.update((f: any) => {
        f.Value = 1234;
        return { ...f };
      }); // Does not work
    }
    

    Full Code:

    import { bootstrapApplication } from '@angular/platform-browser';
    import { Component, signal } from '@angular/core';
    import { FormsModule } from '@angular/forms';
    
    @Component({
      selector: 'app-root',
      imports: [FormsModule],
      template: `
    
       <h1> {{title}}<h1>
        <hr>
       <p> Value of the X Property X: {{x().Value}} <p>
         <button (click)="upDateMethod();"></button>
      `,
    
      styles: ['html { line-height: 1.15;} input{border: 0.1em solid lightgreen;}'],
    })
    export class AppComponent {
      protected title =
        'How to Update an Immutable Signal Object which is itself a AppComponent Property?';
    
      readonly x = signal<IVariableBuilder>({
        variableName: 'X',
        Value: 0,
        unit: '',
      });
    
      upDateMethod() {
        this.x.update((f: any) => {
          f.Value = 1234;
          return { ...f };
        }); // Does not work
      }
    }
    
    interface IVariableBuilder {
      readonly variableName: string; // to get information, which variable was changed by <input>
      readonly Value: number;
      readonly unit: string;
    }
    
    bootstrapApplication(AppComponent);
    
    

    Stackblitz Demo