Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(component-store): add tapResponse operator #2763

Merged
merged 3 commits into from
Nov 7, 2020

Conversation

alex-okrushko
Copy link
Member

PR Checklist

Please check if your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

Adds a simple wrapper around tap that handles success and error + catchError(() => EMPTY).

The was some confusion as to whether catchError(() => EMPTY) was needed when tap({error: (error) => handleError(error)}) was used.

To avoid such confusion and remove a bit of boilerplate around not letting effect collapse, I suggest to have this very thin operator.

If not used, this operator is 100% tree-shakable from the bundle.

[ ] Bugfix
[x] Feature
[ ] Code style update (formatting, local variables)
[ ] Refactoring (no functional changes, no api changes)
[ ] Build related changes
[ ] CI related changes
[ ] Documentation content changes
[ ] Other... Please describe:

Does this PR introduce a breaking change?

[ ] Yes
[x] No

Other information

@alex-okrushko
Copy link
Member Author

FYI, we've been using this operator within Firebase Console for some time already 🙂 so this is pushing it upstream.

Also, if there are any other preferences around the naming - I'm all ears 🙂

Copy link
Member

@timdeschryver timdeschryver left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

About the naming, what about mapResponse?
Reasoning, what is handle, and map feels RxJS-y.

modules/component-store/src/handle-response.ts Outdated Show resolved Hide resolved
@ngrxbot
Copy link
Collaborator

ngrxbot commented Nov 3, 2020

Preview docs changes for 3b25f3c at https://previews.ngrx.io/pr2763-3b25f3c1/

@markostanimirovic
Copy link
Member

About the naming, what about mapResponse?
Reasoning, what is handle, and map feels RxJS-y.

@timdeschryver
All RxJS __map operators are used to transform emitted value and return a new one, but handleResponse is used to perform side effects 🤔

@timdeschryver
Copy link
Member

That's a good point @markostanimirovic

Comment on lines 25 to 29
export function mapResponse<T>(
nextFn: (next: T) => void,
errorFn: (error: unknown) => void,
completeFn?: () => void
): (source: Observable<T>) => Observable<T> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to add two signatures for this function, similar to tap operator in order to make it more flexible.

export function tap<T>(next?: (x: T) => void, error?: (e: any) => void, complete?: () => void): MonoTypeOperatorFunction<T>;
export function tap<T>(observer: PartialObserver<T>): MonoTypeOperatorFunction<T>;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The goal is to not make it flexible. I want to force to handle the error.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I suggested that based on the implementation. According to the operator implementation too, I suggest the name safeTap or something similar. 🙂

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @markostanimirovic that's a good suggestion.
We had similar discussions about safeCall operator for @ngrx/effect and eventually called it mapToAction and then renamed to act: #1224

Looking at the poll tapResponse appears to be the clear winner.

@beeman, technically this operator still maps, but only the error case :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alex-okrushko
Naming - the hardest thing 😄

I would also choose tapResponse between tapResponse, mapResponse and handleResponse.

Response as a part of name confused me a little bit, because it associates me with HTTP response 🙂

@alex-okrushko
Copy link
Member Author

That's a good point @markostanimirovic

@timdeschryver

so tapResponse? 😅

@timdeschryver
Copy link
Member

I was thinking the same thing @alex-okrushko
On the other hand we're still in an effect so everything is a side effect.
I would find mapResponse easier to remember and explain, because you would probably say you map the response to effect X or Y 😅

Sorry for not helping, naming things is hard...
I'm fine with the 3 options (handle, map, tap) we have, if you have a preference I'm happy to use that terminology.
If you don't have a preference we might have a problem, what about a quick poll?

@beeman
Copy link

beeman commented Nov 4, 2020

My 2 cents, I think I like tapResponse best. With mapResponse I would expect that something would be returned, like with map() or mapTo().

@brandonroberts brandonroberts changed the title feat(component-store): add handleResponse operator feat(component-store): add tapResponse operator Nov 7, 2020
@brandonroberts brandonroberts merged commit d1873c9 into master Nov 7, 2020
brandonroberts pushed a commit that referenced this pull request Nov 25, 2020
* feat(component-store): add handleResponse operator

* Rename to mapResponse

* rename to tapResponse
@Ash-kosakyan
Copy link

Ash-kosakyan commented Dec 1, 2020

Hi, can you observe this solution for tapResponse operator

export function tapResponse<T>(
  nextFn: (next: T) => void,
  errorFn: (error: unknown) => void,
  completeFn?: () => void
): (source: Observable<T>) => Observable<T> {
  return (source) =>
    source.pipe(
      tap(nextFn),
      catchError((error) => {
        errorFn(error);
        return EMPTY;
      }),
      finalize(completeFn || noop)
    );
}

@alex-okrushko
Copy link
Member Author

Hi @Ash-kosakyan
What does your solution improve?

@Ash-kosakyan
Copy link

Hi @alex-okrushko
The difference: "finalize" is working every time, although observable can be errored

 /**
  * Effects
  * */
 readonly loadAll = this.effect((origin$: Observable<void>) =>
   origin$.pipe(
     tap(() => this.setLoading(true)),
     concatMap(() =>
       this.modelService.getAll().pipe(
         tap((models) => {
           this.setAll(models);
         }),
         catchError((error: HttpErrorResponse) => {
           this.setError(error.error);
           return EMPTY;
         }),
         finalize(() => {
           this.setLoading(false);
         })
       )
     )
   )
 );

@alex-okrushko
Copy link
Member Author

@Ash-kosakyan - that's true. That also means that complete could bring the unexpected behavior to tapResponse.

Thanks for your input!
I think for this one tapReponse should be as close to tap as possible, so we'll keep the current implementation.

Thanks again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants