Search code examples
javascriptangularrxjssignals

Custom RxJs operator is triggering only for the first time


I am trying to create a custom RxJs operator that checks if the API response contains an error message, and if it is there then it throws an error with that message. Then I want to handle that error in my ticket-feedback.service.ts.

BaseServerResponse> Type:

class BaseServerResponse<T> {
    data?: T;
    error?: string;
    message?: string;
}

Custom RxJs operator:

export const rxThrowCustomServerError = <T>() => {
  return function (source: Observable<BaseServerResponse<T>>): Observable<BaseServerResponse<T>> {
    return new Observable((subscriber) => {
      const subscription = source.subscribe({
        next(x) {
          if (x.error) {
            subscriber.error(x.error);
          }
          subscriber.next(x);
        },
        error(error) {
          subscriber.error(error);
        },
        complete() {
          subscriber.complete();
        },
      });

      return () => {
        subscription.unsubscribe();
      };
    });
  };
};

ticket-feedback.component.ts:

@Component({
  selector: 'ticket-feedback-list',
  standalone: true,
  imports: [...ANGULAR_MODULES, ...ANGULAR_MATERIAL_MODULES],
  providers: [],
  templateUrl: './ticket-feedback-list.component.html',
  styleUrls: ['./ticket-feedback-list.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export default class TicketFeedbackListComponent {
  private _service = inject(TicketFeedbackService);
  form = this._service.getFeedbackForm;
  feedbackResponse = toSignal(this._service.feedbackResponse$);
  public forSubmit() {
    this._service.feedbackRequested$.next({
      fromDate: this.form.controls.fromDate.value,
      toDate: this.form.controls.toDate.value,
      retailerMobileNumber: this.form.controls.retailerMobileNumber.value,
      pageNumber: 1,
      pageSize: 10,
    });
  }
}

ticket-feedback.service.ts:

  feedbackResponse$ = this.feedbackRequested$.pipe(
    filter((feedbackRequest) => feedbackRequest && !isEmpty(feedbackRequest) && this.getFeedbackForm.valid),
    switchMap((request) =>
      this._http.get<BaseServerResponse<Feedback[]>>(appApiResources.feedback, {
        params: {
          pageNumber: request.pageNumber,
          pageSize: request.pageSize,
          fromDate: request.fromDate,
          toDate: request.toDate,
          'api-version': 1,
        },
      })
    ),
    rxThrowCustomServerError(),
    catchError(() => of(new BaseServerResponse<Feedback[]>([] as Feedback[], 0, 0, 0, '', '', 'Unable to fetch feedbacks'))),
    finalize(() => this.feedbackRequested$.next({} as IGetFeedbackRequest))
  );

In HTML I am simply reading the feedbackResponse Singnal as:

feedbackResponse() | json

However, this works (get API data, throw an error from custom RxJs operator, handle error in catchError in the service.) only for the first time when I click on the form submit button, but from next time onwards nothing happens not even the API call. If I remove rxThrowCustomServerError(), then the API call works every time the button is clicked.

What's the issue here?


Solution

  • When an observable enits an error, it completes. Therefore, when

      anyObservable$.pipe(
        rxThrowCustomServerError()
      )
    

    emits an error, it's the last thing it emits, then it closes, blissfully unaware that it might immediately pipe into a catchError() operator.

    Instead, try replacing

        rxThrowCustomServerError(),
        catchError(() => of(new BaseServerResponse<Feedback[]>([] as Feedback[], 0, 0, 0, '', '', 'Unable to fetch feedbacks'))),
    

    with

        map(o => (o?.error) ? new BaseServerResponse<Feedback[]>(
            [] as Feedback[],
            0,
            0,
            0,
            '',
            '',
            'Unable to fetch feedbacks'
          )
          : o
        ),