I am working on a blog using angular & ngrx.
posts are received from the server, and they are shown in the posts-list.component
.
when the user clicks on the post title he should be navigated to the route /posts/:id
to see the full post, and the post should be saved in the store & thee localStorage as selectedPost
.
the problem is that the selectedPost
is set in the store & localStorage but it's not shown in the view.
here is the code: (no styles applied)
modules:
app.module.ts:
// imports...
@NgModule({
declarations: [
AppComponent,
BaseComponent,
HomeComponent,
NavigationComponent,
FooterComponent
],
imports: [
BrowserModule,
AppRoutingModule,
FormsModule,
HttpClientModule,
StoreModule.forRoot({}),
StoreDevtoolsModule.instrument(),
EffectsModule.forRoot(),
FontAwesomeModule,
PostsModule
],
providers: [AppPreloadingStrategyService],
bootstrap: [AppComponent]
})
export class AppModule { }
posts.module.ts:
// imports..
@NgModule({
declarations: [
PostsListComponent,
PostComponent
],
imports: [
CommonModule,
RouterModule,
ReactiveFormsModule,
StoreModule.forFeature(featureKey,postReducer),
EffectsModule.forFeature([PostEffects])
],
exports: [
PostsListComponent,
PostComponent
]
})
export class PostsModule { }
state:
post.actions.ts:
// imports...
export const loadPosts = createAction('[Post] Load');
export const loadPostsSuccess = createAction('[Post] Load Success', props<{posts: Post[]}>());
export const loadPostsError = createAction('[Post] Load Error', props<{error: string}>());
export const setSelectedPost = createAction('[Post] Set Selected Post', props<{post: any}>());
export const selectPost = createAction('[Post] Select', props<{post: Post}>());
export const updatePost = createAction('[Post] Update', props<{update: Update<Post>}>());
export const selectUpdatedPost = createAction('[Post] Select Updated', props<{title: string, content: string}>());
post.reducer.ts:
export interface PostsState extends EntityState<Post> {
selectedPost: Post;
error: any;
}
export function selectItemId(a: Post): string {
return a.id.toString();
}
export function sortByName(a: Post, b: Post): number {
return a.title.localeCompare(b.title);
}
export const postAdapter: EntityAdapter<Post> = createEntityAdapter<Post>();
export const initialState = postAdapter.getInitialState({selectId: selectItemId,
sortComparer: sortByName,selectedPost: undefined, error: ''});
export const _postReducer = createReducer(
initialState,
on(postActions.loadPostsSuccess, (state , { posts }) => {
return postAdapter.setAll(posts, state);
}),
on(postActions.loadPostsError, (state, {error}) => {
return {...state, error: error};
}),
on(postActions.updatePost, (state, {update}) => {
return postAdapter.updateOne(update, state);
}),
on(postActions.setSelectedPost, (state, {post}) => {
return {...state, selectedPost: post};
})
);
export function postReducer(state: any, action: Action) {
return _postReducer(state, action);
}
// selectors
export const featureKey = "posts";
const selectPostsFeature = createFeatureSelector<PostsState>(featureKey);
const {selectAll} = postAdapter.getSelectors();
export const getPosts = createSelector(
selectPostsFeature,
selectAll
);
export const getSelectedPost = createSelector(
selectPostsFeature,
(state: PostsState) => state.selectedPost
);
post.effects.ts:
@Injectable()
export class PostEffects {
constructor(private actions$: Actions, private http: HttpClient) {}
loadPosts$ = createEffect(() => this.actions$.pipe(
ofType('[Post] Load'),
mergeMap(() => this.http.get('http://localhost:3000/posts').pipe(
map(posts => ({type: '[Post] Load Success', posts: posts})),
catchError(err => of({type: '[Post] Load Error', error: err}))
))
));
}
component:
post.component.ts:
@Component({
selector: 'app-post',
templateUrl: './post.component.html',
styleUrls: ['./post.component.scss']
})
export class PostComponent implements OnInit {
constructor(
private store: Store,
private fb: FormBuilder,
private route: ActivatedRoute,) { }
post$: Observable<Post>;
postUrl: any;
formGroup = this.fb.group({
title: [''],
content: ['']
});
ngOnInit(): void {
if(localStorage.getItem('theSelectedPost') !== null) {
this.store.dispatch(setSelectedPost({post: localStorage.getItem('theSelectedPost')}));
}
this.post$ = this.store.pipe(select(getSelectedPost));
this.postUrl = this.route.snapshot.paramMap.get("id");
}
updatePost(post: Post){
this.store.dispatch(updatePost({update: {id: this.postUrl, changes: post}}));
}
}
post.component.html:
<div class="title">
{{(post$ | async)?.title}}
</div>
<div class="content">
{{(post$ | async)?.content}}
</div>
<img [src]="(post$ | async)?.imageSrc">
<div >
<div *ngFor="let category of (post$ | async)?.categories">
{{category}}
</div>
</div>
<form [formGroup]="formGroup">
Update Post: <br>
<input type="text" placeholder="Title..." formControlName="title"> <br>
<textarea placeholder="Content..." formControlName="content"></textarea> <br>
<button (click)="updatePost(formGroup.value)">Update</button>
</form>
I'm trying to do that just for learning how does things work in ngrx.
sorry for the long question but i'm really tired with this problem.
CodePudding user response:
the issue is here
if(localStorage.getItem('theSelectedPost') !== null) {
this.store.dispatch(setSelectedPost({post: localStorage.getItem('theSelectedPost')}));
}
you are reading a string
from localStorage and then passing it to the state. To fix it you could at least JSON.parse
it, before saving to the store.
Reading from localStorage and writing to it sounds like a good usecase for Effects. I would put this logic there.
I would recommend you to write more typesafe code to eliminate such errors.