diff --git a/docs/src/content/docs/utilities/Signals/explicit-effect.md b/docs/src/content/docs/utilities/Signals/explicit-effect.md index 5f77d995..5b7251a1 100644 --- a/docs/src/content/docs/utilities/Signals/explicit-effect.md +++ b/docs/src/content/docs/utilities/Signals/explicit-effect.md @@ -65,17 +65,29 @@ explicitEffect( ## Cleanup -An optional second argument can be provided to `explicitEffect` that will be called when the effect is cleaned up. +An optional second argument can be provided to the `explicitEffect` function that will be called when the effect is cleaned up. ```ts const count = signal(0); -explicitEffect([this.count], ([count], cleanup) => { +explicitEffect([count], ([count], cleanup) => { console.log(`count updated ${count}`); cleanup(() => console.log('cleanup')); }); +``` + +## Defer + +In addition to the regular `effect` options, you can also pass a `defer` value as part of the third object argument of `explicitEffect`. This allows the computation not to execute immediately and only run on first deps change. + +```ts +const count = signal(0); +explicitEffect( + [count], + ([count]) => { + console.log(`count updated ${count}`); + }, + { defer: true }, +); -// count updated 0 -// count.set(1); -// cleanup -// count updated 1 +count.set(1); // this set will trigger the effect function call, not the initial value ``` diff --git a/libs/ngxtension/explicit-effect/src/explicit-effect.spec.ts b/libs/ngxtension/explicit-effect/src/explicit-effect.spec.ts index b7426f24..4ace6086 100644 --- a/libs/ngxtension/explicit-effect/src/explicit-effect.spec.ts +++ b/libs/ngxtension/explicit-effect/src/explicit-effect.spec.ts @@ -106,4 +106,27 @@ describe(explicitEffect.name, () => { }); }); }); + + it('should skip the first run of the effect callback', () => { + const log: string[] = []; + const count = signal(0); + const state = signal('idle'); + + TestBed.runInInjectionContext(() => { + explicitEffect( + [count, state], + ([count, state]) => { + log.push(`count updated ${count}, ${state}`); + }, + { defer: true }, + ); + expect(log.length).toBe(0); + TestBed.flushEffects(); + expect(log.length).toBe(0); + + count.set(1); + TestBed.flushEffects(); + expect(log.length).toBe(1); + }); + }); }); diff --git a/libs/ngxtension/explicit-effect/src/explicit-effect.ts b/libs/ngxtension/explicit-effect/src/explicit-effect.ts index 16e8ea8e..838ae932 100644 --- a/libs/ngxtension/explicit-effect/src/explicit-effect.ts +++ b/libs/ngxtension/explicit-effect/src/explicit-effect.ts @@ -13,6 +13,16 @@ type ExplicitEffectValues = { [K in keyof T]: () => T[K]; }; +/** + * Extend the regular set of effect options + */ +declare interface CreateExplicitEffectOptions extends CreateEffectOptions { + /** + * Option that allows the computation not to execute immediately, but only run on first change. + */ + defer?: boolean; +} + /** * This explicit effect function will take the dependencies and the function to run when the dependencies change. * @@ -34,7 +44,7 @@ type ExplicitEffectValues = { * * @param deps - The dependencies that the effect will run on * @param fn - The function to run when the dependencies change - * @param options - The options for the effect + * @param options - The options for the effect with the addition of defer (it allows the computation to run on first change, not immediately) */ export function explicitEffect< Input extends readonly unknown[], @@ -42,10 +52,16 @@ export function explicitEffect< >( deps: readonly [...ExplicitEffectValues], fn: (deps: Params, onCleanup: EffectCleanupRegisterFn) => void, - options?: CreateEffectOptions | undefined, + options?: CreateExplicitEffectOptions | undefined, ): EffectRef { + let defer = options && options.defer; return effect((onCleanup) => { const depValues = deps.map((s) => s()); - untracked(() => fn(depValues as any, onCleanup)); + untracked(() => { + if (!defer) { + fn(depValues as any, onCleanup); + } + defer = false; + }); }, options); }