Skip to content

Angular 17 - Mutable signals are not supported #52735

@ProfessorPinda

Description

@ProfessorPinda

I noticed that a computed signal does not always fire when one of the dependencies has updated. Let me give a quick example:

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CommonModule, RouterOutlet],
  template: '
  <ul>
    @for(person of filteredPersons(); track person.name) {
      <li>
        {{ person.name }} has {{ person.money }} dollars
      </li>
    }
  </ul>
  <button (click)="previousPage()">Previous Page</button>
  <button (click)="nextPage()">Next Page</button>
  <small>Page {{ pagingState().page }} of {{ totalPages() }}</small>
  ',
  styles: [],
})
export class AppComponent {

  private persons = this.personService.persons;

  filteredPersons = computed(() => {
    console.log('One of the dependents has changed :D')
    const persons = this.persons();
    const pagingState = this.pagingState();
    const amountToSkip = (pagingState.page - 1) * pagingState.pageSize;
    return persons?.slice(amountToSkip, amountToSkip + pagingState.pageSize);;
  })

  pagingState = signal({
    page: 1,
    pageSize: 5
  })

  totalPages = computed(() => Math.ceil(this.persons()?.length / this.pagingState().pageSize));

  constructor(private personService: PersonService) {}

  nextPage(): void {
    this.pagingState.update((state) => {
      if (state.page < this.totalPages()) state.page += 1
      return { ...state}; // Cant return state directly since it wont pickup the change whyy????
    })
  }

  previousPage(): void {
    this.pagingState.update((state) => {
      if (state.page > 1) state.page -= 1
      return { ...state}; // Cant return state directly since it wont pickup the change whyy????
    })
  }
}`

Service
`  private _persons = signal<Person[]>(undefined);
  get persons() {
    return this._persons.asReadonly();
  }

  hasLoaded = computed(() => this.persons() !== undefined);

  constructor() {
    this.refresh();
  }

  getAll(): Observable<Person[]> {
    return of([
      {
        name: 'Alice',
        money: 150,
      },
      {
        name: 'Bob',
        money: 280,
      },
      {
        name: 'Carol',
        money: 210,
      },
      {
        name: 'David',
        money: 320,
      },
      {
        name: 'Eva',
        money: 180,
      },
      {
        name: 'Frank',
        money: 270,
      },
      {
        name: 'Grace',
        money: 190,
      },
      {
        name: 'Helen',
        money: 230,
      }
    ]).pipe(delay(500))
  }

  refresh(): void {
    this._persons.set(undefined);
    this.getAll().subscribe((persons) => {
      this._persons.set(persons);
    });
  }```

Whenever I press one of the page change buttons the pagingState gets updated with the new page value. The correct state is displayed in html but for some reason the computed signal filteredPersons doesnt get triggered. Apparantly it does not see the change which I would have expected. Returning a shallow copy of the state in nextPage and previousPage solves this. If this is expected behavior it would seem really counterintuitive.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3An issue that is relevant to core functions, but does not impede progress. Important, but not urgentarea: coreIssues related to the framework runtimecross-cutting: signalsopen for contributionsAn issue that is suitable for a community contributor (based on its complexity/scope).

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions