Reputation: 871
I am working on a relative simple app where i want to learn more about ngrx, redux and angular 2.
I'll explain briefly about the setup of my app, my appstate and the reducers.
In my app i want to draw certain objects (meshes) on my screen using a webgl framework. I want to add a mesh through the ngrx store by creating simple objects with the properties of those meshes and save those in the app state. Whenever a 'mesh' has been added i want to use ngrx sideeffects to draw the mesh on screen using a service which has access to the webgl framework.
My (simplified) appstate looks like this:
export interface AppState {
meshList: MeshListReducer.MeshListState
}
To add a mesh i have created the following reducer functions:
case MeshActions.ADD_MESH: {
return Object.assign({
entities: [meshAction.payload, ...state.entities]
});
}
case MeshActions.ADD_MESH_SUCCESS:
case MeshActions.UPDATE_MESH: {
// This reducer function is only being called once, see below for further explanation.
return Object.assign({}, state, {
entities: state.entities.map(meshItem => {
// If we can find a mesh with this id, use the sub reducer to update it.
if (meshItem.uuid === meshAction.payload.uuid)
return meshSubReducer(meshItem, meshAction);
return meshItem;
})
});
}
default: {
return state;
}
}
export function meshSubReducer(mesh: BaseMesh, meshAction: MeshAction): BaseMesh {
switch (meshAction.type) {
case MeshActions.ADD_MESH_SUCCESS:
case MeshActions.UPDATE_MESH:
return Object.assign({}, meshAction.payload);
default:
return mesh;
}
}
And finally my effects class which contains the call to the webgl framework service, and calling the succes action:
@Effect()
addMesh$ = this.actions$
.ofType(MeshActions.ADD_MESH)
.map((action: MeshAction) => action.payload)
.switchMap((payload: BaseMesh) => this._worldService.addMeshToWorld(payload))
.map(payload => this._meshActions.addMeshSuccess(payload));
And the worldService.addMeshToWorld function:
public addMeshToWorld(mesh: BaseMesh): Promise<BaseMesh> {
let p = new Promise<BaseMesh>((resolve, reject) => {
let renderer = this._meshRendererFactory.createMeshRenderer(mesh);
// Every call to the ngrx effects always enters this function, and the component is sucessfully added to the canvas.
renderer.addTo(this._world.app).then((component: WHS.Component) => {
resolve(Object.assign({}, mesh, {
rendererId: (<any>component).native.id
}));
});
});
return p;
}
And i am invoking all of these functions using these dispatch methods:
let soccerBall = new SphereMesh({
geometry: {
heightSegments: 20,
widthSegments: 20,
radius: 5
},
gameMeshType: Enums.GameMeshType.Sphere,
uuid: this._meshHelper.generateUUID(),
meshMaterial: {
color: 0xF2F2F2
}
});
let soccerBall2 = new SphereMesh({
geometry: {
heightSegments: 20,
widthSegments: 20,
radius: 5
},
gameMeshType: Enums.GameMeshType.Sphere,
uuid: this._meshHelper.generateUUID(),
meshMaterial: {
color: 0xF2F2F2
}
});
// Dispatching these straight after each other will cause the succes action to only be dispatched once.
this._store.dispatch(this._meshActions.addMesh(soccerBall));
this._store.dispatch(this._meshActions.addMesh(soccerBall2));
The problem i am having is that after the "ADD_MESH" side effect is being called and after adding the mesh to the screen, the effect is only calling the final 'map' action once, so the ADD_MESH_SUCCES reducer function is only called for one mesh, and not the other, see screenshot:
The weird thing is that when i add the second dispatch in a timeout, the success call is now suddenly being called correctly for all dispatched items:
this._store.dispatch(this._meshActions.addMesh(soccerBall));
setTimeout(() => {
// Now succes action is suddenly being called twice like it should..
this._store.dispatch(this._meshActions.addMesh(soccerBall2));
}, 1500);
What could cause this weird behavior? I thought my app was fairly simple but it seems i am missing something.
I am using the following package versions (i picked out the packages that i thought would be most useful to mention here:
"@angular/core": "^5.0.0",
"rxjs": "^5.5.2",
"@ngrx/effects": "^5.2.0",
"@ngrx/store": "^5.0.0",
"typescript": "~2.4.2"
Upvotes: 4
Views: 9261
Reputation: 3116
Must use switchMap
inside effects carefully, my advice is don't use it until you comfortable with RxJS.
Why switchMap is dangerous? Because the second time you dispatch the same action before the first one is complete, it disables the chain that first one started and immediately executes the second action chain.
Why concatMap
works? It queues the actions. After first one is done, the second one starts. So there's no race between actions.
Upvotes: 21