From 269bd32bb7a924b3836dbcff82ca9ab1314bf414 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Stanimirovi=C4=87?= Date: Tue, 1 Oct 2024 19:52:36 +0200 Subject: [PATCH] feat(signals): add deepComputed function (#4539) --- modules/signals/spec/deep-computed.spec.ts | 32 +++++++++++++++++++ modules/signals/src/deep-computed.ts | 8 +++++ modules/signals/src/index.ts | 1 + .../content/guide/signals/deep-computed.md | 30 +++++++++++++++++ projects/ngrx.io/content/navigation.json | 4 +++ 5 files changed, 75 insertions(+) create mode 100644 modules/signals/spec/deep-computed.spec.ts create mode 100644 modules/signals/src/deep-computed.ts create mode 100644 projects/ngrx.io/content/guide/signals/deep-computed.md diff --git a/modules/signals/spec/deep-computed.spec.ts b/modules/signals/spec/deep-computed.spec.ts new file mode 100644 index 0000000000..3f65e31428 --- /dev/null +++ b/modules/signals/spec/deep-computed.spec.ts @@ -0,0 +1,32 @@ +import { isSignal, signal } from '@angular/core'; +import { deepComputed } from '../src'; + +describe('deepComputed', () => { + it('creates a deep computed signal when computation result is an object literal', () => { + const source = signal(0); + const result = deepComputed(() => ({ count: { value: source() + 1 } })); + + expect(isSignal(result)).toBe(true); + expect(isSignal(result.count)).toBe(true); + expect(isSignal(result.count.value)).toBe(true); + + expect(result()).toEqual({ count: { value: 1 } }); + expect(result.count()).toEqual({ value: 1 }); + expect(result.count.value()).toBe(1); + + source.set(1); + + expect(result()).toEqual({ count: { value: 2 } }); + expect(result.count()).toEqual({ value: 2 }); + expect(result.count.value()).toBe(2); + }); + + it('does not create a deep computed signal when computation result is an array', () => { + const source = signal(0); + const result = deepComputed(() => [{ value: source() + 1 }]); + + expect(isSignal(result)).toBe(true); + expect(result()).toEqual([{ value: 1 }]); + expect((result as any)[0]).toBe(undefined); + }); +}); diff --git a/modules/signals/src/deep-computed.ts b/modules/signals/src/deep-computed.ts new file mode 100644 index 0000000000..66eb7a0169 --- /dev/null +++ b/modules/signals/src/deep-computed.ts @@ -0,0 +1,8 @@ +import { computed } from '@angular/core'; +import { DeepSignal, toDeepSignal } from './deep-signal'; + +export function deepComputed( + computation: () => T +): DeepSignal { + return toDeepSignal(computed(computation)); +} diff --git a/modules/signals/src/index.ts b/modules/signals/src/index.ts index 1e9a2294d2..e7332786e5 100644 --- a/modules/signals/src/index.ts +++ b/modules/signals/src/index.ts @@ -1,3 +1,4 @@ +export { deepComputed } from './deep-computed'; export { DeepSignal } from './deep-signal'; export { signalState, SignalState } from './signal-state'; export { signalStore } from './signal-store'; diff --git a/projects/ngrx.io/content/guide/signals/deep-computed.md b/projects/ngrx.io/content/guide/signals/deep-computed.md new file mode 100644 index 0000000000..ea9f5e2a25 --- /dev/null +++ b/projects/ngrx.io/content/guide/signals/deep-computed.md @@ -0,0 +1,30 @@ +# DeepComputed + +The `deepComputed` function creates a `DeepSignal` when a computation result is an object literal. +It can be used as a regular computed signal, but it also contains computed signals for each nested property. + +```ts +import { signal } from '@angular/core'; +import { deepComputed } from '@ngrx/signals'; + +const limit = signal(25); +const offset = signal(0); +const totalItems = signal(100); + +const pagination = deepComputed(() => ({ + currentPage: Math.floor(offset() / limit()) + 1, + pageSize: limit(), + totalPages: Math.ceil(totalItems() / limit()), +})); + +console.log(pagination()); // logs: { currentPage: 1, pageSize: 25, totalPages: 4 } +console.log(pagination.currentPage()); // logs: 1 +console.log(pagination.pageSize()); // logs: 25 +console.log(pagination.totalPages()); // logs: 4 +``` + +
+ +For enhanced performance, deeply nested signals are generated lazily and initialized only upon first access. + +
diff --git a/projects/ngrx.io/content/navigation.json b/projects/ngrx.io/content/navigation.json index fb536be4f8..8e51c01507 100644 --- a/projects/ngrx.io/content/navigation.json +++ b/projects/ngrx.io/content/navigation.json @@ -319,6 +319,10 @@ "title": "SignalState", "url": "guide/signals/signal-state" }, + { + "title": "DeepComputed", + "url": "guide/signals/deep-computed" + }, { "title": "RxJS Integration", "url": "guide/signals/rxjs-integration"