Content-Length: 841193 | pFad | https://github.com/angular/angular/commit/22d3f0562cc6c21ebf7c29ff0e01bce40dcd50a0

EB feat(core): add hook for producer creation side effects (#60333) · angular/angular@22d3f05 · GitHub
Skip to content

Commit 22d3f05

Browse files
mturcopkozlowski-opensource
authored andcommitted
feat(core): add hook for producer creation side effects (#60333)
Adds a hook in the same style as `postSignalSetFn` for running side effects when a producer has been created. This hook will be passed the reactive node being created. PR Close #60333
1 parent 69b688c commit 22d3f05

File tree

9 files changed

+99
-11
lines changed

9 files changed

+99
-11
lines changed

goldens/public-api/core/primitives/signals/index.api.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@ export interface Reactive {
107107
// @public (undocumented)
108108
export const REACTIVE_NODE: ReactiveNode;
109109

110+
// @public (undocumented)
111+
export type ReactiveHookFn = (node: ReactiveNode) => void;
112+
110113
// @public
111114
export interface ReactiveNode {
112115
consumerAllowSignalWrites: boolean;
@@ -132,7 +135,10 @@ export interface ReactiveNode {
132135
}
133136

134137
// @public (undocumented)
135-
export function runPostSignalSetFn(): void;
138+
export function runPostProducerCreatedFn(node: ReactiveNode): void;
139+
140+
// @public (undocumented)
141+
export function runPostSignalSetFn<T>(node: SignalNode<T>): void;
136142

137143
// @public (undocumented)
138144
export function setActiveConsumer(consumer: ReactiveNode | null): ReactiveNode | null;
@@ -141,7 +147,10 @@ export function setActiveConsumer(consumer: ReactiveNode | null): ReactiveNode |
141147
export function setAlternateWeakRefImpl(impl: unknown): void;
142148

143149
// @public (undocumented)
144-
export function setPostSignalSetFn(fn: (() => void) | null): (() => void) | null;
150+
export function setPostProducerCreatedFn(fn: ReactiveHookFn | null): ReactiveHookFn | null;
151+
152+
// @public (undocumented)
153+
export function setPostSignalSetFn(fn: ReactiveHookFn | null): ReactiveHookFn | null;
145154

146155
// @public (undocumented)
147156
export function setThrowInvalidWriteToSignalError(fn: <T>(node: SignalNode<T>) => never): void;

packages/core/primitives/signals/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export {setThrowInvalidWriteToSignalError} from './src/errors';
2020
export {
2121
REACTIVE_NODE,
2222
Reactive,
23+
ReactiveHookFn,
2324
ReactiveNode,
2425
SIGNAL,
2526
consumerAfterComputation,
@@ -36,7 +37,9 @@ export {
3637
producerNotifyConsumers,
3738
producerUpdateValueVersion,
3839
producerUpdatesAllowed,
40+
runPostProducerCreatedFn,
3941
setActiveConsumer,
42+
setPostProducerCreatedFn,
4043
} from './src/graph';
4144
export {
4245
SIGNAL_NODE,

packages/core/primitives/signals/src/computed.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
ReactiveNode,
1717
setActiveConsumer,
1818
SIGNAL,
19+
runPostProducerCreatedFn,
1920
} from './graph';
2021

2122
/**
@@ -76,6 +77,7 @@ export function createComputed<T>(
7677
return node.value;
7778
};
7879
(computed as ComputedGetter<T>)[SIGNAL] = node;
80+
runPostProducerCreatedFn(node);
7981
return computed as unknown as ComputedGetter<T>;
8082
}
8183

packages/core/primitives/signals/src/graph.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@ type Version = number & {__brand: 'Version'};
2525
*/
2626
let epoch: Version = 1 as Version;
2727

28+
export type ReactiveHookFn = (node: ReactiveNode) => void;
29+
30+
/**
31+
* If set, called after a producer `ReactiveNode` is created.
32+
*/
33+
let postProducerCreatedFn: ReactiveHookFn | null = null;
34+
2835
/**
2936
* Symbol used to tell `Signal`s apart from other functions.
3037
*
@@ -527,3 +534,13 @@ function assertProducerNode(node: ReactiveNode): asserts node is ProducerNode {
527534
function isConsumerNode(node: ReactiveNode): node is ConsumerNode {
528535
return node.producerNode !== undefined;
529536
}
537+
538+
export function runPostProducerCreatedFn(node: ReactiveNode): void {
539+
postProducerCreatedFn?.(node);
540+
}
541+
542+
export function setPostProducerCreatedFn(fn: ReactiveHookFn | null): ReactiveHookFn | null {
543+
const prev = postProducerCreatedFn;
544+
postProducerCreatedFn = fn;
545+
return prev;
546+
}

packages/core/primitives/signals/src/linked_signal.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
producerUpdateValueVersion,
1717
REACTIVE_NODE,
1818
ReactiveNode,
19+
runPostProducerCreatedFn,
1920
SIGNAL,
2021
} from './graph';
2122
import {signalSetFn, signalUpdateFn} from './signal';
@@ -86,7 +87,7 @@ export function createLinkedSignal<S, D>(
8687

8788
const getter = linkedSignalGetter as LinkedSignalGetter<S, D>;
8889
getter[SIGNAL] = node;
89-
90+
runPostProducerCreatedFn(node);
9091
return getter;
9192
}
9293

packages/core/primitives/signals/src/signal.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import {
1515
producerUpdatesAllowed,
1616
REACTIVE_NODE,
1717
ReactiveNode,
18+
ReactiveHookFn,
19+
runPostProducerCreatedFn,
1820
SIGNAL,
1921
} from './graph';
2022

@@ -28,7 +30,7 @@ declare const ngDevMode: boolean | undefined;
2830
* This hook can be used to achieve various effects, such as running effects synchronously as part
2931
* of setting a signal.
3032
*/
31-
let postSignalSetFn: (() => void) | null = null;
33+
let postSignalSetFn: ReactiveHookFn | null = null;
3234

3335
export interface SignalNode<T> extends ReactiveNode {
3436
value: T;
@@ -57,10 +59,11 @@ export function createSignal<T>(initialValue: T, equal?: ValueEqualityFn<T>): Si
5759
return node.value;
5860
}) as SignalGetter<T>;
5961
(getter as any)[SIGNAL] = node;
62+
runPostProducerCreatedFn(node);
6063
return getter;
6164
}
6265

63-
export function setPostSignalSetFn(fn: (() => void) | null): (() => void) | null {
66+
export function setPostSignalSetFn(fn: ReactiveHookFn | null): ReactiveHookFn | null {
6467
const prev = postSignalSetFn;
6568
postSignalSetFn = fn;
6669
return prev;
@@ -90,8 +93,8 @@ export function signalUpdateFn<T>(node: SignalNode<T>, updater: (value: T) => T)
9093
signalSetFn(node, updater(node.value));
9194
}
9295

93-
export function runPostSignalSetFn(): void {
94-
postSignalSetFn?.();
96+
export function runPostSignalSetFn<T>(node: SignalNode<T>): void {
97+
postSignalSetFn?.(node);
9598
}
9699

97100
// Note: Using an IIFE here to ensure that the spread assignment is not considered
@@ -110,5 +113,5 @@ function signalValueChanged<T>(node: SignalNode<T>): void {
110113
node.version++;
111114
producerIncrementEpoch();
112115
producerNotifyConsumers(node);
113-
postSignalSetFn?.();
116+
postSignalSetFn?.(node);
114117
}

packages/core/test/signals/computed_spec.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,13 @@
77
*/
88

99
import {computed, signal} from '@angular/core';
10-
import {createWatch, ReactiveNode, SIGNAL, defaultEquals} from '@angular/core/primitives/signals';
10+
import {
11+
createWatch,
12+
ReactiveNode,
13+
SIGNAL,
14+
defaultEquals,
15+
setPostProducerCreatedFn,
16+
} from '@angular/core/primitives/signals';
1117

1218
describe('computed', () => {
1319
it('should create computed', () => {
@@ -317,4 +323,14 @@ describe('computed', () => {
317323
expect(derived()).toBe(2);
318324
});
319325
});
326+
327+
it('should call the post-producer-created fn when signal is called', () => {
328+
const producerKindsCreated: string[] = [];
329+
const prev = setPostProducerCreatedFn((node) => producerKindsCreated.push(node.kind));
330+
const count = signal(0);
331+
computed(() => count() % 2 === 0);
332+
333+
expect(producerKindsCreated).toEqual(['signal', 'computed']);
334+
setPostProducerCreatedFn(prev);
335+
});
320336
});

packages/core/test/signals/linked_signal_spec.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import {isSignal, linkedSignal, signal, computed} from '@angular/core';
10+
import {setPostProducerCreatedFn} from '@angular/core/primitives/signals';
1011
import {testingEffect} from './effect_util';
1112

1213
describe('linkedSignal', () => {
@@ -275,4 +276,14 @@ describe('linkedSignal', () => {
275276
choice.set('explicit');
276277
expect(choice()).toBe('explicit');
277278
});
279+
280+
it('should call the post-producer-created fn when signal is called', () => {
281+
let producers = 0;
282+
const prev = setPostProducerCreatedFn(() => producers++);
283+
const options = signal(['apple', 'banana', 'fig']);
284+
linkedSignal(() => options()[0]);
285+
286+
expect(producers).toBe(2);
287+
setPostProducerCreatedFn(prev);
288+
});
278289
});

packages/core/test/signals/signal_spec.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,13 @@
77
*/
88

99
import {computed, signal} from '@angular/core';
10-
import {ReactiveNode, setPostSignalSetFn, SIGNAL} from '@angular/core/primitives/signals';
10+
import {
11+
ReactiveHookFn,
12+
ReactiveNode,
13+
setPostProducerCreatedFn,
14+
setPostSignalSetFn,
15+
SIGNAL,
16+
} from '@angular/core/primitives/signals';
1117

1218
describe('signals', () => {
1319
it('should be a getter which reflects the set value', () => {
@@ -166,7 +172,7 @@ describe('signals', () => {
166172
});
167173

168174
describe('post-signal-set functions', () => {
169-
let prevPostSignalSetFn: (() => void) | null = null;
175+
let prevPostSignalSetFn: ReactiveHookFn | null = null;
170176
let log: number;
171177
beforeEach(() => {
172178
log = 0;
@@ -197,5 +203,25 @@ describe('signals', () => {
197203
counter.set(0);
198204
expect(log).toBe(0);
199205
});
206+
207+
it('should pass post-signal-set fn the node that was updated', () => {
208+
const counter = signal(0, {debugName: 'test-signal'});
209+
let node: ReactiveNode | null = null;
210+
setPostSignalSetFn((n: ReactiveNode) => {
211+
node = n;
212+
});
213+
214+
counter.set(1);
215+
expect(node!.debugName).toBe('test-signal');
216+
});
217+
});
218+
219+
it('should call the post-producer-created fn when signal is called', () => {
220+
const producerKindsCreated: string[] = [];
221+
const prev = setPostProducerCreatedFn((node) => producerKindsCreated.push(node.kind));
222+
signal(0);
223+
224+
expect(producerKindsCreated).toEqual(['signal']);
225+
setPostProducerCreatedFn(prev);
200226
});
201227
});

0 commit comments

Comments
 (0)








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: https://github.com/angular/angular/commit/22d3f0562cc6c21ebf7c29ff0e01bce40dcd50a0

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy