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);
}
}
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;
});
}
}