angularsignalspass-by-referenceeffectangular-signals

Why is my Angular signal effect not working?


The signal is created in ProductSidenavService:

@Injectable({
  providedIn: 'root'
})
export class ProductSidenavService {

  public productSidenavSignal = signal<ProductSidenavModel>( new ProductSidenavModel())

   public updateProductSidenav(productSidenav:ProductSidenavModel): void {
      this.productSidenavSignal.set(productSidenav)
    console.log('ProductSidenavService.productSidenavSignal', this.productSidenavSignal())
  }
}

ProductSidenav.html contains checkboxes, when modified, call getProducts in ProductSidenavComponent:

<section>
<h5>{{productSidenav.category.title}}</h5>
<ul class="list-unstyled">
    <li *ngFor="let category of productSidenav.category.categories">
        <input [(ngModel)]="category.checked" name="category" 
            type="checkbox" class="me-2"
            (change)="getProducts()">
        <label class="form-label">{{category.name}}</label>
    </li>
</ul>
</section>

getProducts of productSidenavComponent calls productSidenavService.updateProductSidenav(productSidenav) to update the signal with the modified productSidenav. (updateProductSidenav is shown above in ProductSidenavService).

export class ProductSidenavComponent {
  private productSidenavService = inject(ProductSidenavService)

  public productSidenav = this.productSidenavService.productSidenavSignal();

  public getProducts = () => {
    console.log('productSidenav', this.productSidenav)
    this.productSidenavService.updateProductSidenav(this.productSidenav)
  }
}

ProductComponent contains an effect. My expectation is when the signal is updated in productSidenavService.updateProductSidenav, the effect is called with the updated value. but the effect is not called.

ProductComponent:

export class ProductComponent {
  private productSidenavService = inject(ProductSidenavService)
  private productService = inject(ProductService)
  public productSidenav = this.productSidenavService.productSidenavSignal();

  constructor() {
    effect(() => { 
      console.log('ProductComponent.productSidenav', this.productSidenav);
      this.productService.getProducts(this.productSidenavService.productSidenavSignal());
    });
  }

}

ProductSidenavModel:

export class ProductSidenavModel {
category = {
    title: 'Category',
    categories: [
        {
            name: 'Living Room Sets',
            checked: false,
        },
        {
            name: 'Sectionals',
            checked: false,
        },
        {
            name: 'Sofas',
            checked: false,
        },
        {
            name: 'Chair and Seating',
            checked: false,
        }
        ],
};

Solution

  • The effect will only fire when the value inside the signal changes.

    As you know in JS, arrays and objects are stored as references in memory, updating the inner properties and just returning the same object, does not result in a memory reference change. Hence the effect does not fire.

    Instead use someArray.slice() (for Arrays) and Object Destructuring ({...someSignal() }) (for objects) to create a new memory reference so that the signal pickups the change.

    this.productSidenavSignal.set({ ...productSidenav })
    

    Signals And Array Mutability In Angular 18 - Ben Nadel

    Github issue - Angular 17 - Mutable signals are not supported

    Reference Answer - more detailed