Angular v10, NGRX v10
I have a Plan object and one of its properties is an array of Task objects. In my TaskService I make a deep clone (lodash) of my Plan object, update some of the properties in my Tasks and then dispatch an Action to update the Plan in my Store. Curiously when I console.log(action.payload) in my Reducer the Task properties have reverted back to their original values! Four days looking at this and I just can't see it.
The process is started by dragging a Task on a Gantt Chart after which TaskService.updateTasks(tasksToUpdate) is called passing any Tasks which have changed following user interaction.
I have created a hugely simplified version of my updateTasks method in my TaskService and I still get the same problem. My simplified updateTasksSimple method is as follows:
public updateTasksSimple(tasks: ITask[]): void {
const updatedPlan: IPlan = this.coreDataService.cloneDeep(this.currentPlanWithChildren);
const updatedTasks: ITask[] = this.coreDataService.cloneDeep(tasks);
const tasksToUpdate = updatedTasks.filter(t => t.state !== objectState.Unchanged);
// Check we have a tasksToUpdate object.
if (tasksToUpdate !== null) {
// Ensure we have some Tasks to update.
if (tasksToUpdate.length > 0) {
// Logging
tasksToUpdate.forEach(t => {
if (t.state !== objectState.Unchanged) {
console.log(`Updating Task - ${t.title}, Summary: ${t.summary}, Start: ${t.start}, End: ${t.end}, Duration: ${t.durationHours}, Constraint: ${t.taskConstraintType.name}`);
}
});
// Map updated tasks
updatedPlan.tasks = tasksToUpdate.map((t: ITask) => ({
...t,
start: new Date(2021, 2, 26, 16),
end: new Date(2021, 2, 26, 22),
durationHours: 6,
}));
// Logging
console.log(`updateTasksSimple Tasks before dispatch:`);
updatedPlan.tasks.forEach(t => {
console.log(`${t.title}, start: ${t.start}, end: ${t.end}, durationHours: ${t.durationHours}, constraint: ${t.taskConstraintType.name}`);
})
// Dispatch an Action to update the Store.
this.store.dispatch(new planActions.SetCurrentPlanWithChildren(updatedPlan));
}
}
}
My Action is defined as follows:
export class SetCurrentPlanWithChildren implements Action {
readonly type = PlanActionTypes.SetCurrentPlanWithChildren;
constructor(public payload: IPlan) { }
}
I dispatch my Action as follows:
private dispatchUpdate() {
// Logging
console.log(`dispatchUpdate called. Tasks:`);
this.updatedPlan.tasks.forEach(t => {
console.log(`${t.title}, start: ${t.start}, end: ${t.end}, durationHours: ${t.durationHours}, constraint: ${t.taskConstraintType.name}`);
})
this.store.dispatch(new planActions.SetCurrentPlanWithChildren(this.updatedPlan));
};
As you can see I am temporarily logging the Tasks to see exactly what I am dispatching from my TaskService and everything is fine here.
My Reducer is as follows:
case PlanActionTypes.SetCurrentPlanWithChildren:
console.log(`PlanActionTypes.SetCurrentPlanWithChildren.`, action.payload);
return {
...state,
currentPlanWithChildren: action.payload,
error: ''
};
The payload logged here has the original values before my TaskService made changes. As a consequence of this when I navigate to a component which subscribes to the following Selector:
export const getCurrentPlanWithChildren = createSelector(
getPlanFeatureState,
(state: PlanState) => state.currentPlanWithChildren
);
it doesn't display the updated values. There's a good chance I'm missing something obvious here, but I can't see it. Any advice or suggestions extremely welcome.
Update
On further investigation I observed the following: My app has 2 pages; Gantt and Tasks. If I navigate to the Gantt page and call my updateTasksSimple method it changes the start, end and duration properties on my Tasks and dispatches an Action to update the Store, as detailed above. If I then navigate to my Tasks page which subscribes to the Store the initial Tasks received by the observable are the old values. However, if I reload the app, navigate to the Gantt page and call my updateTasksSimple method TWICE then navigate to the Tasks page and look at my console.log (which uses rxjs tap on the observable) the initial Tasks received are the new values.
It's as if the initial values received by my observable are old values, not the current values in the Store. My Tasks page declares my observable as follows:
...
public currentPlanWithChildren$: Observable<IPlan>;
...
ngOnInit() {
// Log the Tasks when the CurrentPlanWithChildren changes.
this.currentPlanWithChildren$ = this.store.pipe(select(fromPlan.getCurrentPlanWithChildren)).pipe(
tap(currentPlanWithChildren => {
if (currentPlanWithChildren != null) {
console.log(`PlanTasksContainerComponent.currentPlanWithChildren changed.`);
currentPlanWithChildren.tasks.forEach(t => {
console.log(`Task - ${t.title}, Summary: ${t.summary}, Start: ${t.start.toString()}, End: ${t.end.toString()}, Duration: ${t.durationHours.valueOf()}, Constraint: ${t.taskConstraintType.name}`);
})
}
})
);
}
Very odd.
dispatchAction()method called? - dallowsTaskServiceand hugely simplified it, hardcoding some dates and durations in. Still when I log the Tasks before dispatch they are as expected and yet in the Reducer they are incorrect! - TDC