Skip to content

Commit

Permalink
refactor: remove thunk magic service
Browse files Browse the repository at this point in the history
  • Loading branch information
SaulMoro committed Mar 11, 2024
1 parent e2007cf commit 27a5caa
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 106 deletions.
41 changes: 27 additions & 14 deletions projects/ngrx-rtk-query/src/lib/create-api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { type Action } from '@ngrx/store';
import type { Signal } from '@angular/core';
import { Store, type Action } from '@ngrx/store';
import type { SelectSignalOptions } from '@ngrx/store/src/models';
import {
buildCreateApi,
coreModule,
Expand All @@ -9,41 +11,52 @@ import {
} from '@reduxjs/toolkit/query';
import { angularHooksModule, angularHooksModuleName, type AngularHooksModule, type Dispatch } from './module';

import { dispatch as _dispatch, getState as _getState, select } from './thunk.service';

export const createApi: CreateApi<typeof coreModuleName | typeof angularHooksModuleName> = (options) => {
const reducerPath = options.reducerPath as string;
const getState = () => {
const storeState = _getState();
return storeState?.[reducerPath]
? storeState
: // Query inside forFeature (Code splitting)
{ [reducerPath]: storeState };
};

const next = (action: unknown): unknown => {
if (typeof action === 'function') {
return action(dispatch, getState, {});
return action(dispatch, storeState, { injector: getApiInjector() });
}
return _dispatch(action as Action);
return storeDispatch(action as Action);
};
const dispatch = (action: unknown): unknown => middleware(next)(action);
const getState = () => storeState();
const useSelector = <K>(mapFn: (state: any) => K, options?: SelectSignalOptions<K>): Signal<K> =>
storeSelect(mapFn, options);

const createApi = /* @__PURE__ */ buildCreateApi(
coreModule(),
angularHooksModule({
hooks: {
dispatch: dispatch as Dispatch,
getState,
useSelector: select,
useSelector,
},
}),
);
const api = createApi(options);

const getApiInjector = () =>
(api as unknown as Api<any, Record<string, any>, string, string, AngularHooksModule | CoreModule>).injector;
const getStore = () => getApiInjector().get(Store);
const storeDispatch = (action: Action) => {
getStore().dispatch(action);
return action;
};
const storeState = () => {
const storeState = getStore().selectSignal((state) => state)();
return storeState?.[reducerPath]
? storeState
: // Query inside forFeature (Code splitting)
{ [reducerPath]: storeState };
};
const storeSelect = <K>(mapFn: (state: any) => K, options?: SelectSignalOptions<K>): Signal<K> =>
getStore().selectSignal(mapFn, options);

const middleware = (
api as unknown as Api<any, Record<string, any>, string, string, AngularHooksModule | CoreModule>
).middleware({ dispatch, getState });
).middleware({ dispatch, getState: storeState });

return api;
};
4 changes: 2 additions & 2 deletions projects/ngrx-rtk-query/src/lib/fetch-base-query.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { runInInjectionContext } from '@angular/core';
import { runInInjectionContext, type Injector } from '@angular/core';
import type { FetchBaseQueryArgs } from '@reduxjs/toolkit/dist/query/fetchBaseQuery';
import { fetchBaseQuery as fetchBaseQueryDefault } from '@reduxjs/toolkit/query';
import { injector } from './thunk.service';

export type FetchBaseQueryFactory = () => ReturnType<typeof fetchBaseQueryDefault>;

