diff --git a/modules/signals/spec/signal-store.spec.ts b/modules/signals/spec/signal-store.spec.ts
index ce2a618cb6..2b73050fde 100644
--- a/modules/signals/spec/signal-store.spec.ts
+++ b/modules/signals/spec/signal-store.spec.ts
@@ -268,32 +268,58 @@ describe('signalStore', () => {
expect(message).toBe('onDestroy');
});
- // FIX: injection context will be provided for `onDestroy` in a separate PR
- // see https://github.com/ngrx/platform/pull/4196#issuecomment-1875228588
- it('executes hooks in injection context', () => {
+ it('executes hooks factory in injection context', () => {
const messages: string[] = [];
- const TOKEN = new InjectionToken('TOKEN', {
+ const TOKEN_INIT = new InjectionToken('TOKEN_INIT', {
+ providedIn: 'root',
+ factory: () => 'init',
+ });
+ const TOKEN_DESTROY = new InjectionToken('TOKEN_DESTROY', {
providedIn: 'root',
- factory: () => 'ngrx',
+ factory: () => 'destroy',
});
const Store = signalStore(
+ withState({ name: 'NgRx Store' }),
+ withHooks((store) => {
+ const tokenInit = inject(TOKEN_INIT);
+ const tokenDestroy = inject(TOKEN_DESTROY);
+ return {
+ onInit() {
+ messages.push(`${tokenInit} ${store.name()}`);
+ },
+ onDestroy() {
+ messages.push(`${tokenDestroy} ${store.name()}`);
+ },
+ };
+ })
+ );
+ const { destroy } = createLocalService(Store);
+
+ expect(messages).toEqual(['init NgRx Store']);
+
+ destroy();
+ expect(messages).toEqual(['init NgRx Store', 'destroy NgRx Store']);
+ });
+
+ it('executes hooks without injection context', () => {
+ const messages: string[] = [];
+ const Store = signalStore(
+ withState({ name: 'NgRx Store' }),
withHooks({
- onInit() {
- inject(TOKEN);
- messages.push('onInit');
+ onInit(store) {
+ messages.push(`init ${store.name()}`);
},
- onDestroy() {
- // inject(TOKEN);
- messages.push('onDestroy');
+ onDestroy(store) {
+ messages.push(`destroy ${store.name()}`);
},
})
);
const { destroy } = createLocalService(Store);
- expect(messages).toEqual(['onInit']);
+ expect(messages).toEqual(['init NgRx Store']);
destroy();
- expect(messages).toEqual(['onInit', 'onDestroy']);
+ expect(messages).toEqual(['init NgRx Store', 'destroy NgRx Store']);
});
it('succeeds with onDestroy and providedIn: root', () => {
diff --git a/modules/signals/src/with-hooks.ts b/modules/signals/src/with-hooks.ts
index a927e0a2e7..df8c65d5dc 100644
--- a/modules/signals/src/with-hooks.ts
+++ b/modules/signals/src/with-hooks.ts
@@ -7,7 +7,7 @@ import {
} from './signal-store-models';
import { Prettify } from './ts-helpers';
-type HooksFactory = (
+type HookFn = (
store: Prettify<
SignalStoreSlices &
Input['signals'] &
@@ -16,11 +16,45 @@ type HooksFactory = (
>
) => void;
+type HooksFactory = (
+ store: Prettify<
+ SignalStoreSlices &
+ Input['signals'] &
+ Input['methods'] &
+ StateSignal>
+ >
+) => {
+ onInit?: () => void;
+ onDestroy?: () => void;
+};
+
export function withHooks(hooks: {
- onInit?: HooksFactory;
- onDestroy?: HooksFactory;
-}): SignalStoreFeature {
+ onInit?: HookFn;
+ onDestroy?: HookFn;
+}): SignalStoreFeature;
+export function withHooks(
+ hooks: HooksFactory
+): SignalStoreFeature;
+
+export function withHooks(
+ hooksOrFactory:
+ | {
+ onInit?: HookFn;
+ onDestroy?: HookFn;
+ }
+ | HooksFactory
+): SignalStoreFeature {
return (store) => {
+ const storeProps = {
+ [STATE_SIGNAL]: store[STATE_SIGNAL],
+ ...store.slices,
+ ...store.signals,
+ ...store.methods,
+ };
+ const hooks =
+ typeof hooksOrFactory === 'function'
+ ? hooksOrFactory(storeProps)
+ : hooksOrFactory;
const createHook = (name: keyof typeof hooks) => {
const hook = hooks[name];
const currentHook = store.hooks[name];
@@ -31,12 +65,7 @@ export function withHooks(hooks: {
currentHook();
}
- hook({
- [STATE_SIGNAL]: store[STATE_SIGNAL],
- ...store.slices,
- ...store.signals,
- ...store.methods,
- });
+ hook(storeProps);
}
: currentHook;
};