6
\$\begingroup\$

I have a usecase where I want to prevent unexpected duplicate Http calls. I'm wondering if I can have a custom operator which can be used in pipe for any API as shown in below example 1. Even better if I can use this operator in Angular's HTTP_INTERCEPTOR as shown in example 2.

Code for customer operator

class RxJsUtils {
  private static readonly inProgress = new Map<string, Observable<unknown>>();

  static shareDuplicate<T>(indentifier: string): MonoTypeOperatorFunction<T> {
    return (source$: Observable<T>): Observable<T> => {
      if (this.inProgress.has(indentifier)) {
        console.log(`DUPLICATE :: ${indentifier}`);
      } else {
        this.inProgress.set(indentifier, source$.pipe(shareReplay(1)));
      }

      return (this.inProgress.get(indentifier) as Observable<T>).pipe(
        finalize(() => this.inProgress.delete(indentifier))
      );
    };
  }
}

Example 1: Usage in individual service call

class MyService {
  constructor(private http: HttpClient) {}

  create() {
    return this.http
      .post('some-url', {})
      .pipe(RxJsUtils.shareDuplicate('some-url'));
  }
}

Example 2: Usage in HTTP_INTERCETOR

class ApiInterceptor implements HttpInterceptor {
  intercept(request: HttpRequest<unknown>, next: HttpHandler) {
    const { urlWithParams } = request;

    return next
      .handle(request)
      .pipe(RxJsUtils.shareDuplicate(urlWithParams));
  }
}

Although I'm able to achieve the desired result as I'm able to prevent duplicate using either of the code. I'm still looking for code review & feedback if I'm missing some corner cases. I'm also seeking some guidance wrt the unit test cases that we can write for these kind of custom rxjs operator.

One more thing which I don't have any clue about is when I try to use this operator for individual api call & in interceptor it doesn't work as expected.

I'm little naive to writing custom operator, Any guidance is highly appreciated.

\$\endgroup\$
1
  • \$\begingroup\$ I find your approach brilliant. I wrongly assumed from the title that you're trying to build a response cache, until I saw the finalize operator and understood it's about concurrent request prevention. If there would be a way to get the URL from the source$ instead of passing it as an argument, it would be perfect. Don't be mad if I let myself be "inspired" by your idea. \$\endgroup\$ Commented May 25, 2022 at 13:36

1 Answer 1

1
\$\begingroup\$

This works like a charm (see UPDATE below). The only thing you need to take care of is to sort your query parameters (if any) alphabetically (or in any other consistent way), otherwise you might have the same requests with different parameter order.

I modified slightly the solution from here: https://stackoverflow.com/a/49967720/134120 to check if the URL contains multiple parameters in the first place (if not, return it as is), and then your line in the interceptor becomes:

    .pipe(RxJsUtils.shareDuplicate(sortQueryParamsAlphabetically(urlWithParams)));

Already using your solution, was thinking of rolling out my own along the same lines, but no need, thanks! 😉

UPDATE: Another thing you need to take care of is the HTTP method/verb and the payload, if the method is not GET. So, unfortunately this solution will only work well for GET, since it's only checking the URL and the query parameters.

PS. In my mind, I was planning to use a debouncing solution, i.e. if the same call happens within e.g. 1sec, return the one from before. I did this in the past with AngularJS, will have to dig that code and see if it still makes sense with Angular and RxJS.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.