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(operators): add mapResponse
#4230
#4302
base: main
Are you sure you want to change the base?
Conversation
✅ Deploy Preview for ngrx-io ready!Built without sensitive environment variables
To edit notification comments on pull requests, go to your Netlify site configuration. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR @tom9744! In addition to the suggested changes, can you please add the mapResponse
section to this page: https://ngrx.io/guide/operators/operators
It should contain the description and example of using the mapResponse
operator. The example can be an NgRx effect + mapResponse
.
I appreciate your kind feedback on my work, @markostanimirovic! I modified the code based on your suggestions, and am currently working on writing a document for ngrx.io. |
error: (error: E) => R2; | ||
type MapResponseObserver<T, S, E> = { | ||
next: (value: T) => S; | ||
error: (error: unknown) => E; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought it would make more sense to type error
as unknown
because we don't know what type the error
can possibly be in various use cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please revert this change. When error
is explicitly set to unknown
, it's not possible to override its type:
mapResponse({
next: (val) => val + 1,
error: (err: { message: string }) => err, // compilation error
})
This should be allowed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @tom9744! Few small suggestions 👇
it('should map the thrown error using the error callback', () => { | ||
throwError(() => 'error') | ||
.pipe( | ||
mapResponse({ | ||
next: noop, | ||
error: (error) => `mapped ${error}`, | ||
}) | ||
) | ||
.subscribe((result) => { | ||
expect(result).toBe('mapped error'); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When using expect
within subscribe
, it's necessary to use done
. Otherwise, incorrect tests will also succeed.
it('should map the thrown error using the error callback', () => { | |
throwError(() => 'error') | |
.pipe( | |
mapResponse({ | |
next: noop, | |
error: (error) => `mapped ${error}`, | |
}) | |
) | |
.subscribe((result) => { | |
expect(result).toBe('mapped error'); | |
}); | |
}); | |
it('should map the thrown error using the error callback', (done) => { | |
throwError(() => 'error') | |
.pipe( | |
mapResponse({ | |
next: noop, | |
error: (error) => `mapped ${error}`, | |
}) | |
) | |
.subscribe((result) => { | |
expect(result).toBe('mapped error'); | |
done(); | |
}); | |
}); |
it('should map the error thrown in next callback using error callback', () => { | ||
function producesError() { | ||
throw 'error'; | ||
} | ||
|
||
of(1) | ||
.pipe( | ||
mapResponse({ | ||
next: producesError, | ||
error: (error) => `mapped ${error}`, | ||
}) | ||
) | ||
.subscribe((result) => { | ||
expect(result).toBe('mapped error'); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here:
it('should map the error thrown in next callback using error callback', () => { | |
function producesError() { | |
throw 'error'; | |
} | |
of(1) | |
.pipe( | |
mapResponse({ | |
next: producesError, | |
error: (error) => `mapped ${error}`, | |
}) | |
) | |
.subscribe((result) => { | |
expect(result).toBe('mapped error'); | |
}); | |
}); | |
it('should map the error thrown in next callback using error callback', (done) => { | |
function producesError() { | |
throw 'error'; | |
} | |
of(1) | |
.pipe( | |
mapResponse({ | |
next: producesError, | |
error: (error) => `mapped ${error}`, | |
}) | |
) | |
.subscribe((result) => { | |
expect(result).toBe('mapped error'); | |
done(); | |
}); | |
}); |
error: (error: E) => R2; | ||
type MapResponseObserver<T, S, E> = { | ||
next: (value: T) => S; | ||
error: (error: unknown) => E; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please revert this change. When error
is explicitly set to unknown
, it's not possible to override its type:
mapResponse({
next: (val) => val + 1,
error: (err: { message: string }) => err, // compilation error
})
This should be allowed.
loadMovies$ = createEffect(() => | ||
this.actions$.pipe( | ||
ofType('[Movies Page] Load Movies'), | ||
exhaustMap(() => this.moviesService.getAll() | ||
.pipe( | ||
mapResponse({ | ||
next: (movies) => ({ type: '[Movies API] Movies Loaded Success', payload: movies }), | ||
error: () => of({ type: '[Movies API] Movies Loaded Error' }) | ||
}) | ||
) | ||
) | ||
) | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's use a bit more complete example:
loadMovies$ = createEffect(() => | |
this.actions$.pipe( | |
ofType('[Movies Page] Load Movies'), | |
exhaustMap(() => this.moviesService.getAll() | |
.pipe( | |
mapResponse({ | |
next: (movies) => ({ type: '[Movies API] Movies Loaded Success', payload: movies }), | |
error: () => of({ type: '[Movies API] Movies Loaded Error' }) | |
}) | |
) | |
) | |
) | |
); | |
export const loadMovies = createEffect( | |
(actions$ = inject(Actions), moviesService = inject(MoviesService)) => { | |
return actions$.pipe( | |
ofType(MoviesPageActions.opened), | |
exhaustMap(() => | |
moviesService.getAll().pipe( | |
mapResponse({ | |
next: (movies) => MoviesApiActions.moviesLoadedSuccess({ movies }), | |
error: (error: { message: string }) => | |
MoviesApiActions.moviesLoadedFailure({ errorMsg: error.message }), | |
}) | |
) | |
) | |
); | |
}, | |
{ functional: true } | |
); |
PR Checklist
PR Type
What kind of change does this PR introduce?
What is the current behavior?
Closes #4230
What is the new behavior?
Does this PR introduce a breaking change?
Other information
I am not 100% sure if I correctly understood the expected behavior of
mapResponse
, as @markostanimirovic suggested in #4230.Please feel free to give me feedback.
Thanks.