1
+ /**
2
+ * @license
3
+ * Copyright Google LLC All Rights Reserved.
4
+ *
5
+ * Use of this source code is governed by an MIT-style license that can be
6
+ * found in the LICENSE file at https://angular.io/license
7
+ */
8
+
9
+ import { defaultEquals , ValueEqualityFn } from './equality.js' ;
10
+ import { consumerAfterComputation , consumerBeforeComputation , producerAccessed , producerUpdateValueVersion , REACTIVE_NODE , ReactiveNode , SIGNAL } from './graph.js' ;
11
+
12
+
13
+ /**
14
+ * A computation, which derives a value from a declarative reactive expression.
15
+ *
16
+ * `Computed`s are both producers and consumers of reactivity.
17
+ */
18
+ export interface ComputedNode < T > extends ReactiveNode {
19
+ /**
20
+ * Current value of the computation, or one of the sentinel values above (`UNSET`, `COMPUTING`,
21
+ * `ERROR`).
22
+ */
23
+ value : T ;
24
+
25
+ /**
26
+ * If `value` is `ERRORED`, the error caught from the last computation attempt which will
27
+ * be re-thrown.
28
+ */
29
+ error : unknown ;
30
+
31
+ /**
32
+ * The computation function which will produce a new value.
33
+ */
34
+ computation : ( ) => T ;
35
+
36
+ equal : ValueEqualityFn < T > ;
37
+ }
38
+
39
+ export type ComputedGetter < T > = ( ( ) => T ) & {
40
+ [ SIGNAL ] : ComputedNode < T > ;
41
+ } ;
42
+
43
+ export function computedGet < T > ( node : ComputedNode < T > ) {
44
+ // Check if the value needs updating before returning it.
45
+ producerUpdateValueVersion ( node ) ;
46
+
47
+ // Record that someone looked at this signal.
48
+ producerAccessed ( node ) ;
49
+
50
+ if ( node . value === ERRORED ) {
51
+ throw node . error ;
52
+ }
53
+
54
+ return node . value ;
55
+ }
56
+
57
+ /**
58
+ * Create a computed signal which derives a reactive value from an expression.
59
+ */
60
+ export function createComputed < T > ( computation : ( ) => T ) : ComputedGetter < T > {
61
+ const node : ComputedNode < T > = Object . create ( COMPUTED_NODE ) ;
62
+ node . computation = computation ;
63
+
64
+ const computed = ( ) => computedGet ( node ) ;
65
+ ( computed as ComputedGetter < T > ) [ SIGNAL ] = node ;
66
+ return computed as unknown as ComputedGetter < T > ;
67
+ }
68
+
69
+ /**
70
+ * A dedicated symbol used before a computed value has been calculated for the first time.
71
+ * Explicitly typed as `any` so we can use it as signal's value.
72
+ */
73
+ const UNSET : any = /* @__PURE__ */ Symbol ( 'UNSET' ) ;
74
+
75
+ /**
76
+ * A dedicated symbol used in place of a computed signal value to indicate that a given computation
77
+ * is in progress. Used to detect cycles in computation chains.
78
+ * Explicitly typed as `any` so we can use it as signal's value.
79
+ */
80
+ const COMPUTING : any = /* @__PURE__ */ Symbol ( 'COMPUTING' ) ;
81
+
82
+ /**
83
+ * A dedicated symbol used in place of a computed signal value to indicate that a given computation
84
+ * failed. The thrown error is cached until the computation gets dirty again.
85
+ * Explicitly typed as `any` so we can use it as signal's value.
86
+ */
87
+ const ERRORED : any = /* @__PURE__ */ Symbol ( 'ERRORED' ) ;
88
+
89
+ // Note: Using an IIFE here to ensure that the spread assignment is not considered
90
+ // a side-effect, ending up preserving `COMPUTED_NODE` and `REACTIVE_NODE`.
91
+ // TODO: remove when https://github.com/evanw/esbuild/issues/3392 is resolved.
92
+ const COMPUTED_NODE = /* @__PURE__ */ ( ( ) => {
93
+ return {
94
+ ...REACTIVE_NODE ,
95
+ value : UNSET ,
96
+ dirty : true ,
97
+ error : null ,
98
+ equal : defaultEquals ,
99
+
100
+ producerMustRecompute ( node : ComputedNode < unknown > ) : boolean {
101
+ // Force a recomputation if there's no current value, or if the current value is in the
102
+ // process of being calculated (which should throw an error).
103
+ return node . value === UNSET || node . value === COMPUTING ;
104
+ } ,
105
+
106
+ producerRecomputeValue ( node : ComputedNode < unknown > ) : void {
107
+ if ( node . value === COMPUTING ) {
108
+ // Our computation somehow led to a cyclic read of itself.
109
+ throw new Error ( 'Detected cycle in computations.' ) ;
110
+ }
111
+
112
+ const oldValue = node . value ;
113
+ node . value = COMPUTING ;
114
+
115
+ const prevConsumer = consumerBeforeComputation ( node ) ;
116
+ let newValue : unknown ;
117
+ try {
118
+ newValue = node . computation . call ( node . wrapper ) ;
119
+ } catch ( err ) {
120
+ newValue = ERRORED ;
121
+ node . error = err ;
122
+ } finally {
123
+ consumerAfterComputation ( node , prevConsumer ) ;
124
+ }
125
+
126
+ if ( oldValue !== UNSET && oldValue !== ERRORED && newValue !== ERRORED &&
127
+ node . equal . call ( node . wrapper , oldValue , newValue ) ) {
128
+ // No change to `valueVersion` - old and new values are
129
+ // semantically equivalent.
130
+ node . value = oldValue ;
131
+ return ;
132
+ }
133
+
134
+ node . value = newValue ;
135
+ node . version ++ ;
136
+ } ,
137
+ } ;
138
+ } ) ( ) ;
0 commit comments