I have a blog application and I'm using ngrx.
the application -for now- have two objects in the store
{
"posts": [...],
"users": [...]
}
now I want to make a separated component to show a single post (selected-post.component).
I tried to select the entity by its id using a selector like this:
export const selectPostById = (id: string) => createSelector(
selectEntities,
entities => entities[id]
);
and then get it in the component:
postId: string = localStorage.getItem("_selectedPostId");
selectedPost: Observable<Post> = this.store.select(selectPostById(postId));
then to show it in the view I used:
<ng-container *ngIf="selectedPost | async">
<div >
<div >{{(selectedPost | async).title}}</div>
<div >
<img [src]="(selectedPost | async).imageSrc">
</div>
<div >
{{(selectedPost | async).content}}
</div>
<div >
<a href="categories/category" *ngFor="let ct of (selectedPost | async).categories">
{{ct}}
</a>
</div>
<div >
{{(selectedPost | async).date}}
</div>
<div >
<a href="users/otheruser">{{(selectedPost | async).author.name}}</a>
</div>
</div>
<div >Comments</div>
<div >
<div *ngFor="let comment of (selectedPost | async).comments">
<div >
<a href="users/otheruser">{{comment.author.name}}</a>
</div>
<div >{{comment.content}}</div>
<div >{{comment.date}}</div>
<div *ngIf="comment.replies">
<div *ngFor="let reply of comment.replies">
<div >
<a href="users/otheruser">{{reply.author.name}}</a>
</div>
<div >{{reply.content}}</div>
<div >{{reply.date}}</div>
</div>
</div>
</div>
</div>
</ng-container>
So the problem is that the post is not shown with error in console:
ERROR TypeError: Cannot read properties of undefined (reading: '1')
I selected the post with the id "1" and it exists in the state shown by redux devtools.
Any ideas?.
EDIT: the reducer:
import { Action, createFeatureSelector, createReducer, createSelector, on } from '@ngrx/store';
import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';
import * as postActions from './actions';
import { Post } from 'src/app/models/post';
export interface PostState extends EntityState<Post> {
error: any;
selectedPostId: string | null;
}
export function selectUserId(a: Post): string {
return a.id;
}
export const adapter: EntityAdapter<Post> = createEntityAdapter<Post>({
selectId: selectUserId,
sortComparer: false
});
export const initialState = adapter.getInitialState({error: null,selectedPostId: null});
const testsReducer = createReducer(
initialState,
on(postActions.loadPostsSuccess, (state, {posts}) => adapter.setAll(posts, state)),
on(postActions.loadPostsFailed, (state, {error}) => ({...state, error})),
on(postActions.setSelectedPostId, (state, {id}) => ({...state, selectedPostId: id}))
);
export function reducer(state: PostState, action: Action) {
return testsReducer(state, action);
}
// Selectors
const {
selectIds,
selectEntities,
selectAll,
selectTotal,
} = adapter.getSelectors();
const feature = createFeatureSelector<PostState>('posts');
export const getPosts = createSelector(feature,selectAll);
export const getSelectedPostId = (state: PostState) => state.selectedPostId;
export const selectPostId = createSelector(
feature,
getSelectedPostId
);
export const selectPostById = (id: string) => createSelector(
selectEntities,
entities => entities[id]
);
CodePudding user response:
You have to link the selectEntities
selector from the adapter to feature
one (created by createFeatureSelector
function), then use it to get the required entity by Id.
You can try the following:
// Selectors
const { selectIds, selectEntities, selectAll, selectTotal } = adapter.getSelectors();
const feature = createFeatureSelector<PostState>('posts');
// ...
// Create a selector from the feature selector and selectEntities one.
export const selectPostEntities = createSelector(feature, selectEntities);
// Use the new selector `selectPostEntities` to slice the post entity from it.
export const selectPostById = (id: number) =>
createSelector(selectPostEntities, (entities) => entities[id]);
Besides, you can optimize your component template by subscribing to the selectedPost
observable using asyn
pipe once only, like the following:
<ng-container *ngIf="selectedPost | async as _selectedPost">
<!-- You can now use the `_selectedPost` variable without `async` pipe for all the elements inside the ng-container -->
</ng-container>