shahaf
shahaf

Reputation: 781

ngrx selecting data of specific object id with memoization method

my question is regarding ngrx4 (with angular5).

i am working on an app with the following data structure:

-> store
--> forums: id, name
--> posts: id, message, forumid

when i present a post, i also present it's forum. so a post representation would be:

postid, message, forum.id, forum.name

from ngrx example app i understand that the way to get forum's data is to:

  1. add selectedForumId to the forum state
  2. when fetching a post: dispatch event to update selectedForumId to the post's forumid
  3. select forum data by selectedForumId

in code:

home.component.ts:

  ngOnInit() {
    this.store.dispatch(new postActions.GetAllPostsAction());
    this.posts$ = store.select(fromReducers.getAllPosts);
  }

home.component.html:

<post *ngFor="let post of (posts$|async)" [post]="post"></post>

post.component.ts:

  @Input() post: Post;
  ngOnInit() {
    this.store.dispatch(new forumActions.SelectForumAction(this.post.forumId));
    this.forum$ = this.store.select(fromReducers.getSelectedForum);
  }

forum.reducer.ts:

export interface State extends EntityState<Forum> {
    selectedForumId: number | null;
};

export function reducer(state = initialState, action: ForumActions): State {
    switch (action.type) {
        case ForumActionTypes.SELECT_FORUM:
            return {
                ...state,
                selectedForumId: action.payload,
            };
...
export const getSelectedForumId = (state: State) => state.selectedForumId;

reducers/index.ts:

export const selectForumState = createFeatureSelector<fromForum.State>('forums');
export const selectForumEntities = createSelector(selectForumState, fromForum.selectForumEntities);
export const selectedForumId = createSelector(selectForumState, fromForum.getSelectedForumId);
export const getSelectedForum = createSelector<State, any, any, Forum>(selectForumEntities, selectedForumId,
    (entities, id) => entities[id]
);

The Question -

my problem with this a approach is that if i have 10 posts in a page it means i would dispatch the action that updates selectedForumId 10 times and it feels wrong.

is there a better-practice approach i can use, that will still use the advantage of createSelector which creates a memoized selector?

Upvotes: 2

Views: 3027

Answers (1)

shahaf
shahaf

Reputation: 781

1) in my example i did some bad practice when i fetched data from my store inside what's supposed to be a dumb component (post.component.ts)

2) first solution

at first i came with this question: @Ngrx/store: how to query models with relationships

where in the comments was a link to the following article: https://netbasal.com/querying-a-normalized-state-with-rxjs-in-angular-71ecd7ca25b4

this article brought me to implement the solution:

posts.service.ts:

getPostsViewModel(posts$: Observable<Post[]>, users$: Observable<User[]>, forums$: Observable<Forum[]>): Observable<PostView[]> {
    return Observable.combineLatest(posts$, users$, forums$, (posts, users, forums) => {
        return posts.map(post => Object.assign({}, post, {
            user: users.find(user => user.id === post.userId),
            forum: forums.find(forum => forum.id === post.forumId)
        }))
    });
}

home.component.ts:

   ngOnInit() {
    this.store.dispatch(new postActions.GetAllPostsAction());

    this.postsView$ = this.postService.getPostsViewModel(
      this.store.select(fromReducers.getAllPosts),
      this.store.select(fromReducers.getAllUsers),
      this.store.select(fromReducers.getAllForums));
   }

and this solutions works!

3) second solution

but i was still bothered that i didnt use the createSelector memoization abilities. so armed with this new approach i tried to think how this can be applied into createSelector, and here is the answer:

reducers/index.ts:

export const selectPostIds = createSelector(selectPostState, fromPost.selectPostIds);
export const selectPostEntities = createSelector(selectPostState, fromPost.selectPostEntities);
export const selectedForumId = createSelector(selectForumState, fromForum.getSelectedForumId);
export const getAllUsers = createSelector<State, any, any, User[]>(selectUserEntities, selectUserIds,
    (entities, ids) => ids.map(id => entities[id])
);
export const getAllForums = createSelector<State, any, any, Forum[]>(selectForumEntities, selectForumIds,
    (entities, ids) => ids.map(id => entities[id])
);

export const getAllPosts = createSelector<State, any, any, any, any, PostView[]>(selectPostEntities, selectPostIds, getAllUsers, getAllForums,
    (entities, ids, users, forums) => {
        let posts = ids.map(id => entities[id]);
        return posts.map(post => Object.assign({}, post, {
            user: users.find(user => user.id === post.userId),
            forum: forums.find(forum => forum.id === post.forumId)
        }));
    }
);

home.component.ts:

  ngOnInit() {
    this.store.dispatch(new postActions.GetAllPostsAction());
    this.posts$ = this.store.select(fromReducers.getAllPosts);
  }

and now i have the solution in createSelector()!

i still wonder though -

is the memoization process in my implementation is good enough?

wouldnt it be better to create a method called:

getForumFromListOfForums(forums:Forum[], forumId)

and use loadsh to memoize it?

then go to it from the post.component.ts and let each post ask for its forum?

that way if a forum was already asked before the response is memoized and therefore would be O(1).

Upvotes: 1

Related Questions