I'm displaying a list of tasks in a todo with an ngFor and a checkbox to toggle the task status. I'm using store from ngrx with the following state interface:
export interface State {
tasks: Task[];
index: { [_id: string]: Task };
};
That's the reducer which is in charge of updating the task when the status change:
case TaskActions.UPDATE_TASK_SUCCESS: {
const task = action.payload;
state.index[task._id] = Object.assign(state.index[task._id], task);
return state;
}
And here is the task component which take the task as @Input and has changeDetectionStrategy set to onPush:
import { Component, OnInit, Input, ChangeDetectionStrategy, DoCheck } from '@angular/core';
import {Store} from '@ngrx/store';
import { AppState } from '../../../../domain';
import { TaskActions } from '../../../../domain/actions';
@Component({
selector: 'task',
templateUrl: './task.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
styleUrls: ['./_task.component.scss']
})
export class TaskComponent implements OnInit, DoCheck {
@Input() task;
constructor(
private store: Store<AppState>,
private TaskActions: TaskActions
) { }
ngOnInit() {
}
ngDoCheck() {
console.warn('Check');
}
public toggleTaskStatus(_task) {
var new_task = Object.assign({}, _task, {['status']: (_task.status == 'todo' ? 'done' : 'todo')});
this.store.dispatch(this.TaskActions.updateTask(new_task));
}
}
With this configuration, the first time I click on the checkbox the task inside the store gets updated, but the UI doesn't reflect any change. From the second time instead, the UI start toggling its status but not according the store state (when task status is 'todo' in the store, the UI is 'done'). Moreover, to get updated the UI doesn't wait for the success action in the reducer (the service is creating an observable with a slight timeout of 1 second).
When changeDetectionStrategy is set to default, everything seems working correctly, with the UI updating according to store state.
What am I doing wrong? Is regarding the immutability of the state or some issues inside the component?
A reducer MUST be immutable.
Here :
You're mutating the state.
This line of code
changeDetection: ChangeDetectionStrategy.OnPush
tells Angular that if the @Input has the same address in memory (reference), it should not be updated (to improve performance).In your case, you mutate the state and return the same (state) object.
That's why it's working when you remove the line
changeDetection: ChangeDetectionStrategy.OnPush
.To make it work, you should rather have :
To avoid mutating the state, you might want to setup ngrx-store-freeze. It will throw an error if a state mutation occurs.
Just to speak a little bit about
ES7
syntax,Typescript
now supports the spread operator on objects and we might also have this syntax :But
Angular
doesn't supportTypescript 2.1.4
for now and using this version will break your app. It'll hopefully be supported soon. CF that PR. (thanks to semver, it should land in Angular 4.0 in march 2017).