javascriptarraysangularsignalsangular-signals

Angular Signal not Recomputing


In the Angular (v18) code below, a static list of 3 allTasks is filtered by user id when the component renders and displays (for userId u3) 2 tasks via the computed tasks property.

There is an event which calls onCompleteTask() which successfully removes the task from allTasks. The length of allTasks is logged as being reduced from 3 to 2. We expect the tasks() property to now return 1 instead of 2 filtered tasks.

However, the component does not re-render - the computed tasks function is never called even though allTasks has been updated - indeed with a new reference.

Why is tasks not updated when allTasks changes?

import { Component, input, computed } from '@angular/core';
//import { NgIf } from '@angular/common';

import { TaskComponent } from './task/task.component';

import { Task } from './task.model';

@Component({
  selector: 'app-task-list',
  standalone: true,
  templateUrl: './tasks.component.html',
  imports: [TaskComponent],
  styleUrls: ['./tasks.component.css'],
})
export class TaskListComponent {
  userId = input<string>();
  userName = input<string>();
  allTasks: Task[] = [
    {
      id: 't1',
      userId: 'u1',
      title: 'Master Angular',
      summary: 'Learn all the basic and advanced features of Angular & how to apply them.',
      dueDate: '2025-12-31',
    },
    {
      id: 't2',
      userId: 'u3',
      title: 'Build first prototype',
      summary: 'Build a first prototype of the online shop website',
      dueDate: '2024-05-31',
    },
    {
      id: 't3',
      userId: 'u3',
      title: 'Prepare issue template',
      summary: 'Prepare and describe an issue template which will help with project management',
      dueDate: '2024-06-15',
    },
  ];
  tasks = computed<Task[]>(() => {
    console.log('Computing tasks for user', this.userId());
    return this.allTasks.filter((task) => task.userId === this.userId());
  });

  constructor() {}

  onCompleteTask(id: string) {
    let newTasks = this.allTasks.filter((task) => task.id !== id);
    this.allTasks = newTasks;
    console.log('Completed task', id, this.allTasks.length);
  }
}


Solution

  • The onCompleteTask is not changing any signals inside it, so there is no need for the computed to run (only when the signals inside the computed are changed, the value is recomputed).

    If you want to run, then just convert the array to a signal and use update method to return a new memory reference (Create a new array using array de-structuring ).

    By doing this, the computed detects the allTasks signal has changed, since arrays and objects are stored as memory reference and since we do newTasks, a new memory reference is returned, so the compute will run and recalculate using the new allTasks.

    import { Component, input, computed, WritableSignal, signal } from '@angular/core';
    //import { NgIf } from '@angular/common';
    
    import { TaskComponent } from './task/task.component';
    
    import { Task } from './task.model';
    
    @Component({
      selector: 'app-task-list',
      standalone: true,
      templateUrl: './tasks.component.html',
      imports: [TaskComponent],
      styleUrls: ['./tasks.component.css'],
    })
    export class TaskListComponent {
      userId = input<string>();
      userName = input<string>();
      allTasks: WritableSignal<Task[]> = signal([
        {
          id: 't1',
          userId: 'u1',
          title: 'Master Angular',
          summary: 'Learn all the basic and advanced features of Angular & how to apply them.',
          dueDate: '2025-12-31',
        },
        {
          id: 't2',
          userId: 'u3',
          title: 'Build first prototype',
          summary: 'Build a first prototype of the online shop website',
          dueDate: '2024-05-31',
        },
        {
          id: 't3',
          userId: 'u3',
          title: 'Prepare issue template',
          summary: 'Prepare and describe an issue template which will help with project management',
          dueDate: '2024-06-15',
        },
      ]);
      tasks = computed<Task[]>(() => {
        console.log('Computing tasks for user', this.userId());
        return this.allTasks().filter((task) => task.userId === this.userId());
      });
    
      constructor() {}
    
      onCompleteTask(id: string) {
        this.allTasks.update((allTasksPrev: Task[]) => {
          let newTasks = allTasksPrev.filter((task) => task.id !== id);
          console.log('Completed task', id, newTasks.length);
          return newTasks;
        });
      }
    }