mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
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
This commit is contained in:
committed by
Pawel Kozlowski
parent
69b688c7d1
commit
22d3f0562c
@@ -20,6 +20,7 @@ export {setThrowInvalidWriteToSignalError} from './src/errors';
|
||||
export {
|
||||
REACTIVE_NODE,
|
||||
Reactive,
|
||||
ReactiveHookFn,
|
||||
ReactiveNode,
|
||||
SIGNAL,
|
||||
consumerAfterComputation,
|
||||
@@ -36,7 +37,9 @@ export {
|
||||
producerNotifyConsumers,
|
||||
producerUpdateValueVersion,
|
||||
producerUpdatesAllowed,
|
||||
runPostProducerCreatedFn,
|
||||
setActiveConsumer,
|
||||
setPostProducerCreatedFn,
|
||||
} from './src/graph';
|
||||
export {
|
||||
SIGNAL_NODE,
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
ReactiveNode,
|
||||
setActiveConsumer,
|
||||
SIGNAL,
|
||||
runPostProducerCreatedFn,
|
||||
} from './graph';
|
||||
|
||||
/**
|
||||
@@ -76,6 +77,7 @@ export function createComputed<T>(
|
||||
return node.value;
|
||||
};
|
||||
(computed as ComputedGetter<T>)[SIGNAL] = node;
|
||||
runPostProducerCreatedFn(node);
|
||||
return computed as unknown as ComputedGetter<T>;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,13 @@ type Version = number & {__brand: 'Version'};
|
||||
*/
|
||||
let epoch: Version = 1 as Version;
|
||||
|
||||
export type ReactiveHookFn = (node: ReactiveNode) => void;
|
||||
|
||||
/**
|
||||
* If set, called after a producer `ReactiveNode` is created.
|
||||
*/
|
||||
let postProducerCreatedFn: ReactiveHookFn | null = null;
|
||||
|
||||
/**
|
||||
* Symbol used to tell `Signal`s apart from other functions.
|
||||
*
|
||||
@@ -527,3 +534,13 @@ function assertProducerNode(node: ReactiveNode): asserts node is ProducerNode {
|
||||
function isConsumerNode(node: ReactiveNode): node is ConsumerNode {
|
||||
return node.producerNode !== undefined;
|
||||
}
|
||||
|
||||
export function runPostProducerCreatedFn(node: ReactiveNode): void {
|
||||
postProducerCreatedFn?.(node);
|
||||
}
|
||||
|
||||
export function setPostProducerCreatedFn(fn: ReactiveHookFn | null): ReactiveHookFn | null {
|
||||
const prev = postProducerCreatedFn;
|
||||
postProducerCreatedFn = fn;
|
||||
return prev;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
producerUpdateValueVersion,
|
||||
REACTIVE_NODE,
|
||||
ReactiveNode,
|
||||
runPostProducerCreatedFn,
|
||||
SIGNAL,
|
||||
} from './graph';
|
||||
import {signalSetFn, signalUpdateFn} from './signal';
|
||||
@@ -86,7 +87,7 @@ export function createLinkedSignal<S, D>(
|
||||
|
||||
const getter = linkedSignalGetter as LinkedSignalGetter<S, D>;
|
||||
getter[SIGNAL] = node;
|
||||
|
||||
runPostProducerCreatedFn(node);
|
||||
return getter;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@ import {
|
||||
producerUpdatesAllowed,
|
||||
REACTIVE_NODE,
|
||||
ReactiveNode,
|
||||
ReactiveHookFn,
|
||||
runPostProducerCreatedFn,
|
||||
SIGNAL,
|
||||
} from './graph';
|
||||
|
||||
@@ -28,7 +30,7 @@ declare const ngDevMode: boolean | undefined;
|
||||
* This hook can be used to achieve various effects, such as running effects synchronously as part
|
||||
* of setting a signal.
|
||||
*/
|
||||
let postSignalSetFn: (() => void) | null = null;
|
||||
let postSignalSetFn: ReactiveHookFn | null = null;
|
||||
|
||||
export interface SignalNode<T> extends ReactiveNode {
|
||||
value: T;
|
||||
@@ -57,10 +59,11 @@ export function createSignal<T>(initialValue: T, equal?: ValueEqualityFn<T>): Si
|
||||
return node.value;
|
||||
}) as SignalGetter<T>;
|
||||
(getter as any)[SIGNAL] = node;
|
||||
runPostProducerCreatedFn(node);
|
||||
return getter;
|
||||
}
|
||||
|
||||
export function setPostSignalSetFn(fn: (() => void) | null): (() => void) | null {
|
||||
export function setPostSignalSetFn(fn: ReactiveHookFn | null): ReactiveHookFn | null {
|
||||
const prev = postSignalSetFn;
|
||||
postSignalSetFn = fn;
|
||||
return prev;
|
||||
@@ -90,8 +93,8 @@ export function signalUpdateFn<T>(node: SignalNode<T>, updater: (value: T) => T)
|
||||
signalSetFn(node, updater(node.value));
|
||||
}
|
||||
|
||||
export function runPostSignalSetFn(): void {
|
||||
postSignalSetFn?.();
|
||||
export function runPostSignalSetFn<T>(node: SignalNode<T>): void {
|
||||
postSignalSetFn?.(node);
|
||||
}
|
||||
|
||||
// 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 {
|
||||
node.version++;
|
||||
producerIncrementEpoch();
|
||||
producerNotifyConsumers(node);
|
||||
postSignalSetFn?.();
|
||||
postSignalSetFn?.(node);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,13 @@
|
||||
*/
|
||||
|
||||
import {computed, signal} from '@angular/core';
|
||||
import {createWatch, ReactiveNode, SIGNAL, defaultEquals} from '@angular/core/primitives/signals';
|
||||
import {
|
||||
createWatch,
|
||||
ReactiveNode,
|
||||
SIGNAL,
|
||||
defaultEquals,
|
||||
setPostProducerCreatedFn,
|
||||
} from '@angular/core/primitives/signals';
|
||||
|
||||
describe('computed', () => {
|
||||
it('should create computed', () => {
|
||||
@@ -317,4 +323,14 @@ describe('computed', () => {
|
||||
expect(derived()).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
it('should call the post-producer-created fn when signal is called', () => {
|
||||
const producerKindsCreated: string[] = [];
|
||||
const prev = setPostProducerCreatedFn((node) => producerKindsCreated.push(node.kind));
|
||||
const count = signal(0);
|
||||
computed(() => count() % 2 === 0);
|
||||
|
||||
expect(producerKindsCreated).toEqual(['signal', 'computed']);
|
||||
setPostProducerCreatedFn(prev);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import {isSignal, linkedSignal, signal, computed} from '@angular/core';
|
||||
import {setPostProducerCreatedFn} from '@angular/core/primitives/signals';
|
||||
import {testingEffect} from './effect_util';
|
||||
|
||||
describe('linkedSignal', () => {
|
||||
@@ -275,4 +276,14 @@ describe('linkedSignal', () => {
|
||||
choice.set('explicit');
|
||||
expect(choice()).toBe('explicit');
|
||||
});
|
||||
|
||||
it('should call the post-producer-created fn when signal is called', () => {
|
||||
let producers = 0;
|
||||
const prev = setPostProducerCreatedFn(() => producers++);
|
||||
const options = signal(['apple', 'banana', 'fig']);
|
||||
linkedSignal(() => options()[0]);
|
||||
|
||||
expect(producers).toBe(2);
|
||||
setPostProducerCreatedFn(prev);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,7 +7,13 @@
|
||||
*/
|
||||
|
||||
import {computed, signal} from '@angular/core';
|
||||
import {ReactiveNode, setPostSignalSetFn, SIGNAL} from '@angular/core/primitives/signals';
|
||||
import {
|
||||
ReactiveHookFn,
|
||||
ReactiveNode,
|
||||
setPostProducerCreatedFn,
|
||||
setPostSignalSetFn,
|
||||
SIGNAL,
|
||||
} from '@angular/core/primitives/signals';
|
||||
|
||||
describe('signals', () => {
|
||||
it('should be a getter which reflects the set value', () => {
|
||||
@@ -166,7 +172,7 @@ describe('signals', () => {
|
||||
});
|
||||
|
||||
describe('post-signal-set functions', () => {
|
||||
let prevPostSignalSetFn: (() => void) | null = null;
|
||||
let prevPostSignalSetFn: ReactiveHookFn | null = null;
|
||||
let log: number;
|
||||
beforeEach(() => {
|
||||
log = 0;
|
||||
@@ -197,5 +203,25 @@ describe('signals', () => {
|
||||
counter.set(0);
|
||||
expect(log).toBe(0);
|
||||
});
|
||||
|
||||
it('should pass post-signal-set fn the node that was updated', () => {
|
||||
const counter = signal(0, {debugName: 'test-signal'});
|
||||
let node: ReactiveNode | null = null;
|
||||
setPostSignalSetFn((n: ReactiveNode) => {
|
||||
node = n;
|
||||
});
|
||||
|
||||
counter.set(1);
|
||||
expect(node!.debugName).toBe('test-signal');
|
||||
});
|
||||
});
|
||||
|
||||
it('should call the post-producer-created fn when signal is called', () => {
|
||||
const producerKindsCreated: string[] = [];
|
||||
const prev = setPostProducerCreatedFn((node) => producerKindsCreated.push(node.kind));
|
||||
signal(0);
|
||||
|
||||
expect(producerKindsCreated).toEqual(['signal']);
|
||||
setPostProducerCreatedFn(prev);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user