Skip to content

Commit 269bd32

Browse files
feat(signals): add deepComputed function (#4539)
1 parent ffc1d87 commit 269bd32

File tree

5 files changed

+75
-0
lines changed

5 files changed

+75
-0
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { isSignal, signal } from '@angular/core';
2+
import { deepComputed } from '../src';
3+
4+
describe('deepComputed', () => {
5+
it('creates a deep computed signal when computation result is an object literal', () => {
6+
const source = signal(0);
7+
const result = deepComputed(() => ({ count: { value: source() + 1 } }));
8+
9+
expect(isSignal(result)).toBe(true);
10+
expect(isSignal(result.count)).toBe(true);
11+
expect(isSignal(result.count.value)).toBe(true);
12+
13+
expect(result()).toEqual({ count: { value: 1 } });
14+
expect(result.count()).toEqual({ value: 1 });
15+
expect(result.count.value()).toBe(1);
16+
17+
source.set(1);
18+
19+
expect(result()).toEqual({ count: { value: 2 } });
20+
expect(result.count()).toEqual({ value: 2 });
21+
expect(result.count.value()).toBe(2);
22+
});
23+
24+
it('does not create a deep computed signal when computation result is an array', () => {
25+
const source = signal(0);
26+
const result = deepComputed(() => [{ value: source() + 1 }]);
27+
28+
expect(isSignal(result)).toBe(true);
29+
expect(result()).toEqual([{ value: 1 }]);
30+
expect((result as any)[0]).toBe(undefined);
31+
});
32+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { computed } from '@angular/core';
2+
import { DeepSignal, toDeepSignal } from './deep-signal';
3+
4+
export function deepComputed<T extends object>(
5+
computation: () => T
6+
): DeepSignal<T> {
7+
return toDeepSignal(computed(computation));
8+
}

modules/signals/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export { deepComputed } from './deep-computed';
12
export { DeepSignal } from './deep-signal';
23
export { signalState, SignalState } from './signal-state';
34
export { signalStore } from './signal-store';
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# DeepComputed
2+
3+
The `deepComputed` function creates a `DeepSignal` when a computation result is an object literal.
4+
It can be used as a regular computed signal, but it also contains computed signals for each nested property.
5+
6+
```ts
7+
import { signal } from '@angular/core';
8+
import { deepComputed } from '@ngrx/signals';
9+
10+
const limit = signal(25);
11+
const offset = signal(0);
12+
const totalItems = signal(100);
13+
14+
const pagination = deepComputed(() => ({
15+
currentPage: Math.floor(offset() / limit()) + 1,
16+
pageSize: limit(),
17+
totalPages: Math.ceil(totalItems() / limit()),
18+
}));
19+
20+
console.log(pagination()); // logs: { currentPage: 1, pageSize: 25, totalPages: 4 }
21+
console.log(pagination.currentPage()); // logs: 1
22+
console.log(pagination.pageSize()); // logs: 25
23+
console.log(pagination.totalPages()); // logs: 4
24+
```
25+
26+
<div class="alert is-helpful">
27+
28+
For enhanced performance, deeply nested signals are generated lazily and initialized only upon first access.
29+
30+
</div>

projects/ngrx.io/content/navigation.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,10 @@
319319
"title": "SignalState",
320320
"url": "guide/signals/signal-state"
321321
},
322+
{
323+
"title": "DeepComputed",
324+
"url": "guide/signals/deep-computed"
325+
},
322326
{
323327
"title": "RxJS Integration",
324328
"url": "guide/signals/rxjs-integration"

0 commit comments

Comments
 (0)