I am working with an API that has a fixed, consistent structure of responses: it is always an object that has a data
property on it. As it is very tiresome and too explicit to constantly map the data in RxJS requests (or ngrx effects), I decided to introduce a custom RxJS operator that plucks the data and applies an optional callback.
But now some of my effects complain about the type information (like: property x doesn't exist on type {}
), so I guess my effort to properly type-guard the operator's I/O isn't enough:
export function mapData<T, R>(callback?: (T) => R) {
return (source: Observable<T>) => source.pipe(
map(value => value['data'] as R), // isn't that an equivalent of `pluck<T>('data')` ?
map(value => typeof callback === 'function' ? callback(value) : value as R),
);
}
An example of ngrx effect with type-guard problems :
switchMap(() => this.api.getData().pipe(
mapData(),
mergeMap(data => [
new actions.DataSuccessAction({ id: data.id }), // <-- id does not exist on type {}
new actions.SomeOtherAction(data),
]),
catchError(err => of(new actions.DataFailureAction(err))),
)),
Which of course goes away when I type-cast it explicitly:
mapData<any, IMyData>(....),
I would love to hear whether this is the right, TypeScript way to do stuff.
You can use multiple overloads to model different type behaviors. I am not 100% sure what the behavior should be, it's not 100% clear from your question, but my reading of it suggests the following rules:
T
hasdata
and nocallback
is specified returndata
callback
then the return is dictated bycallback
callback
is specified and 1 does not apply just returnT
An overloaded version would look something like this: