I've got the following issue and I do need some advice how to tackle this best.
On our application we do a have a container component as following:
export class ArchiveAllComponent implements OnInit {
archiveAllViewModel$: Observable<ArchiveAllViewModel>
constructor(
private store: Store<ApplicationState>,
private votingService: VotingService
) { }
ngOnInit() {
// Subscribe to any changes in the category and voting entities
this.archiveAllViewModel$ = this.store.pipe(
select(selectCategoriesWithVoting),
map((categories: Category[]) => mapToArchiveAllViewModel(categories))
)
// Load all categories
this.votingService.getCategories().subscribe((categories: Category[]) => {
this.store.dispatch(new LoadCategoryAction(categories));
});
// Load all votings
this.votingService.getVotings().subscribe((votings: Voting[]) => {
this.store.dispatch(new LoadVotingAction(votings));
});
}
}
Once this component is rendered, two HTTP GET requests are being executed to different API's.
For each of those request an action is dispatched to the store.
// Reducer function
export function entityReducer(currentState: Entities, action: EntityActionsUnion): Entities {
switch (action.type) {
case EntityActionTypes.LOAD_CATEGORIES:
return merge({}, currentState, {categories : action.categories});
case EntityActionTypes.LOAD_VOTINGS:
return merge({}, currentState, {votings : action.votings});
default:
return currentState;
}
}
export function selectCategoryEntity(state: ApplicationState) {
return state.entities.categories;
}
export function selectVotingEntity(state: ApplicationState) {
return state.entities.votings;
}
export const selectCategoriesWithVoting = createSelector(
selectCategoryEntity,
selectVotingEntity,
(categoryEntities: Category[], votingEntities: Voting[]) => {
if (categoryEntities && categoryEntities.length > 0 && votingEntities && votingEntities.length > 0) {
let categories = categoryEntities.slice();
votingEntities.forEach(voting => {
if (voting.categoryId) {
let category = categories.find(x => x.id == voting.categoryId);
if(!category.votings)
{
category.votings = [];
}
category.votings.push(voting);
}
});
return categories;
}
return [];
}
);
The archiveAllViewModel$
observable is then passed to some child components for rendering the HTMl accordingly.
This seemed to work on first glance and even if you do a refresh, the following is executed:
getCategories()
+ Action / reducer is triggeredgetVotings()
+ Action / reducer is triggered<archiveElement>
+ first child element is correctly rendered<archiveElement>
+ second child element is correctly rendered<archiveElement>
+ third child element is correctly renderedThe problem starts to appear, as soon as I start to navigate away from the component and come back to the same route via client site routing.
<a routerLink="/someotherpage" routerLinkActive="active" mat-button>Other Page</a>
Returning to the same component:
<a routerLink="/archiveAll" routerLinkActive="active" mat-button>Archive</a>
Now compared to a full page refresh, everything is rendered twice:
<archiveElement>
<archiveElement>
<archiveElement>
getCategories()
+ Action / reducer is triggered<archiveElement>
<archiveElement>
<archiveElement>
getVotings()
+ Action / reducer is triggered<archiveElement>
<archiveElement>
<archiveElement>
As a result each of the the child components now appears twice on the page.
The createSelector
is now being executed for each http requests once because the predicate categoryEntities && categoryEntities.length > 0 && votingEntities && votingEntities.length > 0
is now no longer only valid for the second http request.
It had nothing to do with Angular. After some further debugging I realised that the issue was within the selector function.
I've refactored it and now seems to work!
export const selectCategoriesWithVoting = createSelector(
selectCategoryEntity,
selectVotingEntity,
(categoryEntities: Category[], votingEntities: Voting[]) => {
if (categoryEntities && categoryEntities.length > 0 && votingEntities && votingEntities.length > 0) {
let categories = categoryEntities.slice();
votingEntities.forEach(newVoting => {
if (newVoting.categoryId) {
let category = categories.find(x => x.id == newVoting.categoryId);
if(!category.votings)
{
category.votings = [];
}
let oldVotingIndex = category.votings.findIndex(x => x.id == newVoting.id);
if(oldVotingIndex != -1)
{
category.votings[oldVotingIndex] = newVoting;
} else {
category.votings.push(newVoting);
}
}
});
return categories;
}
return [];
}
);