diff --git a/modules/signals/spec/helpers.ts b/modules/signals/spec/helpers.ts index a5ef18f781..0d1e734212 100644 --- a/modules/signals/spec/helpers.ts +++ b/modules/signals/spec/helpers.ts @@ -2,7 +2,8 @@ import { Component, inject, Type } from '@angular/core'; import { TestBed } from '@angular/core/testing'; export function createLocalService>( - serviceToken: Service + serviceToken: Service, + provideLocally = true ): { service: InstanceType; flushEffects: () => void; @@ -11,7 +12,7 @@ export function createLocalService>( @Component({ standalone: true, template: '', - providers: [serviceToken], + providers: provideLocally ? [serviceToken] : [], }) class TestComponent { service = inject(serviceToken); diff --git a/modules/signals/spec/signal-store.spec.ts b/modules/signals/spec/signal-store.spec.ts index 39a6e79db8..17094781ff 100644 --- a/modules/signals/spec/signal-store.spec.ts +++ b/modules/signals/spec/signal-store.spec.ts @@ -1,5 +1,11 @@ -import { inject, InjectionToken, isSignal, signal } from '@angular/core'; -import { TestBed } from '@angular/core/testing'; +import { + inject, + Injectable, + InjectionToken, + isSignal, + signal, +} from '@angular/core'; +import { TestBed, waitForAsync } from '@angular/core/testing'; import { patchState, signalStore, @@ -275,15 +281,16 @@ describe('signalStore', () => { factory: () => 'ngrx', }); const Store = signalStore( - withHooks({ - onInit() { - inject(TOKEN); - messages.push('onInit'); - }, - onDestroy() { - inject(TOKEN); - messages.push('onDestroy'); - }, + withHooks(() => { + const token = inject(TOKEN); + return { + onInit() { + messages.push('onInit'); + }, + onDestroy() { + messages.push('onDestroy'); + }, + }; }) ); const { destroy } = createLocalService(Store); @@ -293,6 +300,29 @@ describe('signalStore', () => { destroy(); expect(messages).toEqual(['onInit', 'onDestroy']); }); + + it('runs a destroy hook in a root-scoped store during instantiation', waitForAsync(() => { + const TOKEN = new InjectionToken('TOKEN', { + providedIn: 'root', + factory: () => 'ngrx', + }); + let value = ''; + const Store = signalStore( + { providedIn: 'root' }, + withHooks(() => { + const token = inject(TOKEN); + return { + onDestroy: () => { + value = token; + }, + }; + }) + ); + + createLocalService(Store, false); + TestBed.resetTestEnvironment(); + expect(value).toBe('ngrx'); + })); }); describe('composition', () => { diff --git a/modules/signals/src/signal-store.ts b/modules/signals/src/signal-store.ts index 3125d3fc24..886f7ed9d7 100644 --- a/modules/signals/src/signal-store.ts +++ b/modules/signals/src/signal-store.ts @@ -331,10 +331,9 @@ export function signalStore( if (hooks.onDestroy) { const injector = inject(Injector); + const { onDestroy } = hooks; - inject(DestroyRef).onDestroy(() => { - runInInjectionContext(injector, hooks.onDestroy!); - }); + inject(DestroyRef).onDestroy(onDestroy); } } } diff --git a/modules/signals/src/with-hooks.ts b/modules/signals/src/with-hooks.ts index a927e0a2e7..ad38bcafb1 100644 --- a/modules/signals/src/with-hooks.ts +++ b/modules/signals/src/with-hooks.ts @@ -16,13 +16,24 @@ type HooksFactory = ( > ) => void; -export function withHooks(hooks: { +type HooksSupplier = () => { onInit?: HooksFactory; onDestroy?: HooksFactory; -}): SignalStoreFeature { +}; + +export function withHooks( + hooks: + | { + onInit?: HooksFactory; + onDestroy?: HooksFactory; + } + | HooksSupplier +): SignalStoreFeature { return (store) => { - const createHook = (name: keyof typeof hooks) => { - const hook = hooks[name]; + const _hooks = typeof hooks === 'function' ? hooks() : hooks; + + const createHook = (name: keyof typeof _hooks) => { + const hook = _hooks[name]; const currentHook = store.hooks[name]; return hook