diff --git a/CHANGELOG.md b/CHANGELOG.md index 917e235..9175ee1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,61 @@ +# [17.3.0-next.8](https://github.com/SaulMoro/ngrx-rtk-query/compare/v17.3.0-next.7...v17.3.0-next.8) (2024-03-12) + + +### Bug Fixes + +* move lazy signal to useQueryState and add to options ([d884770](https://github.com/SaulMoro/ngrx-rtk-query/commit/d88477096dfe4fa3e9d5e5e23c5c2cf3c0de209d)) + +# [17.3.0-next.7](https://github.com/SaulMoro/ngrx-rtk-query/compare/v17.3.0-next.6...v17.3.0-next.7) (2024-03-11) + + +### Bug Fixes + +* required signal inputs errors v2 ([1960a40](https://github.com/SaulMoro/ngrx-rtk-query/commit/1960a404d5708b06c1195937cd2c77b6d2254f22)) + +# [17.3.0-next.6](https://github.com/SaulMoro/ngrx-rtk-query/compare/v17.3.0-next.5...v17.3.0-next.6) (2024-03-11) + + +### Bug Fixes + +* package tags ([1f42bbc](https://github.com/SaulMoro/ngrx-rtk-query/commit/1f42bbcf7d2113cf39e3b4270628f88ed1e839b5)) +* required signal inputs errors ([47e8905](https://github.com/SaulMoro/ngrx-rtk-query/commit/47e89052204105ed2f8edf512b352d9703c6e364)) + +# [17.3.0-next.5](https://github.com/SaulMoro/ngrx-rtk-query/compare/v17.3.0-next.4...v17.3.0-next.5) (2024-03-11) + + +### Bug Fixes + +* **lib:** select from result when options is signal or function ([e2007cf](https://github.com/SaulMoro/ngrx-rtk-query/commit/e2007cf3072fff7c0c2f6b15995c9a5b0284e210)) + +# [17.3.0-next.4](https://github.com/SaulMoro/ngrx-rtk-query/compare/v17.3.0-next.3...v17.3.0-next.4) (2024-03-07) + + +### Bug Fixes + +* better serializable stable value ([fdb5316](https://github.com/SaulMoro/ngrx-rtk-query/commit/fdb5316e1cf9989eb391460cdfc5479a34da5d6f)) +* new signal proxy ([f6f7147](https://github.com/SaulMoro/ngrx-rtk-query/commit/f6f7147726a40904858c4c7ec221174440c058ab)) + +# [17.3.0-next.3](https://github.com/SaulMoro/ngrx-rtk-query/compare/v17.3.0-next.2...v17.3.0-next.3) (2024-03-06) + + +### Features + +* **lib:** remove dispatch and fetch functions ([6f441fb](https://github.com/SaulMoro/ngrx-rtk-query/commit/6f441fb0bc23fa0ca237e1ec5bc1e8a46bad6c43)) + +# [17.3.0-next.2](https://github.com/SaulMoro/ngrx-rtk-query/compare/v17.3.0-next.1...v17.3.0-next.2) (2024-02-29) + + +### Bug Fixes + +* new query deep signals ([87b4b45](https://github.com/SaulMoro/ngrx-rtk-query/commit/87b4b45e64275d74d71aa672e75a25eaddc8a792)) + +# [17.3.0-next.1](https://github.com/SaulMoro/ngrx-rtk-query/compare/v17.2.1...v17.3.0-next.1) (2024-02-28) + + +### Features + +* **lib:** add deep signals and refactor mutations and lazy ([c7f3ca0](https://github.com/SaulMoro/ngrx-rtk-query/commit/c7f3ca091155cf09b120b29b3e515426a5294049)) + ## [17.2.1](https://github.com/SaulMoro/ngrx-rtk-query/compare/v17.2.0...v17.2.1) (2024-02-27) diff --git a/README.md b/README.md index 6d64e0e..92c8980 100644 --- a/README.md +++ b/README.md @@ -31,43 +31,21 @@ ## Installation +```bash +npm install ngrx-rtk-query +``` + ### Versions | Angular / NgRx | ngrx-rtk-query | @reduxjs/toolkit | Support | | :----------------: | :--------------------: | :--------------: | :-----------------: | -| 17.x | >=17.1.x (signals) | ~2.2.1 | Bugs / New Features | -| 17.x | >=17.0.x (signals) | ~1.9.7 | Bugs | -| 16.x | >=16.x.x (signals) | ~1.9.7 | Bugs | +| 17.x | >=17.3.x (signals) | ~2.2.1 | Bugs / New Features | +| 17.x | >=17.1.x (signals) | ~2.2.1 | Bugs | | 16.x | >=4.2.x (rxjs) | ~1.9.5 | Critical bugs | | 15.x | 4.1.x (rxjs) | 1.9.5 | None | Only the latest version of Angular in the table above is actively supported. This is due to the fact that compilation of Angular libraries is [incompatible between major versions](https://angular.io/guide/creating-libraries#ensuring-library-version-compatibility). -You can install it with **npm**: - -```bash -npm install ngrx-rtk-query -``` - -When you install using **npm or yarn**, you will also need to use the **Standalone provider** `provideStoreApi` in your `app` or in a `lazy route`. You can also set setupListeners here: - -```typescript -import { provideStoreApi } from 'ngrx-rtk-query'; -import { api } from './route/to/api.ts'; - -bootstrapApplication(AppComponent, { - providers: [ - ... - - provideStoreApi(api), - // Or to disable setupListeners: - // provideStoreApi(api, { setupListeners: false }) - - ... - ], -}).catch((err) => console.error(err)); -``` - ## Basic Usage You can follow the official [RTK Query guide with hooks](https://redux-toolkit.js.org/rtk-query/overview), with slight variations. @@ -119,20 +97,23 @@ export const { } = counterApi; ``` -Add the api to your store +Add the api to your store in your `app` or in a `lazy route`. ```typescript import { provideStoreApi } from 'ngrx-rtk-query'; +import { counterApi } from './route/to/counterApi.ts'; -... +bootstrapApplication(AppComponent, { providers: [ ... provideStoreApi(counterApi), + // Or to disable setupListeners: + // provideStoreApi(counterApi, { setupListeners: false }) ... ], -... +}).catch((err) => console.error(err)); ``` Use the query in a component @@ -145,15 +126,15 @@ import { useDecrementCountMutation, useGetCountQuery, useIncrementCountMutation template: `
- {{ countQuery().data?.count || 0 }} + {{ countQuery.data()?.count ?? 0 }}
`, @@ -169,7 +150,7 @@ export class CounterManagerComponent { ## Usage with HttpClient or injectable service -You can use the `fetchBaseQuery` function to create a base query that uses the Angular `HttpClient` to make requests or any injectable service. Example: +You can use the `fetchBaseQuery` function to create a base query that uses the Angular `HttpClient` to make requests or any injectable service. Basic HttpClient example: ```ts @@ -210,31 +191,49 @@ The use of queries is a bit different compared to the original [Queries - RTK Qu The parameters and options of the Query can be **signals** or static. You can update the signal to change the parameter/option. -The hook `useXXXQuery()` returns a signal with all the information indicated in the official documentation (including `refetch()` function). +The hook `useXXXQuery()` returns a signal with all the information indicated in the official documentation (including `refetch()` function). Can be used as an object with each of its properties acting like a signal. For example, 'isLoading' can be accessed as `xxxQuery.isLoading()` or `xxxQuery().isLoading()`. The first case offers a more fine-grained change detection. ```ts // Use query without params or options postsQuery = useGetPostsQuery(); -// Use query with static params or options +// Use query with signals params or options (can be mixed with static) +postQuery = useGetPostsQuery(myArgSignal, myOptionsSignal); + +// Use query with function (similar to a computed), detect changes in the function (can be mixed) +postQuery = useGetPostsQuery(() => id(), () => ({ skip: id() <= 5 })); + +// Use query with static params or options (can be mixed) postQuery = useGetPostsQuery(2, { selectFromResult: ({ data: post, isLoading }) => ({ post, isLoading }), }); +``` -// Use query with signals params or options (can be mixed with static) -id = signal(2); -options = signal(...); -postQuery = useGetPostsQuery(id, options); +A good use case is to work with router inputs. + +```ts +// ... +{{ locationQuery.isLoading() }} +{{ locationQuery.data() }} +// ... + +export class CharacterCardComponent { + readonly characterParamId = input.required(); + readonly characterQuery = useGetCharacterQuery(this.characterParamId); + +// ... ``` -Another good use case is with signals inputs and skipToken +Another good use case is with signals inputs not required and use skipToken ```ts -{{ locationQuery().data }} +// ... +{{ locationQuery.data() }} +// ... export class CharacterCardComponent implements OnInit { readonly character = input(undefined); - readonly locationQuery = useGetLocationQuery(computed(() => this.character()?.currentLocation ?? skipToken)); + readonly locationQuery = useGetLocationQuery(() => this.character()?.currentLocation ?? skipToken); // ... ``` @@ -243,56 +242,53 @@ export class CharacterCardComponent implements OnInit { The use of lazy queries is a bit different compared to the original. As in the case of queries, the parameters and options of the Query can be signal or static. You can look at lazy feature example from this repository. -Like in the original library, a lazy returns a object (not array) of 3 items, but the structure and naming of the items is different. - -- `fetch(arg)`: This function is the trigger to run the fetch action. -- `state`: Signal that returns an object with the query state. -- `lastArg`: Signal that returns the last argument. +Like in the original library, a lazy query returns a object (not array) with each of its properties acting like a signal. ```ts // Use query without options postsQuery = useLazyGetPostsQuery(); +// Use query with signal options +options = signal(...); +postQuery = useLazyGetPostsQuery(options); // Use query with static options postQuery = useLazyGetPostsQuery({ selectFromResult: ({ data: post, isLoading }) => ({ post, isLoading }), }); -// Use query with signal options -options = signal(...); -postQuery = useLazyGetPostsQuery(options); ``` Use when data needs to be loaded on demand ```ts -{{ xxxQuery.state().data }} +//... +{{ xxxQuery.data() }} {{ xxxQuery.lastArg() }} - //... export class XxxComponent { xxxQuery = useLazyGetXxxQuery(); // ... - xxx(id: string) { - this.xxxQuery.fetch(id).unwrap(); + this.xxxQuery(id).unwrap(); } - // ... ``` -Another good use case is to work with nested or relational data +Another use case is to work with nested or relational data. + +> [!TIP] +> We advise using 'query' instead of 'lazy query' for these cases for more declarative code. ```ts -{{ locationQuery.state().data }} +{{ locationQuery.data() }} export class CharacterCardComponent implements OnInit { - @Input() character: Character; + readonly character = input.required(); locationQuery = useLazyGetLocationQuery(); ngOnInit(): void { - this.locationQuery.fetch(this.character.currentLocation, { preferCacheValue: true }); + this.locationQuery(this.character().currentLocation, { preferCacheValue: true }); } // ... @@ -305,19 +301,36 @@ Perfect for ngOnInit cases. You can look at pagination feature example from this The use of mutations is a bit different compared to the original [Mutations - RTK Query guide](https://redux-toolkit.js.org/rtk-query/usage/mutations). You can look at the examples from this repository. -Like in the original library, a mutation is a object (not array) of 2 items, but the structure and naming of the items is different. - -- `dispatch(params)`: This function is the trigger to run the mutation action. -- `state`: Signal that returns an object with the state, including the status flags and other info (see official docs). +Like in the original library, a mutation is a object (not array) with each of its properties acting like a signal. ```ts // Use mutation hook addPost = useAddPostMutation(); // Mutation trigger -addPost.dispatch({params}); -// Signal with the state of mutation -addPost.state() +this.addPost({params}); + + +// Can unwrap the mutation to do a action + +this.addPost({params}).unwrap().then((data) => { + // Do something with data +}).catch((error) => { + // Do something with error +}); + +// Or + +try { + const data = await this.addPost({params}).unwrap(); + // Do something with data +} catch (error) { + // Do something with error +} + +// Signal with the state of mutation to use in the template or component (isLoading, data, error, isSuccess, etc) +addPost.isLoading(); +addPost.data(); ``` ### **Code-splitted/Lazy feature/Lazy modules** @@ -329,7 +342,6 @@ Import this module where needed. You can look at posts feature example from this ```ts // ... - export const postsApi = createApi({ reducerPath: 'postsApi', baseQuery: baseQueryWithRetry, @@ -338,20 +350,17 @@ export const postsApi = createApi({ // ... }), }); - // ... import { provideStoreApi } from 'ngrx-rtk-query'; -... +// ... providers: [ - ... - + // ... provideStoreApi(postsApi), - - ... + // ... ], -... +// ... ```
diff --git a/projects/ngrx-rtk-query/package.json b/projects/ngrx-rtk-query/package.json index d2e0c0a..2cee207 100644 --- a/projects/ngrx-rtk-query/package.json +++ b/projects/ngrx-rtk-query/package.json @@ -11,12 +11,13 @@ "tslib": "^2.0.0" }, "keywords": [ - "angular", + "angular query", "angular rtk query", "ngrx-rtk-query", "ngrx rtk query", "rtk-query", "ngrx", + "angular", "signals", "query", "redux", diff --git a/projects/ngrx-rtk-query/src/lib/build-hooks.ts b/projects/ngrx-rtk-query/src/lib/build-hooks.ts index 61fb77b..1e77340 100644 --- a/projects/ngrx-rtk-query/src/lib/build-hooks.ts +++ b/projects/ngrx-rtk-query/src/lib/build-hooks.ts @@ -1,4 +1,4 @@ -import { DestroyRef, computed, effect, inject, isDevMode, signal, untracked } from '@angular/core'; +import { DestroyRef, computed, effect, inject, isDevMode, isSignal, signal, untracked } from '@angular/core'; import type { Action, DefaultProjectorFn, MemoizedSelector } from '@ngrx/store'; import type { SubscriptionSelectors } from '@reduxjs/toolkit/dist/query/core/buildMiddleware/types'; import type { @@ -36,7 +36,7 @@ import type { UseQuerySubscription, } from './types'; import { useStableQueryArgs } from './useSerializedStableValue'; -import { shallowEqual } from './utils'; +import { shallowEqual, signalProxy, toDeepSignal, toLazySignal } from './utils'; /** * Wrapper around `defaultQueryStateSelector` to be used in `useQuery`. @@ -128,6 +128,13 @@ export function buildHooks({ isFetching, isLoading, isSuccess, + // Deep signals required init in undefined atleast + endpointName: currentState.endpointName, + error: currentState.error, + fulfilledTimeStamp: currentState.fulfilledTimeStamp, + originalArgs: currentState.originalArgs, + requestId: currentState.requestId, + startedTimeStamp: currentState.startedTimeStamp, } as UseQueryStateDefaultResult; } @@ -335,12 +342,24 @@ export function buildHooks({ }; const useQueryState: UseQueryState = (arg: any, options = {}) => { + // We need to use `toLazySignal` here to prevent 'signal required inputs' errors + const lazyArg = isSignal(arg) + ? toLazySignal(arg, { initialValue: skipToken }) + : typeof arg === 'function' + ? arg + : () => arg; + const lazyOptions = isSignal(options) + ? toLazySignal(options, { initialValue: {} }) + : typeof options === 'function' + ? options + : () => options; + const stateOptions = computed(() => { - const { skip = false, selectFromResult } = typeof options === 'function' ? options() : options; + const { skip = false, selectFromResult } = lazyOptions(); return { skip, selectFromResult }; }); const subscriptionArg = computed(() => { - const subscriptionArg = typeof arg === 'function' ? arg() : arg; + const subscriptionArg = lazyArg(); return stateOptions().skip ? skipToken : subscriptionArg; }); @@ -370,8 +389,9 @@ export function buildHooks({ lastValue = selectDefaultResult(getState()); return currentState(); }); + const deepSignal = toDeepSignal(currentState); - return currentState; + return deepSignal as any; }; return { @@ -385,20 +405,27 @@ export function buildHooks({ skip: arg() === UNINITIALIZED_VALUE, })); const queryStateResults = useQueryState(arg, subscriptionOptions); + const signalsMap = signalProxy(queryStateResults); + Object.assign(trigger, { lastArg: arg }); + Object.assign(trigger, signalsMap); - return { fetch: trigger, state: queryStateResults, lastArg: arg } as const; + return trigger as any; }, useQuery(arg, options) { const querySubscriptionResults = useQuerySubscription(arg, options); const subscriptionOptions = computed(() => { - const subscriptionArg = typeof arg === 'function' ? arg() : arg; - const { skip } = typeof options === 'function' ? options() : options || {}; - const selectFromResult = subscriptionArg === skipToken || skip ? undefined : noPendingQueryStateSelector; - return { selectFromResult, ...options }; + const subscriptionArg = typeof arg === 'function' ? arg() : options; + const subscriptionOptions = typeof options === 'function' ? options() : options; + return { + selectFromResult: + subscriptionArg === skipToken || subscriptionOptions?.skip ? undefined : noPendingQueryStateSelector, + ...subscriptionOptions, + }; }); const queryStateResults = useQueryState(arg, subscriptionOptions); + Object.assign(queryStateResults, querySubscriptionResults); - return computed(() => ({ ...queryStateResults(), ...querySubscriptionResults })); + return queryStateResults as any; }, selector: select as QuerySelector, }; @@ -428,8 +455,22 @@ export function buildHooks({ return promise; }; + const fixedSelect: typeof select = (args) => (state) => { + const currentState = select(args)(state); + return { + ...currentState, + // Deep signals required init in undefined atleast + data: currentState.data, + endpointName: currentState.endpointName, + error: currentState.error, + fulfilledTimeStamp: currentState.fulfilledTimeStamp, + requestId: currentState.requestId, + startedTimeStamp: currentState.startedTimeStamp, + } as any; + }; + const requestId = computed(() => promiseRef()?.requestId); - const selectDefaultResult = (requestId?: string) => select({ fixedCacheKey, requestId }); + const selectDefaultResult = (requestId?: string) => fixedSelect({ fixedCacheKey, requestId }); const mutationSelector = ( requestId?: string, ): MemoizedSelector, any, DefaultProjectorFn> => @@ -457,9 +498,13 @@ export function buildHooks({ } }; - const finalState = computed(() => ({ ...currentState()(), originalArgs: originalArgs(), reset })); + const finalState = computed(() => currentState()()); + const signalsMap = signalProxy(finalState); + Object.assign(triggerMutation, { originalArgs }); + Object.assign(triggerMutation, { reset }); + Object.assign(triggerMutation, signalsMap); - return { dispatch: triggerMutation, state: finalState } as const; + return triggerMutation as any; }; return { diff --git a/projects/ngrx-rtk-query/src/lib/create-api.ts b/projects/ngrx-rtk-query/src/lib/create-api.ts index 3005262..b1e84c0 100644 --- a/projects/ngrx-rtk-query/src/lib/create-api.ts +++ b/projects/ngrx-rtk-query/src/lib/create-api.ts @@ -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, @@ -9,25 +11,19 @@ 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 = (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 = (mapFn: (state: any) => K, options?: SelectSignalOptions): Signal => + storeSelect(mapFn, options); const createApi = /* @__PURE__ */ buildCreateApi( coreModule(), @@ -35,15 +31,32 @@ export const createApi: CreateApi + (api as unknown as Api, 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 = (mapFn: (state: any) => K, options?: SelectSignalOptions): Signal => + getStore().selectSignal(mapFn, options); + const middleware = ( api as unknown as Api, string, string, AngularHooksModule | CoreModule> - ).middleware({ dispatch, getState }); + ).middleware({ dispatch, getState: storeState }); return api; }; diff --git a/projects/ngrx-rtk-query/src/lib/fetch-base-query.ts b/projects/ngrx-rtk-query/src/lib/fetch-base-query.ts index 7097a60..8773588 100644 --- a/projects/ngrx-rtk-query/src/lib/fetch-base-query.ts +++ b/projects/ngrx-rtk-query/src/lib/fetch-base-query.ts @@ -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; @@ -12,6 +11,7 @@ export function fetchBaseQuery( ): ReturnType { 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); }; diff --git a/projects/ngrx-rtk-query/src/lib/module.ts b/projects/ngrx-rtk-query/src/lib/module.ts index 641c6e7..923adc3 100644 --- a/projects/ngrx-rtk-query/src/lib/module.ts +++ b/projects/ngrx-rtk-query/src/lib/module.ts @@ -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, @@ -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, @@ -61,9 +61,13 @@ declare module '@reduxjs/toolkit/query' { options?: PrefetchOptions, ): (arg: QueryArgFrom, 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; } } @@ -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: (mapFn: (state: any) => K, options?: SelectSignalOptions) => Signal; }; /** * A selector creator (usually from `reselect`, or matching the same signature) @@ -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) * } * }) * ); @@ -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 => { - 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)[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, 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) { diff --git a/projects/ngrx-rtk-query/src/lib/provide-rtk-query.ts b/projects/ngrx-rtk-query/src/lib/provide-store-api.ts similarity index 75% rename from projects/ngrx-rtk-query/src/lib/provide-rtk-query.ts rename to projects/ngrx-rtk-query/src/lib/provide-store-api.ts index fdf68ba..18f4e64 100644 --- a/projects/ngrx-rtk-query/src/lib/provide-rtk-query.ts +++ b/projects/ngrx-rtk-query/src/lib/provide-store-api.ts @@ -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[1] | false; @@ -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), diff --git a/projects/ngrx-rtk-query/src/lib/thunk.service.ts b/projects/ngrx-rtk-query/src/lib/thunk.service.ts deleted file mode 100644 index 34fc0f1..0000000 --- a/projects/ngrx-rtk-query/src/lib/thunk.service.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { inject, Injectable, Injector, type Signal } from '@angular/core'; -import { Store, type Action } from '@ngrx/store'; -import type { SelectSignalOptions } from '@ngrx/store/src/models'; - -let service: ThunkService; -export let injector: Injector; - -export function dispatch(action: Action) { - service?.dispatch(action); - return action; -} - -export function getState() { - return service?.getState(); -} - -export function select(mapFn: (state: any) => K, options?: SelectSignalOptions): Signal { - return service?.select(mapFn, options); -} - -@Injectable({ providedIn: 'root' }) -export class ThunkService { - readonly #injector = inject(Injector); - readonly #store = inject(Store); - - init() { - // eslint-disable-next-line @typescript-eslint/no-this-alias - service = this; - injector = this.#injector; - } - - getState = this.#store.selectSignal((state) => state); - - dispatch(action: Action): void { - this.#store.dispatch(action); - } - - select(mapFn: (state: any) => K, options?: SelectSignalOptions): Signal { - return this.#store.selectSignal(mapFn, options); - } -} diff --git a/projects/ngrx-rtk-query/src/lib/types/hooks-types.ts b/projects/ngrx-rtk-query/src/lib/types/hooks-types.ts index 7e3f22c..418d81f 100644 --- a/projects/ngrx-rtk-query/src/lib/types/hooks-types.ts +++ b/projects/ngrx-rtk-query/src/lib/types/hooks-types.ts @@ -22,6 +22,7 @@ import type { TSHelpersOverride, } from '@reduxjs/toolkit/query'; import type { UninitializedValue } from '../constants'; +import { DeepSignal, SignalsMap } from '../utils'; export interface QueryHooks> { useQuery: UseQuery; @@ -67,9 +68,9 @@ export type MutationSelector> = < R extends Record = UseQueryStateDefaultResult, >( - arg: Signal | SkipToken> | QueryArgFrom | SkipToken, - options?: UseQueryOptions | Signal>, -) => Signal>; + arg: Signal | SkipToken> | (() => QueryArgFrom | SkipToken) | QueryArgFrom | SkipToken, + options?: UseQueryOptions | Signal> | (() => UseQueryOptions), +) => UseQueryHookResult; export type TypedUseQuery = UseQuery< QueryDefinition @@ -156,8 +157,8 @@ interface UseQuerySubscriptionOptions extends SubscriptionOptions { * - Accepts polling/re-fetching options to trigger automatic re-fetches when the corresponding criteria is met */ export type UseQuerySubscription> = ( - arg: Signal | SkipToken> | QueryArgFrom | SkipToken, - options?: UseQuerySubscriptionOptions | Signal, + arg: Signal | SkipToken> | (() => QueryArgFrom | SkipToken) | QueryArgFrom | SkipToken, + options?: UseQuerySubscriptionOptions | Signal | (() => UseQuerySubscriptionOptions), ) => UseQuerySubscriptionResult; export type TypedUseQuerySubscription = UseQuerySubscription< @@ -194,7 +195,7 @@ export type LazyQueryTrigger> = { * ```ts * // codeblock-meta title="Using .unwrap with async await" * try { - * const payload = await xxxLazyQuery.fetch(1).unwrap(); + * const payload = await xxxLazyQuery(1).unwrap(); * console.log('fulfilled', payload) * } catch (error) { * console.error('rejected', error); @@ -236,12 +237,11 @@ export type UseLazyQueryLastPromiseInfo> = < R extends Record = UseQueryStateDefaultResult, >( - options?: UseLazyQueryOptions | Signal>, -) => { - fetch: LazyQueryTrigger; - state: Signal>; - lastArg: Signal>; -}; + options?: UseLazyQueryOptions | Signal> | (() => UseLazyQueryOptions), +) => LazyQueryTrigger & + UseLazyQueryStateResult & { + lastArg: Signal>; + }; export type TypedUseLazyQuery = UseLazyQuery< QueryDefinition @@ -271,7 +271,7 @@ export type LazyQueryOptions< * and the fetch has been manually called at least once */ export type UseLazyQuerySubscription> = ( - options?: SubscriptionOptions | Signal, + options?: SubscriptionOptions | Signal | (() => SubscriptionOptions), ) => readonly [LazyQueryTrigger, Signal | UninitializedValue>]; export type TypedUseLazyQuerySubscription< @@ -303,9 +303,9 @@ export type TypedUseQueryState> = < R extends Record = UseQueryStateDefaultResult, >( - arg: Signal | SkipToken> | QueryArgFrom | SkipToken, - options?: UseQueryStateOptions | Signal>, -) => Signal>; + arg: Signal | SkipToken> | (() => QueryArgFrom | SkipToken) | QueryArgFrom | SkipToken, + options?: UseQueryStateOptions | Signal> | (() => UseQueryStateOptions), +) => UseQueryStateResult; export type UseQueryStateOptions, R extends Record> = { /** @@ -350,7 +350,8 @@ export type UseQueryStateOptions, selectFromResult?: QueryStateSelector; }; -export type UseQueryStateResult<_ extends QueryDefinition, R> = TSHelpersNoInfer; +export type UseQueryStateResult<_ extends QueryDefinition, R> = DeepSignal>; +export type UseLazyQueryStateResult<_ extends QueryDefinition, R> = SignalsMap>; /** * Helper type to manually type the result @@ -361,7 +362,7 @@ export type TypedUseQueryStateResult< QueryArg, BaseQuery extends BaseQueryFn, R = UseQueryStateDefaultResult>, -> = TSHelpersNoInfer; +> = DeepSignal>; type UseQueryStateBaseResult> = QuerySubState & { /** @@ -430,8 +431,10 @@ export type UseMutationStateOptions, R> = TSHelpersNoInfer & { - originalArgs?: QueryArgFrom; +export type UseMutationStateResult, R> = SignalsMap< + TSHelpersNoInfer +> & { + originalArgs: Signal | undefined>; /** * Resets the hook state to it's initial `uninitialized` state. * This will also remove the last result from the cache. @@ -465,10 +468,7 @@ export type UseMutation> = < R extends Record = MutationResultSelectorResult, >( options?: UseMutationStateOptions, -) => { - dispatch: MutationTrigger; - state: Signal>; -}; +) => MutationTrigger & UseMutationStateResult; export type TypedUseMutation = UseMutation< MutationDefinition @@ -484,7 +484,7 @@ export type MutationTrigger> = * ```ts * // codeblock-meta title="Using .unwrap with async await" * try { - * const payload = await this.addPostMutation.dispatch({ id: 1, name: 'Example' }).unwrap(); + * const payload = await this.addPostMutation({ id: 1, name: 'Example' }).unwrap(); * console.log('fulfilled', payload) * } catch (error) { * console.error('rejected', error); @@ -494,8 +494,7 @@ export type MutationTrigger> = * @example * ```ts * // codeblock-meta title="Using .unwrap with async await" - * this.deletePostMutation - * .dispatch(+this.route.snapshot.params.id) + * this.deletePostMutation(+this.route.snapshot.params.id) * .unwrap() * .then(() => this.router.navigate(['/posts'])) * .catch(() => console.error('Error deleting Post')); diff --git a/projects/ngrx-rtk-query/src/lib/useSerializedStableValue.ts b/projects/ngrx-rtk-query/src/lib/useSerializedStableValue.ts index 3dad160..6b57095 100644 --- a/projects/ngrx-rtk-query/src/lib/useSerializedStableValue.ts +++ b/projects/ngrx-rtk-query/src/lib/useSerializedStableValue.ts @@ -1,4 +1,4 @@ -import { Signal, computed, effect } from '@angular/core'; +import { Signal, computed } from '@angular/core'; import type { EndpointDefinition, SerializeQueryArgs } from '@reduxjs/toolkit/query'; export function useStableQueryArgs( @@ -7,22 +7,18 @@ export function useStableQueryArgs( endpointDefinition: EndpointDefinition, endpointName: string, ) { - const incoming = computed(() => { - const incomingArgs = queryArgs(); - return { - queryArgs: incomingArgs, - serialized: - typeof incomingArgs == 'object' - ? serialize({ queryArgs: incomingArgs, endpointDefinition, endpointName }) - : incomingArgs, - }; - }); - let cache = incoming(); - effect(() => { - if (cache.serialized !== incoming().serialized) { - cache = incoming(); - } - }); - - return computed(() => (cache.serialized === incoming().serialized ? cache.queryArgs : queryArgs())); + const incoming = computed( + () => { + const incomingArgs = queryArgs(); + return { + queryArgs: incomingArgs, + serialized: + typeof incomingArgs == 'object' + ? serialize({ queryArgs: incomingArgs, endpointDefinition, endpointName }) + : incomingArgs, + }; + }, + { equal: (a, b) => a.serialized === b.serialized }, + ); + return computed(() => incoming().queryArgs); } diff --git a/projects/ngrx-rtk-query/src/lib/utils/index.ts b/projects/ngrx-rtk-query/src/lib/utils/index.ts index 3e0c7cd..8776efb 100644 --- a/projects/ngrx-rtk-query/src/lib/utils/index.ts +++ b/projects/ngrx-rtk-query/src/lib/utils/index.ts @@ -1,3 +1,5 @@ export * from './capitalize'; +export * from './lazy-signal'; export * from './shallow-equal'; +export * from './signal-proxy'; export * from './tsHelpers'; diff --git a/projects/ngrx-rtk-query/src/lib/utils/lazy-signal.ts b/projects/ngrx-rtk-query/src/lib/utils/lazy-signal.ts new file mode 100644 index 0000000..77eeaaa --- /dev/null +++ b/projects/ngrx-rtk-query/src/lib/utils/lazy-signal.ts @@ -0,0 +1,12 @@ +import { effect, signal, untracked, type Signal } from '@angular/core'; + +export function toLazySignal(inputSignal: Signal, { initialValue }: { initialValue: T }): Signal { + const s = signal(initialValue as T); + + effect(() => { + const input = inputSignal(); + untracked(() => s.set(input)); + }); + + return s.asReadonly(); +} diff --git a/projects/ngrx-rtk-query/src/lib/utils/signal-proxy.ts b/projects/ngrx-rtk-query/src/lib/utils/signal-proxy.ts new file mode 100644 index 0000000..1cdf6e0 --- /dev/null +++ b/projects/ngrx-rtk-query/src/lib/utils/signal-proxy.ts @@ -0,0 +1,68 @@ +/** + * The code in this file is adapted from TanStack/query + * + * TanStack/query is an open-source project licensed under the MIT license. + * + * For more information about the original code, see + * https://github.com/TanStack/query + */ +import type { Signal as NgSignal } from '@angular/core'; +import { computed, untracked } from '@angular/core'; + +// An extended Signal type that enables the correct typing +// of nested signals with the `name` or `length` key. +export interface Signal extends NgSignal { + name: unknown; + length: unknown; +} + +export type SignalsMap = Required<{ + [K in keyof T]: T[K] extends Function ? T[K] : Signal; +}>; + +export type DeepSignal = Signal & SignalsMap; + +/** + * Exposes fields of an object passed via an Angular `Signal` as `Computed` signals. + * + * Functions on the object are passed through as-is. + * + * @param signal - `Signal` that must return an object. + * + */ +export function signalProxy>(signal: Signal): SignalsMap { + const internalState = {} as SignalsMap; + + return new Proxy>(internalState, { + get(target, prop) { + // first check if we have it in our internal state and return it + const computedField = target[prop]; + if (computedField) return computedField; + + // then, check if it's a function on the resultState and return it + const targetField = untracked(signal)[prop]; + if (typeof targetField === 'function') return targetField; + + // finally, create a computed field, store it and return it + // @ts-expect-error bypass + return (target[prop] = computed(() => signal()[prop])); + }, + has(_, prop) { + return !!untracked(signal)[prop]; + }, + ownKeys() { + return Reflect.ownKeys(untracked(signal)); + }, + getOwnPropertyDescriptor() { + return { + enumerable: true, + configurable: true, + }; + }, + }); +} + +export function toDeepSignal>(signal: Signal): DeepSignal { + const deepSignal = signalProxy(signal); + return Object.assign(signal, deepSignal); +} diff --git a/projects/ngrx-rtk-query/src/lib/utils/tsHelpers.ts b/projects/ngrx-rtk-query/src/lib/utils/tsHelpers.ts index 111b460..04fb0f5 100644 --- a/projects/ngrx-rtk-query/src/lib/utils/tsHelpers.ts +++ b/projects/ngrx-rtk-query/src/lib/utils/tsHelpers.ts @@ -3,3 +3,19 @@ import type { NoInfer } from '@reduxjs/toolkit/dist/tsHelpers'; export function safeAssign(target: T, ...args: Array>>): T { return Object.assign(target, ...args); } + +export type IsRecord = T extends object + ? T extends unknown[] + ? false + : T extends Set + ? false + : T extends Map + ? false + : T extends Function + ? false + : true + : false; + +export type IsUnknownRecord = string extends keyof T ? true : number extends keyof T ? true : false; + +export type IsKnownRecord = IsRecord extends true ? (IsUnknownRecord extends true ? false : true) : false; diff --git a/projects/ngrx-rtk-query/src/public-api.ts b/projects/ngrx-rtk-query/src/public-api.ts index c13a15b..61a2b49 100644 --- a/projects/ngrx-rtk-query/src/public-api.ts +++ b/projects/ngrx-rtk-query/src/public-api.ts @@ -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'; diff --git a/src/app/features/counter/counter-manager/counter-manager.component.ts b/src/app/features/counter/counter-manager/counter-manager.component.ts index 3f4b634..4883217 100644 --- a/src/app/features/counter/counter-manager/counter-manager.component.ts +++ b/src/app/features/counter/counter-manager/counter-manager.component.ts @@ -10,15 +10,9 @@ import { nanoid } from '@reduxjs/toolkit';

Main Counter

- - {{ countQuery().data?.count || 0 }} - + + {{ countQuery.data()?.count || 0 }} +
Decrease is a optimistic update! @@ -35,7 +29,7 @@ import { nanoid } from '@reduxjs/toolkit';
- +
@@ -50,8 +44,6 @@ export class CounterManagerComponent { counters: string[] = []; - constructor() {} - addCounter(): void { this.counters = [...this.counters, nanoid()]; } diff --git a/src/app/features/counter/counter/counter.component.ts b/src/app/features/counter/counter/counter.component.ts index 333d08d..e59ff9e 100644 --- a/src/app/features/counter/counter/counter.component.ts +++ b/src/app/features/counter/counter/counter.component.ts @@ -1,68 +1,56 @@ -import { ChangeDetectionStrategy, Component, Input, OnInit, computed, signal } from '@angular/core'; -import { LazyQueryOptions } from 'ngrx-rtk-query'; +import { ChangeDetectionStrategy, Component, input, signal } from '@angular/core'; -import { - useDecrementCountByIdMutation, - useIncrementCountByIdMutation, - useLazyGetCountByIdQuery, -} from '@app/core/services'; +import { useDecrementCountByIdMutation, useGetCountByIdQuery, useIncrementCountByIdMutation } from '@app/core/services'; import { pollingOptions } from '../utils/polling-options'; @Component({ selector: 'app-counter', template: `
-

{{ id }}

+

{{ counterId() }}

- - {{ - countQuery.state().data?.count || 0 + {{ + countQuery.data()?.count || 0 }} - - + +
`, styles: [], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class CounterComponent implements OnInit { - @Input() id = ''; +export class CounterComponent { + readonly counterId = input.required(); // Polling pollingOptions = pollingOptions; pollingInterval = signal(this.pollingOptions[0].value); - options = computed(() => ({ pollingInterval: this.pollingInterval() }) as LazyQueryOptions); // Queries - countQuery = useLazyGetCountByIdQuery(this.options); + countQuery = useGetCountByIdQuery(this.counterId, () => ({ pollingInterval: this.pollingInterval() })); increment = useIncrementCountByIdMutation(); decrement = useDecrementCountByIdMutation(); - - constructor() {} - - ngOnInit(): void { - this.countQuery.fetch(this.id); - } - - changePollingInterval(interval: number): void { - this.pollingInterval.set(+interval); - } - - incrementCounter(): void { - this.increment.dispatch({ id: this.id, amount: 1 }); - } - - decrementCounter(): void { - this.decrement.dispatch({ id: this.id, amount: 1 }); - } } diff --git a/src/app/features/lazy/counter-row/counter-row.component.ts b/src/app/features/lazy/counter-row/counter-row.component.ts index 5a708c8..925dd7e 100644 --- a/src/app/features/lazy/counter-row/counter-row.component.ts +++ b/src/app/features/lazy/counter-row/counter-row.component.ts @@ -8,18 +8,16 @@ import { useDecrementCountByIdMutation, useIncrementCountByIdMutation } from '@a
- {{ - counterData?.data?.count || 0 - }} + {{ data?.count || 0 }} @@ -29,23 +27,11 @@ import { useDecrementCountByIdMutation, useIncrementCountByIdMutation } from '@a changeDetection: ChangeDetectionStrategy.OnPush, }) export class CounterRowComponent { - @Input() counterData?: { - originalArgs?: string; - isFetching: boolean; - isUninitialized: boolean; - data?: { count: number }; - }; + @Input() originalArgs?: string; + @Input() data?: { count: number }; + @Input() isFetching: boolean = false; + @Input() isUninitialized: boolean = true; increment = useIncrementCountByIdMutation(); decrement = useDecrementCountByIdMutation(); - - constructor() {} - - incrementCounter(): void { - this.increment.dispatch({ id: this.counterData?.originalArgs ?? '', amount: 1 }); - } - - decrementCounter(): void { - this.decrement.dispatch({ id: this.counterData?.originalArgs ?? '', amount: 1 }); - } } diff --git a/src/app/features/lazy/lazy-counter/lazy.component.ts b/src/app/features/lazy/lazy-counter/lazy.component.ts index dbe5368..37efc5f 100644 --- a/src/app/features/lazy/lazy-counter/lazy.component.ts +++ b/src/app/features/lazy/lazy-counter/lazy.component.ts @@ -13,12 +13,8 @@ const { getCountById } = counterApiEndpoints;

Start Lazy Counter

-
@@ -37,13 +38,32 @@ const { getCountById } = counterApiEndpoints;

Duplicate state (Share state, subscription & selectFromResult)

Use in same component (not subscripted by self)

- +

Select from state (Share state & subscription, another selectFromResult)

Use in same component or child components (not subscripted by self)

- + +
+
+

Select from state parent signal

+
@@ -51,7 +71,12 @@ const { getCountById } = counterApiEndpoints; Related Query (Share cache data / another subscription & selectFromResult or Options)

Use anywhere (subscripted by self), skip subscribe with uninitialized value

- +
`, @@ -73,8 +98,7 @@ export class LazyComponent { constructor(private formBuilder: UntypedFormBuilder) {} async startCounterById({ id, preferCacheValue }: { id: string; preferCacheValue: boolean }): Promise { - this.countLazyQuery - .fetch(id, { preferCacheValue }) + this.countLazyQuery(id, { preferCacheValue }) .unwrap() .then((result) => { console.log('result method 1', result); @@ -83,7 +107,7 @@ export class LazyComponent { .catch(console.error); try { - const result = await this.countLazyQuery.fetch(id, { preferCacheValue }).unwrap(); + const result = await this.countLazyQuery(id, { preferCacheValue }).unwrap(); console.log('result method 2', result); this.form.reset(); } catch (error) { diff --git a/src/app/features/pagination/character-card/character-card.component.ts b/src/app/features/pagination/character-card/character-card.component.ts index 06e6b18..54a8eb6 100644 --- a/src/app/features/pagination/character-card/character-card.component.ts +++ b/src/app/features/pagination/character-card/character-card.component.ts @@ -37,12 +37,12 @@ import { useLazyGetEpisodeQuery } from '../services';
First seen:
- {{ episodeQuery.state().data?.name }} + {{ episodeLazyQuery.data()?.name }}
@@ -54,12 +54,10 @@ import { useLazyGetEpisodeQuery } from '../services'; export class CharacterCardComponent implements OnInit { @Input() character!: Character; - episodeQuery = useLazyGetEpisodeQuery(); + episodeLazyQuery = useLazyGetEpisodeQuery(); statusTypes = CharacterStatus; - constructor() {} - ngOnInit(): void { - this.episodeQuery.fetch(+this.character.episode[0].split('episode/')[1], { preferCacheValue: true }); + this.episodeLazyQuery(+this.character.episode[0].split('episode/')[1], { preferCacheValue: true }); } } diff --git a/src/app/features/pagination/characters-list/characters-list.component.ts b/src/app/features/pagination/characters-list/characters-list.component.ts index f6d0651..c3c9253 100644 --- a/src/app/features/pagination/characters-list/characters-list.component.ts +++ b/src/app/features/pagination/characters-list/characters-list.component.ts @@ -12,21 +12,21 @@ import { useGetCharactersQuery } from '../services';
-
{{ charactersQuery().error | json }}
+
{{ charactersQuery.error() | json }}
-
+
-

{{ postQuery().data?.name }}

- Loading... +

{{ postQuery.data()?.name }}

+ Loading...
-
- -
-
{{ postQuery().data | json }}
+
{{ postQuery.data() | json }}
`, styles: [], @@ -74,15 +72,13 @@ export class PostDetailComponent { ) {} updatePost(): void { - this.updatePostMutation - .dispatch({ id: +this.route.snapshot.params.id, name: this.postFormControl.value }) + this.updatePostMutation({ id: +this.route.snapshot.params.id, name: this.postFormControl.value }) .unwrap() .then(() => this.toggleEdit()); } deletePost(): void { - this.deletePostMutation - .dispatch(+this.route.snapshot.params.id) + this.deletePostMutation(+this.route.snapshot.params.id) .unwrap() .then(() => this.router.navigate(['/posts'])) .catch(() => console.error('Error deleting Post')); diff --git a/src/app/features/posts/posts-list/posts-list.component.ts b/src/app/features/posts/posts-list/posts-list.component.ts index 1053c73..5708097 100644 --- a/src/app/features/posts/posts-list/posts-list.component.ts +++ b/src/app/features/posts/posts-list/posts-list.component.ts @@ -6,7 +6,7 @@ import { useGetPostsQuery } from '../services'; selector: 'app-posts-list', template: `
- +
  • {{ post.name }} @@ -28,8 +28,6 @@ import { useGetPostsQuery } from '../services'; export class PostsListComponent { postsQuery = useGetPostsQuery(); - constructor() {} - trackByFn(_index: number, post: Post): number { return post.id; } diff --git a/src/app/features/posts/posts-manager/posts-manager.component.ts b/src/app/features/posts/posts-manager/posts-manager.component.ts index 3463db6..cf7fafb 100644 --- a/src/app/features/posts/posts-manager/posts-manager.component.ts +++ b/src/app/features/posts/posts-manager/posts-manager.component.ts @@ -10,10 +10,10 @@ import { useAddPostMutation } from '../services';
  • @@ -34,8 +34,7 @@ export class PostsManagerComponent { constructor() {} addNewPost(): void { - this.addPost - .dispatch({ name: this.postNameFormControl.value }) + this.addPost({ name: this.postNameFormControl.value }) .unwrap() .then(() => this.postNameFormControl.setValue('')); }