@angular/core 17.0.0-next.1 → 17.0.0-next.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm2022/rxjs-interop/src/to_signal.mjs +4 -6
- package/esm2022/src/core_private_export.mjs +2 -2
- package/esm2022/src/core_render3_private_export.mjs +5 -2
- package/esm2022/src/di/injection_token.mjs +12 -7
- package/esm2022/src/hydration/annotate.mjs +61 -30
- package/esm2022/src/hydration/cleanup.mjs +2 -2
- package/esm2022/src/linker/template_ref.mjs +3 -3
- package/esm2022/src/linker/view_container_ref.mjs +80 -25
- package/esm2022/src/render3/after_render_hooks.mjs +74 -43
- package/esm2022/src/render3/assert.mjs +2 -3
- package/esm2022/src/render3/collect_native_nodes.mjs +30 -23
- package/esm2022/src/render3/component.mjs +4 -3
- package/esm2022/src/render3/di.mjs +1 -1
- package/esm2022/src/render3/index.mjs +2 -2
- package/esm2022/src/render3/instructions/advance.mjs +3 -3
- package/esm2022/src/render3/instructions/all.mjs +2 -1
- package/esm2022/src/render3/instructions/change_detection.mjs +5 -6
- package/esm2022/src/render3/instructions/component_instance.mjs +23 -0
- package/esm2022/src/render3/instructions/control_flow.mjs +175 -3
- package/esm2022/src/render3/instructions/defer.mjs +409 -18
- package/esm2022/src/render3/instructions/shared.mjs +20 -14
- package/esm2022/src/render3/instructions/template.mjs +2 -1
- package/esm2022/src/render3/interfaces/defer.mjs +9 -0
- package/esm2022/src/render3/interfaces/definition.mjs +1 -1
- package/esm2022/src/render3/interfaces/injector.mjs +1 -1
- package/esm2022/src/render3/interfaces/node.mjs +16 -1
- package/esm2022/src/render3/interfaces/styling.mjs +4 -7
- package/esm2022/src/render3/interfaces/type_checks.mjs +4 -1
- package/esm2022/src/render3/interfaces/view.mjs +1 -1
- package/esm2022/src/render3/jit/environment.mjs +6 -1
- package/esm2022/src/render3/node_manipulation.mjs +4 -3
- package/esm2022/src/render3/reactive_lview_consumer.mjs +25 -45
- package/esm2022/src/render3/reactivity/effect.mjs +8 -8
- package/esm2022/src/render3/util/injector_utils.mjs +1 -1
- package/esm2022/src/render3/view_manipulation.mjs +13 -2
- package/esm2022/src/signals/index.mjs +4 -4
- package/esm2022/src/signals/src/api.mjs +2 -11
- package/esm2022/src/signals/src/computed.mjs +43 -93
- package/esm2022/src/signals/src/graph.mjs +238 -162
- package/esm2022/src/signals/src/signal.mjs +59 -79
- package/esm2022/src/signals/src/watch.mjs +38 -52
- package/esm2022/src/signals/src/weak_ref.mjs +2 -29
- package/esm2022/src/util/dom.mjs +2 -2
- package/esm2022/src/util/security/trusted_type_defs.mjs +1 -1
- package/esm2022/src/util/security/trusted_types.mjs +1 -1
- package/esm2022/src/version.mjs +1 -1
- package/esm2022/src/zone/ng_zone.mjs +16 -1
- package/esm2022/testing/src/logger.mjs +3 -3
- package/esm2022/testing/src/test_bed.mjs +2 -3
- package/esm2022/testing/src/test_bed_compiler.mjs +9 -13
- package/fesm2022/core.mjs +18796 -18114
- package/fesm2022/core.mjs.map +1 -1
- package/fesm2022/rxjs-interop.mjs +9 -708
- package/fesm2022/rxjs-interop.mjs.map +1 -1
- package/fesm2022/testing.mjs +35 -25672
- package/fesm2022/testing.mjs.map +1 -1
- package/index.d.ts +549 -232
- package/package.json +1 -1
- package/rxjs-interop/index.d.ts +1 -1
- package/schematics/ng-generate/standalone-migration/bundle.js +7307 -6572
- package/schematics/ng-generate/standalone-migration/bundle.js.map +4 -4
- package/testing/index.d.ts +1 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @license Angular v17.0.0-next.
|
|
2
|
+
* @license Angular v17.0.0-next.3
|
|
3
3
|
* (c) 2010-2022 Google LLC. https://angular.io/
|
|
4
4
|
* License: MIT
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { assertInInjectionContext, inject, DestroyRef, Injector, effect, untracked
|
|
7
|
+
import { assertInInjectionContext, inject, DestroyRef, Injector, effect, untracked, signal, ɵRuntimeError, computed } from '@angular/core';
|
|
8
8
|
import { Observable, ReplaySubject } from 'rxjs';
|
|
9
9
|
import { takeUntil } from 'rxjs/operators';
|
|
10
10
|
|
|
@@ -51,10 +51,10 @@ function toObservable(source, options) {
|
|
|
51
51
|
value = source();
|
|
52
52
|
}
|
|
53
53
|
catch (err) {
|
|
54
|
-
untracked
|
|
54
|
+
untracked(() => subject.error(err));
|
|
55
55
|
return;
|
|
56
56
|
}
|
|
57
|
-
untracked
|
|
57
|
+
untracked(() => subject.next(value));
|
|
58
58
|
}, { injector, manualCleanup: true });
|
|
59
59
|
injector.get(DestroyRef).onDestroy(() => {
|
|
60
60
|
watcher.destroy();
|
|
@@ -63,705 +63,6 @@ function toObservable(source, options) {
|
|
|
63
63
|
return subject.asObservable();
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
/**
|
|
67
|
-
* Base URL for the error details page.
|
|
68
|
-
*
|
|
69
|
-
* Keep this constant in sync across:
|
|
70
|
-
* - packages/compiler-cli/src/ngtsc/diagnostics/src/error_details_base_url.ts
|
|
71
|
-
* - packages/core/src/error_details_base_url.ts
|
|
72
|
-
*/
|
|
73
|
-
const ERROR_DETAILS_PAGE_BASE_URL = 'https://angular.io/errors';
|
|
74
|
-
/**
|
|
75
|
-
* URL for the XSS security documentation.
|
|
76
|
-
*/
|
|
77
|
-
const XSS_SECURITY_URL = 'https://g.co/ng/security#xss';
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Class that represents a runtime error.
|
|
81
|
-
* Formats and outputs the error message in a consistent way.
|
|
82
|
-
*
|
|
83
|
-
* Example:
|
|
84
|
-
* ```
|
|
85
|
-
* throw new RuntimeError(
|
|
86
|
-
* RuntimeErrorCode.INJECTOR_ALREADY_DESTROYED,
|
|
87
|
-
* ngDevMode && 'Injector has already been destroyed.');
|
|
88
|
-
* ```
|
|
89
|
-
*
|
|
90
|
-
* Note: the `message` argument contains a descriptive error message as a string in development
|
|
91
|
-
* mode (when the `ngDevMode` is defined). In production mode (after tree-shaking pass), the
|
|
92
|
-
* `message` argument becomes `false`, thus we account for it in the typings and the runtime
|
|
93
|
-
* logic.
|
|
94
|
-
*/
|
|
95
|
-
class RuntimeError extends Error {
|
|
96
|
-
constructor(code, message) {
|
|
97
|
-
super(formatRuntimeError(code, message));
|
|
98
|
-
this.code = code;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Called to format a runtime error.
|
|
103
|
-
* See additional info on the `message` argument type in the `RuntimeError` class description.
|
|
104
|
-
*/
|
|
105
|
-
function formatRuntimeError(code, message) {
|
|
106
|
-
// Error code might be a negative number, which is a special marker that instructs the logic to
|
|
107
|
-
// generate a link to the error details page on angular.io.
|
|
108
|
-
// We also prepend `0` to non-compile-time errors.
|
|
109
|
-
const fullCode = `NG0${Math.abs(code)}`;
|
|
110
|
-
let errorMessage = `${fullCode}${message ? ': ' + message : ''}`;
|
|
111
|
-
if (ngDevMode && code < 0) {
|
|
112
|
-
const addPeriodSeparator = !errorMessage.match(/[.,;!?\n]$/);
|
|
113
|
-
const separator = addPeriodSeparator ? '.' : '';
|
|
114
|
-
errorMessage =
|
|
115
|
-
`${errorMessage}${separator} Find more at ${ERROR_DETAILS_PAGE_BASE_URL}/${fullCode}`;
|
|
116
|
-
}
|
|
117
|
-
return errorMessage;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Symbol used to tell `Signal`s apart from other functions.
|
|
122
|
-
*
|
|
123
|
-
* This can be used to auto-unwrap signals in various cases, or to auto-wrap non-signal values.
|
|
124
|
-
*/
|
|
125
|
-
const SIGNAL = Symbol('SIGNAL');
|
|
126
|
-
/**
|
|
127
|
-
* Checks if the given `value` is a reactive `Signal`.
|
|
128
|
-
*
|
|
129
|
-
* @developerPreview
|
|
130
|
-
*/
|
|
131
|
-
function isSignal(value) {
|
|
132
|
-
return typeof value === 'function' && value[SIGNAL] !== undefined;
|
|
133
|
-
}
|
|
134
|
-
/**
|
|
135
|
-
* Converts `fn` into a marked signal function (where `isSignal(fn)` will be `true`), and
|
|
136
|
-
* potentially add some set of extra properties (passed as an object record `extraApi`).
|
|
137
|
-
*/
|
|
138
|
-
function createSignalFromFunction(node, fn, extraApi = {}) {
|
|
139
|
-
fn[SIGNAL] = node;
|
|
140
|
-
// Copy properties from `extraApi` to `fn` to complete the desired API of the `Signal`.
|
|
141
|
-
return Object.assign(fn, extraApi);
|
|
142
|
-
}
|
|
143
|
-
/**
|
|
144
|
-
* The default equality function used for `signal` and `computed`, which treats objects and arrays
|
|
145
|
-
* as never equal, and all other primitive values using identity semantics.
|
|
146
|
-
*
|
|
147
|
-
* This allows signals to hold non-primitive values (arrays, objects, other collections) and still
|
|
148
|
-
* propagate change notification upon explicit mutation without identity change.
|
|
149
|
-
*
|
|
150
|
-
* @developerPreview
|
|
151
|
-
*/
|
|
152
|
-
function defaultEquals(a, b) {
|
|
153
|
-
// `Object.is` compares two values using identity semantics which is desired behavior for
|
|
154
|
-
// primitive values. If `Object.is` determines two values to be equal we need to make sure that
|
|
155
|
-
// those don't represent objects (we want to make sure that 2 objects are always considered
|
|
156
|
-
// "unequal"). The null check is needed for the special case of JavaScript reporting null values
|
|
157
|
-
// as objects (`typeof null === 'object'`).
|
|
158
|
-
return (a === null || typeof a !== 'object') && Object.is(a, b);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const _global = globalThis;
|
|
162
|
-
|
|
163
|
-
function ngDevModeResetPerfCounters() {
|
|
164
|
-
const locationString = typeof location !== 'undefined' ? location.toString() : '';
|
|
165
|
-
const newCounters = {
|
|
166
|
-
namedConstructors: locationString.indexOf('ngDevMode=namedConstructors') != -1,
|
|
167
|
-
firstCreatePass: 0,
|
|
168
|
-
tNode: 0,
|
|
169
|
-
tView: 0,
|
|
170
|
-
rendererCreateTextNode: 0,
|
|
171
|
-
rendererSetText: 0,
|
|
172
|
-
rendererCreateElement: 0,
|
|
173
|
-
rendererAddEventListener: 0,
|
|
174
|
-
rendererSetAttribute: 0,
|
|
175
|
-
rendererRemoveAttribute: 0,
|
|
176
|
-
rendererSetProperty: 0,
|
|
177
|
-
rendererSetClassName: 0,
|
|
178
|
-
rendererAddClass: 0,
|
|
179
|
-
rendererRemoveClass: 0,
|
|
180
|
-
rendererSetStyle: 0,
|
|
181
|
-
rendererRemoveStyle: 0,
|
|
182
|
-
rendererDestroy: 0,
|
|
183
|
-
rendererDestroyNode: 0,
|
|
184
|
-
rendererMoveNode: 0,
|
|
185
|
-
rendererRemoveNode: 0,
|
|
186
|
-
rendererAppendChild: 0,
|
|
187
|
-
rendererInsertBefore: 0,
|
|
188
|
-
rendererCreateComment: 0,
|
|
189
|
-
hydratedNodes: 0,
|
|
190
|
-
hydratedComponents: 0,
|
|
191
|
-
dehydratedViewsRemoved: 0,
|
|
192
|
-
dehydratedViewsCleanupRuns: 0,
|
|
193
|
-
componentsSkippedHydration: 0,
|
|
194
|
-
};
|
|
195
|
-
// Make sure to refer to ngDevMode as ['ngDevMode'] for closure.
|
|
196
|
-
const allowNgDevModeTrue = locationString.indexOf('ngDevMode=false') === -1;
|
|
197
|
-
_global['ngDevMode'] = allowNgDevModeTrue && newCounters;
|
|
198
|
-
return newCounters;
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* This function checks to see if the `ngDevMode` has been set. If yes,
|
|
202
|
-
* then we honor it, otherwise we default to dev mode with additional checks.
|
|
203
|
-
*
|
|
204
|
-
* The idea is that unless we are doing production build where we explicitly
|
|
205
|
-
* set `ngDevMode == false` we should be helping the developer by providing
|
|
206
|
-
* as much early warning and errors as possible.
|
|
207
|
-
*
|
|
208
|
-
* `ɵɵdefineComponent` is guaranteed to have been called before any component template functions
|
|
209
|
-
* (and thus Ivy instructions), so a single initialization there is sufficient to ensure ngDevMode
|
|
210
|
-
* is defined for the entire instruction set.
|
|
211
|
-
*
|
|
212
|
-
* When checking `ngDevMode` on toplevel, always init it before referencing it
|
|
213
|
-
* (e.g. `((typeof ngDevMode === 'undefined' || ngDevMode) && initNgDevMode())`), otherwise you can
|
|
214
|
-
* get a `ReferenceError` like in https://github.com/angular/angular/issues/31595.
|
|
215
|
-
*
|
|
216
|
-
* Details on possible values for `ngDevMode` can be found on its docstring.
|
|
217
|
-
*
|
|
218
|
-
* NOTE:
|
|
219
|
-
* - changes to the `ngDevMode` name must be synced with `compiler-cli/src/tooling.ts`.
|
|
220
|
-
*/
|
|
221
|
-
function initNgDevMode() {
|
|
222
|
-
// The below checks are to ensure that calling `initNgDevMode` multiple times does not
|
|
223
|
-
// reset the counters.
|
|
224
|
-
// If the `ngDevMode` is not an object, then it means we have not created the perf counters
|
|
225
|
-
// yet.
|
|
226
|
-
if (typeof ngDevMode === 'undefined' || ngDevMode) {
|
|
227
|
-
if (typeof ngDevMode !== 'object') {
|
|
228
|
-
ngDevModeResetPerfCounters();
|
|
229
|
-
}
|
|
230
|
-
return typeof ngDevMode !== 'undefined' && !!ngDevMode;
|
|
231
|
-
}
|
|
232
|
-
return false;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// Required as the signals library is in a separate package, so we need to explicitly ensure the
|
|
236
|
-
/**
|
|
237
|
-
* A `WeakRef`-compatible reference that fakes the API with a strong reference
|
|
238
|
-
* internally.
|
|
239
|
-
*/
|
|
240
|
-
class LeakyRef {
|
|
241
|
-
constructor(ref) {
|
|
242
|
-
this.ref = ref;
|
|
243
|
-
}
|
|
244
|
-
deref() {
|
|
245
|
-
return this.ref;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
// `WeakRef` is not always defined in every TS environment where Angular is compiled. Instead,
|
|
249
|
-
// read it off of the global context if available.
|
|
250
|
-
// tslint:disable-next-line: no-toplevel-property-access
|
|
251
|
-
let WeakRefImpl = _global['WeakRef'] ?? LeakyRef;
|
|
252
|
-
function newWeakRef(value) {
|
|
253
|
-
if (typeof ngDevMode !== 'undefined' && ngDevMode && WeakRefImpl === undefined) {
|
|
254
|
-
throw new Error(`Angular requires a browser which supports the 'WeakRef' API`);
|
|
255
|
-
}
|
|
256
|
-
return new WeakRefImpl(value);
|
|
257
|
-
}
|
|
258
|
-
function setAlternateWeakRefImpl(impl) {
|
|
259
|
-
// no-op since the alternate impl is included by default by the framework. Remove once internal
|
|
260
|
-
// migration is complete.
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// Required as the signals library is in a separate package, so we need to explicitly ensure the
|
|
264
|
-
/**
|
|
265
|
-
* Counter tracking the next `ProducerId` or `ConsumerId`.
|
|
266
|
-
*/
|
|
267
|
-
let _nextReactiveId = 0;
|
|
268
|
-
/**
|
|
269
|
-
* Tracks the currently active reactive consumer (or `null` if there is no active
|
|
270
|
-
* consumer).
|
|
271
|
-
*/
|
|
272
|
-
let activeConsumer = null;
|
|
273
|
-
/**
|
|
274
|
-
* Whether the graph is currently propagating change notifications.
|
|
275
|
-
*/
|
|
276
|
-
let inNotificationPhase = false;
|
|
277
|
-
function setActiveConsumer(consumer) {
|
|
278
|
-
const prev = activeConsumer;
|
|
279
|
-
activeConsumer = consumer;
|
|
280
|
-
return prev;
|
|
281
|
-
}
|
|
282
|
-
/**
|
|
283
|
-
* A node in the reactive graph.
|
|
284
|
-
*
|
|
285
|
-
* Nodes can be producers of reactive values, consumers of other reactive values, or both.
|
|
286
|
-
*
|
|
287
|
-
* Producers are nodes that produce values, and can be depended upon by consumer nodes.
|
|
288
|
-
*
|
|
289
|
-
* Producers expose a monotonic `valueVersion` counter, and are responsible for incrementing this
|
|
290
|
-
* version when their value semantically changes. Some producers may produce their values lazily and
|
|
291
|
-
* thus at times need to be polled for potential updates to their value (and by extension their
|
|
292
|
-
* `valueVersion`). This is accomplished via the `onProducerUpdateValueVersion` method for
|
|
293
|
-
* implemented by producers, which should perform whatever calculations are necessary to ensure
|
|
294
|
-
* `valueVersion` is up to date.
|
|
295
|
-
*
|
|
296
|
-
* Consumers are nodes that depend on the values of producers and are notified when those values
|
|
297
|
-
* might have changed.
|
|
298
|
-
*
|
|
299
|
-
* Consumers do not wrap the reads they consume themselves, but rather can be set as the active
|
|
300
|
-
* reader via `setActiveConsumer`. Reads of producers that happen while a consumer is active will
|
|
301
|
-
* result in those producers being added as dependencies of that consumer node.
|
|
302
|
-
*
|
|
303
|
-
* The set of dependencies of a consumer is dynamic. Implementers expose a monotonically increasing
|
|
304
|
-
* `trackingVersion` counter, which increments whenever the consumer is about to re-run any reactive
|
|
305
|
-
* reads it needs and establish a new set of dependencies as a result.
|
|
306
|
-
*
|
|
307
|
-
* Producers store the last `trackingVersion` they've seen from `Consumer`s which have read them.
|
|
308
|
-
* This allows a producer to identify whether its record of the dependency is current or stale, by
|
|
309
|
-
* comparing the consumer's `trackingVersion` to the version at which the dependency was
|
|
310
|
-
* last observed.
|
|
311
|
-
*/
|
|
312
|
-
class ReactiveNode {
|
|
313
|
-
constructor() {
|
|
314
|
-
this.id = _nextReactiveId++;
|
|
315
|
-
/**
|
|
316
|
-
* A cached weak reference to this node, which will be used in `ReactiveEdge`s.
|
|
317
|
-
*/
|
|
318
|
-
this.ref = newWeakRef(this);
|
|
319
|
-
/**
|
|
320
|
-
* Edges to producers on which this node depends (in its consumer capacity).
|
|
321
|
-
*/
|
|
322
|
-
this.producers = new Map();
|
|
323
|
-
/**
|
|
324
|
-
* Edges to consumers on which this node depends (in its producer capacity).
|
|
325
|
-
*/
|
|
326
|
-
this.consumers = new Map();
|
|
327
|
-
/**
|
|
328
|
-
* Monotonically increasing counter representing a version of this `Consumer`'s
|
|
329
|
-
* dependencies.
|
|
330
|
-
*/
|
|
331
|
-
this.trackingVersion = 0;
|
|
332
|
-
/**
|
|
333
|
-
* Monotonically increasing counter which increases when the value of this `Producer`
|
|
334
|
-
* semantically changes.
|
|
335
|
-
*/
|
|
336
|
-
this.valueVersion = 0;
|
|
337
|
-
}
|
|
338
|
-
/**
|
|
339
|
-
* Polls dependencies of a consumer to determine if they have actually changed.
|
|
340
|
-
*
|
|
341
|
-
* If this returns `false`, then even though the consumer may have previously been notified of a
|
|
342
|
-
* change, the values of its dependencies have not actually changed and the consumer should not
|
|
343
|
-
* rerun any reactions.
|
|
344
|
-
*/
|
|
345
|
-
consumerPollProducersForChange() {
|
|
346
|
-
for (const [producerId, edge] of this.producers) {
|
|
347
|
-
const producer = edge.producerNode.deref();
|
|
348
|
-
// On Safari < 16.1 deref can return null, we need to check for null also.
|
|
349
|
-
// See https://github.com/WebKit/WebKit/commit/44c15ba58912faab38b534fef909dd9e13e095e0
|
|
350
|
-
if (producer == null || edge.atTrackingVersion !== this.trackingVersion) {
|
|
351
|
-
// This dependency edge is stale, so remove it.
|
|
352
|
-
this.producers.delete(producerId);
|
|
353
|
-
producer?.consumers.delete(this.id);
|
|
354
|
-
continue;
|
|
355
|
-
}
|
|
356
|
-
if (producer.producerPollStatus(edge.seenValueVersion)) {
|
|
357
|
-
// One of the dependencies reports a real value change.
|
|
358
|
-
return true;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
// No dependency reported a real value change, so the `Consumer` has also not been
|
|
362
|
-
// impacted.
|
|
363
|
-
return false;
|
|
364
|
-
}
|
|
365
|
-
/**
|
|
366
|
-
* Notify all consumers of this producer that its value may have changed.
|
|
367
|
-
*/
|
|
368
|
-
producerMayHaveChanged() {
|
|
369
|
-
// Prevent signal reads when we're updating the graph
|
|
370
|
-
const prev = inNotificationPhase;
|
|
371
|
-
inNotificationPhase = true;
|
|
372
|
-
try {
|
|
373
|
-
for (const [consumerId, edge] of this.consumers) {
|
|
374
|
-
const consumer = edge.consumerNode.deref();
|
|
375
|
-
// On Safari < 16.1 deref can return null, we need to check for null also.
|
|
376
|
-
// See https://github.com/WebKit/WebKit/commit/44c15ba58912faab38b534fef909dd9e13e095e0
|
|
377
|
-
if (consumer == null || consumer.trackingVersion !== edge.atTrackingVersion) {
|
|
378
|
-
this.consumers.delete(consumerId);
|
|
379
|
-
consumer?.producers.delete(this.id);
|
|
380
|
-
continue;
|
|
381
|
-
}
|
|
382
|
-
consumer.onConsumerDependencyMayHaveChanged();
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
finally {
|
|
386
|
-
inNotificationPhase = prev;
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
/**
|
|
390
|
-
* Mark that this producer node has been accessed in the current reactive context.
|
|
391
|
-
*/
|
|
392
|
-
producerAccessed() {
|
|
393
|
-
if (inNotificationPhase) {
|
|
394
|
-
throw new Error(typeof ngDevMode !== 'undefined' && ngDevMode ?
|
|
395
|
-
`Assertion error: signal read during notification phase` :
|
|
396
|
-
'');
|
|
397
|
-
}
|
|
398
|
-
if (activeConsumer === null) {
|
|
399
|
-
return;
|
|
400
|
-
}
|
|
401
|
-
// Either create or update the dependency `Edge` in both directions.
|
|
402
|
-
let edge = activeConsumer.producers.get(this.id);
|
|
403
|
-
if (edge === undefined) {
|
|
404
|
-
edge = {
|
|
405
|
-
consumerNode: activeConsumer.ref,
|
|
406
|
-
producerNode: this.ref,
|
|
407
|
-
seenValueVersion: this.valueVersion,
|
|
408
|
-
atTrackingVersion: activeConsumer.trackingVersion,
|
|
409
|
-
};
|
|
410
|
-
activeConsumer.producers.set(this.id, edge);
|
|
411
|
-
this.consumers.set(activeConsumer.id, edge);
|
|
412
|
-
}
|
|
413
|
-
else {
|
|
414
|
-
edge.seenValueVersion = this.valueVersion;
|
|
415
|
-
edge.atTrackingVersion = activeConsumer.trackingVersion;
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
/**
|
|
419
|
-
* Whether this consumer currently has any producers registered.
|
|
420
|
-
*/
|
|
421
|
-
get hasProducers() {
|
|
422
|
-
return this.producers.size > 0;
|
|
423
|
-
}
|
|
424
|
-
/**
|
|
425
|
-
* Whether this `ReactiveNode` in its producer capacity is currently allowed to initiate updates,
|
|
426
|
-
* based on the current consumer context.
|
|
427
|
-
*/
|
|
428
|
-
get producerUpdatesAllowed() {
|
|
429
|
-
return activeConsumer?.consumerAllowSignalWrites !== false;
|
|
430
|
-
}
|
|
431
|
-
/**
|
|
432
|
-
* Checks if a `Producer` has a current value which is different than the value
|
|
433
|
-
* last seen at a specific version by a `Consumer` which recorded a dependency on
|
|
434
|
-
* this `Producer`.
|
|
435
|
-
*/
|
|
436
|
-
producerPollStatus(lastSeenValueVersion) {
|
|
437
|
-
// `producer.valueVersion` may be stale, but a mismatch still means that the value
|
|
438
|
-
// last seen by the `Consumer` is also stale.
|
|
439
|
-
if (this.valueVersion !== lastSeenValueVersion) {
|
|
440
|
-
return true;
|
|
441
|
-
}
|
|
442
|
-
// Trigger the `Producer` to update its `valueVersion` if necessary.
|
|
443
|
-
this.onProducerUpdateValueVersion();
|
|
444
|
-
// At this point, we can trust `producer.valueVersion`.
|
|
445
|
-
return this.valueVersion !== lastSeenValueVersion;
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
/**
|
|
450
|
-
* Create a computed `Signal` which derives a reactive value from an expression.
|
|
451
|
-
*
|
|
452
|
-
* @developerPreview
|
|
453
|
-
*/
|
|
454
|
-
function computed(computation, options) {
|
|
455
|
-
const node = new ComputedImpl(computation, options?.equal ?? defaultEquals);
|
|
456
|
-
// Casting here is required for g3, as TS inference behavior is slightly different between our
|
|
457
|
-
// version/options and g3's.
|
|
458
|
-
return createSignalFromFunction(node, node.signal.bind(node));
|
|
459
|
-
}
|
|
460
|
-
/**
|
|
461
|
-
* A dedicated symbol used before a computed value has been calculated for the first time.
|
|
462
|
-
* Explicitly typed as `any` so we can use it as signal's value.
|
|
463
|
-
*/
|
|
464
|
-
const UNSET = Symbol('UNSET');
|
|
465
|
-
/**
|
|
466
|
-
* A dedicated symbol used in place of a computed signal value to indicate that a given computation
|
|
467
|
-
* is in progress. Used to detect cycles in computation chains.
|
|
468
|
-
* Explicitly typed as `any` so we can use it as signal's value.
|
|
469
|
-
*/
|
|
470
|
-
const COMPUTING = Symbol('COMPUTING');
|
|
471
|
-
/**
|
|
472
|
-
* A dedicated symbol used in place of a computed signal value to indicate that a given computation
|
|
473
|
-
* failed. The thrown error is cached until the computation gets dirty again.
|
|
474
|
-
* Explicitly typed as `any` so we can use it as signal's value.
|
|
475
|
-
*/
|
|
476
|
-
const ERRORED = Symbol('ERRORED');
|
|
477
|
-
/**
|
|
478
|
-
* A computation, which derives a value from a declarative reactive expression.
|
|
479
|
-
*
|
|
480
|
-
* `Computed`s are both producers and consumers of reactivity.
|
|
481
|
-
*/
|
|
482
|
-
class ComputedImpl extends ReactiveNode {
|
|
483
|
-
constructor(computation, equal) {
|
|
484
|
-
super();
|
|
485
|
-
this.computation = computation;
|
|
486
|
-
this.equal = equal;
|
|
487
|
-
/**
|
|
488
|
-
* Current value of the computation.
|
|
489
|
-
*
|
|
490
|
-
* This can also be one of the special values `UNSET`, `COMPUTING`, or `ERRORED`.
|
|
491
|
-
*/
|
|
492
|
-
this.value = UNSET;
|
|
493
|
-
/**
|
|
494
|
-
* If `value` is `ERRORED`, the error caught from the last computation attempt which will
|
|
495
|
-
* be re-thrown.
|
|
496
|
-
*/
|
|
497
|
-
this.error = null;
|
|
498
|
-
/**
|
|
499
|
-
* Flag indicating that the computation is currently stale, meaning that one of the
|
|
500
|
-
* dependencies has notified of a potential change.
|
|
501
|
-
*
|
|
502
|
-
* It's possible that no dependency has _actually_ changed, in which case the `stale`
|
|
503
|
-
* state can be resolved without recomputing the value.
|
|
504
|
-
*/
|
|
505
|
-
this.stale = true;
|
|
506
|
-
this.consumerAllowSignalWrites = false;
|
|
507
|
-
}
|
|
508
|
-
onConsumerDependencyMayHaveChanged() {
|
|
509
|
-
if (this.stale) {
|
|
510
|
-
// We've already notified consumers that this value has potentially changed.
|
|
511
|
-
return;
|
|
512
|
-
}
|
|
513
|
-
// Record that the currently cached value may be stale.
|
|
514
|
-
this.stale = true;
|
|
515
|
-
// Notify any consumers about the potential change.
|
|
516
|
-
this.producerMayHaveChanged();
|
|
517
|
-
}
|
|
518
|
-
onProducerUpdateValueVersion() {
|
|
519
|
-
if (!this.stale) {
|
|
520
|
-
// The current value and its version are already up to date.
|
|
521
|
-
return;
|
|
522
|
-
}
|
|
523
|
-
// The current value is stale. Check whether we need to produce a new one.
|
|
524
|
-
if (this.value !== UNSET && this.value !== COMPUTING &&
|
|
525
|
-
!this.consumerPollProducersForChange()) {
|
|
526
|
-
// Even though we were previously notified of a potential dependency update, all of
|
|
527
|
-
// our dependencies report that they have not actually changed in value, so we can
|
|
528
|
-
// resolve the stale state without needing to recompute the current value.
|
|
529
|
-
this.stale = false;
|
|
530
|
-
return;
|
|
531
|
-
}
|
|
532
|
-
// The current value is stale, and needs to be recomputed. It still may not change -
|
|
533
|
-
// that depends on whether the newly computed value is equal to the old.
|
|
534
|
-
this.recomputeValue();
|
|
535
|
-
}
|
|
536
|
-
recomputeValue() {
|
|
537
|
-
if (this.value === COMPUTING) {
|
|
538
|
-
// Our computation somehow led to a cyclic read of itself.
|
|
539
|
-
throw new Error('Detected cycle in computations.');
|
|
540
|
-
}
|
|
541
|
-
const oldValue = this.value;
|
|
542
|
-
this.value = COMPUTING;
|
|
543
|
-
// As we're re-running the computation, update our dependent tracking version number.
|
|
544
|
-
this.trackingVersion++;
|
|
545
|
-
const prevConsumer = setActiveConsumer(this);
|
|
546
|
-
let newValue;
|
|
547
|
-
try {
|
|
548
|
-
newValue = this.computation();
|
|
549
|
-
}
|
|
550
|
-
catch (err) {
|
|
551
|
-
newValue = ERRORED;
|
|
552
|
-
this.error = err;
|
|
553
|
-
}
|
|
554
|
-
finally {
|
|
555
|
-
setActiveConsumer(prevConsumer);
|
|
556
|
-
}
|
|
557
|
-
this.stale = false;
|
|
558
|
-
if (oldValue !== UNSET && oldValue !== ERRORED && newValue !== ERRORED &&
|
|
559
|
-
this.equal(oldValue, newValue)) {
|
|
560
|
-
// No change to `valueVersion` - old and new values are
|
|
561
|
-
// semantically equivalent.
|
|
562
|
-
this.value = oldValue;
|
|
563
|
-
return;
|
|
564
|
-
}
|
|
565
|
-
this.value = newValue;
|
|
566
|
-
this.valueVersion++;
|
|
567
|
-
}
|
|
568
|
-
signal() {
|
|
569
|
-
// Check if the value needs updating before returning it.
|
|
570
|
-
this.onProducerUpdateValueVersion();
|
|
571
|
-
// Record that someone looked at this signal.
|
|
572
|
-
this.producerAccessed();
|
|
573
|
-
if (this.value === ERRORED) {
|
|
574
|
-
throw this.error;
|
|
575
|
-
}
|
|
576
|
-
return this.value;
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
function defaultThrowError() {
|
|
581
|
-
throw new Error();
|
|
582
|
-
}
|
|
583
|
-
let throwInvalidWriteToSignalErrorFn = defaultThrowError;
|
|
584
|
-
function throwInvalidWriteToSignalError() {
|
|
585
|
-
throwInvalidWriteToSignalErrorFn();
|
|
586
|
-
}
|
|
587
|
-
function setThrowInvalidWriteToSignalError(fn) {
|
|
588
|
-
throwInvalidWriteToSignalErrorFn = fn;
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
/**
|
|
592
|
-
* If set, called after `WritableSignal`s are updated.
|
|
593
|
-
*
|
|
594
|
-
* This hook can be used to achieve various effects, such as running effects synchronously as part
|
|
595
|
-
* of setting a signal.
|
|
596
|
-
*/
|
|
597
|
-
let postSignalSetFn = null;
|
|
598
|
-
class WritableSignalImpl extends ReactiveNode {
|
|
599
|
-
constructor(value, equal) {
|
|
600
|
-
super();
|
|
601
|
-
this.value = value;
|
|
602
|
-
this.equal = equal;
|
|
603
|
-
this.consumerAllowSignalWrites = false;
|
|
604
|
-
}
|
|
605
|
-
onConsumerDependencyMayHaveChanged() {
|
|
606
|
-
// This never happens for writable signals as they're not consumers.
|
|
607
|
-
}
|
|
608
|
-
onProducerUpdateValueVersion() {
|
|
609
|
-
// Writable signal value versions are always up to date.
|
|
610
|
-
}
|
|
611
|
-
/**
|
|
612
|
-
* Directly update the value of the signal to a new value, which may or may not be
|
|
613
|
-
* equal to the previous.
|
|
614
|
-
*
|
|
615
|
-
* In the event that `newValue` is semantically equal to the current value, `set` is
|
|
616
|
-
* a no-op.
|
|
617
|
-
*/
|
|
618
|
-
set(newValue) {
|
|
619
|
-
if (!this.producerUpdatesAllowed) {
|
|
620
|
-
throwInvalidWriteToSignalError();
|
|
621
|
-
}
|
|
622
|
-
if (!this.equal(this.value, newValue)) {
|
|
623
|
-
this.value = newValue;
|
|
624
|
-
this.valueVersion++;
|
|
625
|
-
this.producerMayHaveChanged();
|
|
626
|
-
postSignalSetFn?.();
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
/**
|
|
630
|
-
* Derive a new value for the signal from its current value using the `updater` function.
|
|
631
|
-
*
|
|
632
|
-
* This is equivalent to calling `set` on the result of running `updater` on the current
|
|
633
|
-
* value.
|
|
634
|
-
*/
|
|
635
|
-
update(updater) {
|
|
636
|
-
if (!this.producerUpdatesAllowed) {
|
|
637
|
-
throwInvalidWriteToSignalError();
|
|
638
|
-
}
|
|
639
|
-
this.set(updater(this.value));
|
|
640
|
-
}
|
|
641
|
-
/**
|
|
642
|
-
* Calls `mutator` on the current value and assumes that it has been mutated.
|
|
643
|
-
*/
|
|
644
|
-
mutate(mutator) {
|
|
645
|
-
if (!this.producerUpdatesAllowed) {
|
|
646
|
-
throwInvalidWriteToSignalError();
|
|
647
|
-
}
|
|
648
|
-
// Mutate bypasses equality checks as it's by definition changing the value.
|
|
649
|
-
mutator(this.value);
|
|
650
|
-
this.valueVersion++;
|
|
651
|
-
this.producerMayHaveChanged();
|
|
652
|
-
postSignalSetFn?.();
|
|
653
|
-
}
|
|
654
|
-
asReadonly() {
|
|
655
|
-
if (this.readonlySignal === undefined) {
|
|
656
|
-
this.readonlySignal = createSignalFromFunction(this, () => this.signal());
|
|
657
|
-
}
|
|
658
|
-
return this.readonlySignal;
|
|
659
|
-
}
|
|
660
|
-
signal() {
|
|
661
|
-
this.producerAccessed();
|
|
662
|
-
return this.value;
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
/**
|
|
666
|
-
* Create a `Signal` that can be set or updated directly.
|
|
667
|
-
*
|
|
668
|
-
* @developerPreview
|
|
669
|
-
*/
|
|
670
|
-
function signal(initialValue, options) {
|
|
671
|
-
const signalNode = new WritableSignalImpl(initialValue, options?.equal ?? defaultEquals);
|
|
672
|
-
// Casting here is required for g3, as TS inference behavior is slightly different between our
|
|
673
|
-
// version/options and g3's.
|
|
674
|
-
const signalFn = createSignalFromFunction(signalNode, signalNode.signal.bind(signalNode), {
|
|
675
|
-
set: signalNode.set.bind(signalNode),
|
|
676
|
-
update: signalNode.update.bind(signalNode),
|
|
677
|
-
mutate: signalNode.mutate.bind(signalNode),
|
|
678
|
-
asReadonly: signalNode.asReadonly.bind(signalNode)
|
|
679
|
-
});
|
|
680
|
-
return signalFn;
|
|
681
|
-
}
|
|
682
|
-
function setPostSignalSetFn(fn) {
|
|
683
|
-
const prev = postSignalSetFn;
|
|
684
|
-
postSignalSetFn = fn;
|
|
685
|
-
return prev;
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
/**
|
|
689
|
-
* Execute an arbitrary function in a non-reactive (non-tracking) context. The executed function
|
|
690
|
-
* can, optionally, return a value.
|
|
691
|
-
*
|
|
692
|
-
* @developerPreview
|
|
693
|
-
*/
|
|
694
|
-
function untracked(nonReactiveReadsFn) {
|
|
695
|
-
const prevConsumer = setActiveConsumer(null);
|
|
696
|
-
// We are not trying to catch any particular errors here, just making sure that the consumers
|
|
697
|
-
// stack is restored in case of errors.
|
|
698
|
-
try {
|
|
699
|
-
return nonReactiveReadsFn();
|
|
700
|
-
}
|
|
701
|
-
finally {
|
|
702
|
-
setActiveConsumer(prevConsumer);
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
const NOOP_CLEANUP_FN = () => { };
|
|
707
|
-
/**
|
|
708
|
-
* Watches a reactive expression and allows it to be scheduled to re-run
|
|
709
|
-
* when any dependencies notify of a change.
|
|
710
|
-
*
|
|
711
|
-
* `Watch` doesn't run reactive expressions itself, but relies on a consumer-
|
|
712
|
-
* provided scheduling operation to coordinate calling `Watch.run()`.
|
|
713
|
-
*/
|
|
714
|
-
class Watch extends ReactiveNode {
|
|
715
|
-
constructor(watch, schedule, allowSignalWrites) {
|
|
716
|
-
super();
|
|
717
|
-
this.watch = watch;
|
|
718
|
-
this.schedule = schedule;
|
|
719
|
-
this.dirty = false;
|
|
720
|
-
this.cleanupFn = NOOP_CLEANUP_FN;
|
|
721
|
-
this.registerOnCleanup = (cleanupFn) => {
|
|
722
|
-
this.cleanupFn = cleanupFn;
|
|
723
|
-
};
|
|
724
|
-
this.consumerAllowSignalWrites = allowSignalWrites;
|
|
725
|
-
}
|
|
726
|
-
notify() {
|
|
727
|
-
if (!this.dirty) {
|
|
728
|
-
this.schedule(this);
|
|
729
|
-
}
|
|
730
|
-
this.dirty = true;
|
|
731
|
-
}
|
|
732
|
-
onConsumerDependencyMayHaveChanged() {
|
|
733
|
-
this.notify();
|
|
734
|
-
}
|
|
735
|
-
onProducerUpdateValueVersion() {
|
|
736
|
-
// Watches are not producers.
|
|
737
|
-
}
|
|
738
|
-
/**
|
|
739
|
-
* Execute the reactive expression in the context of this `Watch` consumer.
|
|
740
|
-
*
|
|
741
|
-
* Should be called by the user scheduling algorithm when the provided
|
|
742
|
-
* `schedule` hook is called by `Watch`.
|
|
743
|
-
*/
|
|
744
|
-
run() {
|
|
745
|
-
this.dirty = false;
|
|
746
|
-
if (this.trackingVersion !== 0 && !this.consumerPollProducersForChange()) {
|
|
747
|
-
return;
|
|
748
|
-
}
|
|
749
|
-
const prevConsumer = setActiveConsumer(this);
|
|
750
|
-
this.trackingVersion++;
|
|
751
|
-
try {
|
|
752
|
-
this.cleanupFn();
|
|
753
|
-
this.cleanupFn = NOOP_CLEANUP_FN;
|
|
754
|
-
this.watch(this.registerOnCleanup);
|
|
755
|
-
}
|
|
756
|
-
finally {
|
|
757
|
-
setActiveConsumer(prevConsumer);
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
cleanup() {
|
|
761
|
-
this.cleanupFn();
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
|
|
765
66
|
function toSignal(source, options) {
|
|
766
67
|
const requiresCleanup = !options?.manualCleanup;
|
|
767
68
|
requiresCleanup && !options?.injector && assertInInjectionContext(toSignal);
|
|
@@ -771,11 +72,11 @@ function toSignal(source, options) {
|
|
|
771
72
|
let state;
|
|
772
73
|
if (options?.requireSync) {
|
|
773
74
|
// Initially the signal is in a `NoValue` state.
|
|
774
|
-
state = signal
|
|
75
|
+
state = signal({ kind: 0 /* StateKind.NoValue */ });
|
|
775
76
|
}
|
|
776
77
|
else {
|
|
777
78
|
// If an initial value was passed, use it. Otherwise, use `undefined` as the initial value.
|
|
778
|
-
state = signal
|
|
79
|
+
state = signal({ kind: 1 /* StateKind.Value */, value: options?.initialValue });
|
|
779
80
|
}
|
|
780
81
|
const sub = source.subscribe({
|
|
781
82
|
next: value => state.set({ kind: 1 /* StateKind.Value */, value }),
|
|
@@ -784,13 +85,13 @@ function toSignal(source, options) {
|
|
|
784
85
|
// "complete".
|
|
785
86
|
});
|
|
786
87
|
if (ngDevMode && options?.requireSync && untracked(state).kind === 0 /* StateKind.NoValue */) {
|
|
787
|
-
throw new
|
|
88
|
+
throw new ɵRuntimeError(601 /* ɵRuntimeErrorCode.REQUIRE_SYNC_WITHOUT_SYNC_EMIT */, '`toSignal()` called with `requireSync` but `Observable` did not emit synchronously.');
|
|
788
89
|
}
|
|
789
90
|
// Unsubscribe when the current context is destroyed, if requested.
|
|
790
91
|
cleanupRef?.onDestroy(sub.unsubscribe.bind(sub));
|
|
791
92
|
// The actual returned signal is a `computed` of the `State` signal, which maps the various states
|
|
792
93
|
// to either values or errors.
|
|
793
|
-
return computed
|
|
94
|
+
return computed(() => {
|
|
794
95
|
const current = state();
|
|
795
96
|
switch (current.kind) {
|
|
796
97
|
case 1 /* StateKind.Value */:
|
|
@@ -800,7 +101,7 @@ function toSignal(source, options) {
|
|
|
800
101
|
case 0 /* StateKind.NoValue */:
|
|
801
102
|
// This shouldn't really happen because the error is thrown on creation.
|
|
802
103
|
// TODO(alxhub): use a RuntimeError when we finalize the error semantics
|
|
803
|
-
throw new
|
|
104
|
+
throw new ɵRuntimeError(601 /* ɵRuntimeErrorCode.REQUIRE_SYNC_WITHOUT_SYNC_EMIT */, '`toSignal()` called with `requireSync` but `Observable` did not emit synchronously.');
|
|
804
105
|
}
|
|
805
106
|
});
|
|
806
107
|
}
|