Expand All @@ -12,6 +11,7 @@ export function fetchBaseQuery(
): ReturnType<typeof fetchBaseQueryDefault> {
if (typeof paramsOrFactory === 'object') return fetchBaseQueryDefault(paramsOrFactory as FetchBaseQueryArgs);
return async (args, api, extraOptions) => {
const injector = (api.extra as any).injector as Injector;
const baseQuery = runInInjectionContext(injector, paramsOrFactory as FetchBaseQueryFactory);
return await baseQuery(args, api, extraOptions);
};
Expand Down
60 changes: 15 additions & 45 deletions projects/ngrx-rtk-query/src/lib/module.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { isDevMode } from '@angular/core';
import { type Injector, type Signal } from '@angular/core';
import { createSelectorFactory, defaultMemoize, type Action } from '@ngrx/store';
import type { SelectSignalOptions } from '@ngrx/store/src/models';
import type { ThunkAction } from '@reduxjs/toolkit';
import type {
Api,
Expand All @@ -14,7 +15,6 @@ import type {
} from '@reduxjs/toolkit/query';

import { buildHooks } from './build-hooks';
import { dispatch as _dispatch, getState as _getState, select } from './thunk.service';
import {
isMutationDefinition,
isQueryDefinition,
Expand Down Expand Up @@ -61,9 +61,13 @@ declare module '@reduxjs/toolkit/query' {
options?: PrefetchOptions,
): (arg: QueryArgFrom<Definitions[EndpointName]>, options?: PrefetchOptions) => void;
/**
* A hook that provides access to the store's api dispatch function.
* Provides access to the api dispatch function.
*/
dispatch: Dispatch;
/**
* Provides access to the api injector.
*/
injector: Injector;
} & HooksWithUniqueNames<Definitions>;
}
}
Expand All @@ -82,11 +86,11 @@ export interface AngularHooksModuleOptions {
/**
* The version of the `getState` to be used
*/
getState: typeof _getState;
getState: () => any;
/**
* The version of the `useSelector` hook to be used
*/
useSelector: typeof select;
useSelector: <K>(mapFn: (state: any) => K, options?: SelectSignalOptions<K>) => Signal<K>;
};
/**
* A selector creator (usually from `reselect`, or matching the same signature)
Expand All @@ -103,9 +107,9 @@ export interface AngularHooksModuleOptions {
* coreModule(),
* angularHooksModule({
* hooks: {
* useDispatch: createDispatchHook(MyContext),
* useSelector: createSelectorHook(MyContext),
* useStore: createStoreHook(MyContext)
* dispatch: createDispatchHook(MyContext),
* getState: createSelectorHook(MyContext),
* useSelector: createStoreHook(MyContext)
* }
* })
* );
Expand All @@ -114,55 +118,21 @@ export interface AngularHooksModuleOptions {
* @returns A module for use with `buildCreateApi`
*/
export const angularHooksModule = ({
hooks = {
dispatch: _dispatch as Dispatch,
useSelector: select,
getState: _getState,
},
hooks,
createSelector = _createSelector,
...rest
}: AngularHooksModuleOptions = {}): Module<AngularHooksModule> => {
if (isDevMode()) {
const hookNames = ['dispatch', 'useSelector', 'getState'] as const;
let warned = false;
for (const hookName of hookNames) {
// warn for old hook options
if (Object.keys(rest).length > 0) {
if ((rest as Partial<typeof hooks>)[hookName]) {
if (!warned) {
console.warn(
'As of RTK 2.0, the hooks now need to be specified as one object, provided under a `hooks` key:' +
'\n`angularHooksModule({ hooks: { dispatch, useSelector, getState } })`',
);
warned = true;
}
}
// @ts-expect-error migrate
hooks[hookName] = rest[hookName];
}
// then make sure we have them all
if (typeof hooks[hookName] !== 'function') {
throw new Error(
`When using custom hooks for context, all ${hookNames.length} hooks need to be provided: ${hookNames.join(
', ',
)}.\nHook ${hookName} was either not provided or not a function.`,
);
}
}
}

return {
name: angularHooksModuleName,
init(api, { serializeQueryArgs }, context) {
const anyApi = api as any as Api<any, Record<string, any>, any, any, AngularHooksModule>;
const { buildQueryHooks, buildMutationHook, usePrefetch } = buildHooks({
api,
moduleOptions: { hooks, createSelector },
moduleOptions: { hooks: hooks!, createSelector },
serializeQueryArgs,
context,
});
safeAssign(anyApi, { usePrefetch });
safeAssign(anyApi, { dispatch: hooks.dispatch });
safeAssign(anyApi, { dispatch: hooks!.dispatch });

return {
injectEndpoint(endpointName, definition) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { ENVIRONMENT_INITIALIZER, inject, makeEnvironmentProviders, type EnvironmentProviders } from '@angular/core';
import {
ENVIRONMENT_INITIALIZER,
Injector,
inject,
makeEnvironmentProviders,
type EnvironmentProviders,
} from '@angular/core';
import { provideState } from '@ngrx/store';
import { setupListeners as setupListenersFn, type Api } from '@reduxjs/toolkit/query';
import { ThunkService } from './thunk.service';

export interface StoreQueryConfig {
setupListeners?: Parameters<typeof setupListenersFn>[1] | false;
Expand All @@ -18,7 +23,8 @@ export function provideStoreApi(
provide: ENVIRONMENT_INITIALIZER,
multi: true,
useValue() {
inject(ThunkService).init();
const injector = inject(Injector);
Object.assign(api, { injector });
},
},
provideState(api.reducerPath, api.reducer),
Expand Down
41 changes: 0 additions & 41 deletions projects/ngrx-rtk-query/src/lib/thunk.service.ts

This file was deleted.

2 changes: 1 addition & 1 deletion projects/ngrx-rtk-query/src/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { angularHooksModule, angularHooksModuleName } from './lib/module';

export * from '@reduxjs/toolkit/query';
export { fetchBaseQuery } from './lib/fetch-base-query';
export { provideStoreApi } from './lib/provide-rtk-query';
export { provideStoreApi } from './lib/provide-store-api';

export { createApi } from './lib/create-api';

Expand Down

0 comments on commit 27a5caa

Please sign in to comment.