Home > Software design >  What is the reason that a subscription is not being invoked?
What is the reason that a subscription is not being invoked?

Time:12-20

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

  • Related