I am trying that when creating a record the user is notified of the success or failure of the action
To this end I have prepared the following:
post.actions.ts
export const PostActions = createActionGroup({
source: 'Create post component',
events: {
'Create post': props<{ payload: CreatePostModel }>(),
'Successful post creation': emptyProps(),
'Failed post creation': props<{ payload: any }>(),
},
});
post.effects.ts
@Injectable()
export class ContentEffects {
addContent$ = createEffect(() =>
this.actions$.pipe(
ofType(PostActions.createPost),
exhaustMap((action) =>
this.postService.save(action.payload).pipe(
map(
(payload) => PostActions.successfulPostCreation(),
catchError((error) =>
of(PostActions.failedPostCreation({ payload: error }))
)
)
)
)
)
);
constructor(
private readonly postService: PostService,
private readonly actions$: Actions
) {}
}
post.reducer.ts
export interface PostState {
success: string;
error: any;
}
export const initialState: PostState = { success: '', error: undefined };
export const postReducer = createReducer(
initialState,
on(PostActions.successfulPostCreation, (state) => ({
...state,
success: 'shared.success',
error: undefined,
})),
on(PostActions.failedPostCreation, (state, { payload }) => ({
...state,
success: '',
error: payload,
}))
);
post.selectors.ts
export const selectSuccess = createFeatureSelector<string>('success');
export const selectError = createFeatureSelector<any>('error');
create-post.component.ts
@Component({
selector: 'app-post-create',
templateUrl: './post-create.component.html',
styleUrls: ['./post-create.component.scss'],
})
export class PostCreateComponent implements OnInit {
@ViewChild('form', { static: false }) form: NgForm;
success$: Observable<string>;
error$: Observable<any>;
constructor(
private readonly notificationService: NotificationService,
private readonly store: Store)
{
this.success$ = this.store.select(selectSuccess);
this.error$ = this.store.select(selectError);
}
ngOnInit(): void {
this.success$.subscribe({
next: (value) => {
this.form.resetForm();
this.formGroup.reset();
this.notificationService.success(value);
},
});
this.error$.subscribe({
next: (value) => this.notificationService.error(value),
});
}
submit() {
const model { ... }
this.store.dispatch(PostActions.createPost({ payload: model }));
}
}
The process is successful, I can verify that the record is created, I can also confirm that the success property of the state is created, but the success notification message is never displayed. Debugging I managed to verify that the subscription is never invoked
I expect this to be called upon successful completion of the process.
this.success$.subscribe({
next: (value) => {
this.form.resetForm();
this.formGroup.reset();
this.notificationService.success(value);
},
});
What is the reason that a subscription is not being invoked?
Thanks in advance
CodePudding user response:
createFeatureSelector
are used for returning a top level feature state, for example, the whole PostState
slice, you are using it for listening a particular property of PostState
, for that, just use a simple selector, based of the feature one, for example:
export const selectFeature = createFeatureSelector<PostState>('post-feature');
export const selectSuccess = createSelector(selectFeature, (state) => state.success)
More on selectors here
CodePudding user response:
I have managed to solve the problem by changing the selectors to the following
export const selectContent = (state: AppState) => state.content;
export const selectSuccess = createSelector(
selectContent,
(state: ContentState) => state.success
);
export const selectError = createSelector(
selectContent,
(state: ContentState) => state.error
);
The AppState
looks like this
export interface AppState {
post: PostState;
}
export const reducers: ActionReducerMap<AppState> = {
post: postReducer,
};
export function storageSyncReducer(
reducer: ActionReducer<AppState>
): ActionReducer<AppState> {
const metaReducer = storageSync<AppState>({
features: [
{ stateKey: 'post' },
],
rehydrate: true,
storage: window.sessionStorage,
});
return metaReducer(reducer);
}
Modifying the selector resolves the issue. But I do identify that a new behavior emerges. And it is that when creating a record, the 'success' property of the reducer preserves the value, which means that when trying to register another post, the notification is not made since the value of the property success
remains the same. In order to fix this. I have created an action that resets the status of notifications succes
and error
so that it is executed every time a new record is created
post.actions.ts
export const PostActions = createActionGroup({
source: 'Create post component',
events: {
'Create post': props<{ payload: CreatePostModel }>(),
'Successful post creation': emptyProps(),
'Failed post creation': props<{ payload: any }>(),
'Reset post notifications': emptyProps(),
},
});
post.reducer.ts
export interface PostState {
success: string;
error: any;
}
export const initialState: PostState = { success: '', error: undefined };
export const contentReducer = createReducer(
initialState,
on(PostActions.successfulPostCreation, (state) => ({
...state,
success: 'shared.success',
error: undefined,
})),
on(PostActions.failedPostCreation, (state, { payload }) => ({
...state,
success: '',
error: payload,
})),
on(PostActions.resetPostNotifications, (state) => ({
...state,
success: '',
error: undefined,
}))
);
PostActions.resetPostNotifications
resolves the described behavior
However, there is another unexpected behavior. And it is that every time the component is loaded, it seems to subscribe and gives an error in this line
this.form.resetForm();
I moved the subscriptions to the AfterViewInit hook but the behavior remains. I could do a return if the value is an empty string. But it seems to me a workaround what is the correct way to handle this case?
update-0
Returning early if the value is an empty string does not solve the problem