@angular/core 21.0.0-next.8 → 21.0.0-rc.0
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/fesm2022/_attribute-chunk.mjs +2 -14
- package/fesm2022/_attribute-chunk.mjs.map +1 -1
- package/fesm2022/_debug_node-chunk.mjs +15214 -28375
- package/fesm2022/_debug_node-chunk.mjs.map +1 -1
- package/fesm2022/_effect-chunk.mjs +402 -120
- package/fesm2022/_effect-chunk.mjs.map +1 -1
- package/fesm2022/_effect-chunk2.mjs +2951 -0
- package/fesm2022/_effect-chunk2.mjs.map +1 -0
- package/fesm2022/_not_found-chunk.mjs +18 -35
- package/fesm2022/_not_found-chunk.mjs.map +1 -1
- package/fesm2022/_resource-chunk.mjs +316 -563
- package/fesm2022/_resource-chunk.mjs.map +1 -1
- package/fesm2022/_untracked-chunk.mjs +96 -0
- package/fesm2022/_untracked-chunk.mjs.map +1 -0
- package/fesm2022/_weak_ref-chunk.mjs +2 -4
- package/fesm2022/_weak_ref-chunk.mjs.map +1 -1
- package/fesm2022/core.mjs +2466 -4309
- package/fesm2022/core.mjs.map +1 -1
- package/fesm2022/primitives-di.mjs +9 -9
- package/fesm2022/primitives-di.mjs.map +1 -1
- package/fesm2022/primitives-event-dispatch.mjs +626 -1460
- package/fesm2022/primitives-event-dispatch.mjs.map +1 -1
- package/fesm2022/primitives-signals.mjs +157 -191
- package/fesm2022/primitives-signals.mjs.map +1 -1
- package/fesm2022/rxjs-interop.mjs +208 -308
- package/fesm2022/rxjs-interop.mjs.map +1 -1
- package/fesm2022/testing.mjs +2305 -3164
- package/fesm2022/testing.mjs.map +1 -1
- package/package.json +8 -2
- package/resources/best-practices.md +56 -0
- package/schematics/bundles/add-bootstrap-context-to-server-main.cjs +7 -25
- package/schematics/bundles/application-config-core.cjs +8 -19
- package/schematics/bundles/{apply_import_manager-CBLmogDD.cjs → apply_import_manager-1Zs_gpB6.cjs} +4 -5
- package/schematics/bundles/bootstrap-options-migration.cjs +93 -132
- package/schematics/bundles/cleanup-unused-imports.cjs +9 -13
- package/schematics/bundles/common-to-standalone-migration.cjs +381 -0
- package/schematics/bundles/{compiler_host-T6xncpiw.cjs → compiler_host-DBwYMlTo.cjs} +10 -11
- package/schematics/bundles/control-flow-migration.cjs +29 -31
- package/schematics/bundles/{imports-DwPXlGFl.cjs → imports-DP72APSx.cjs} +1 -23
- package/schematics/bundles/{index-DWSaRJdz.cjs → index-B7I9sIUx.cjs} +36 -37
- package/schematics/bundles/inject-migration.cjs +9 -26
- package/schematics/bundles/leading_space-D9nQ8UQC.cjs +1 -1
- package/schematics/bundles/{migrate_ts_type_references-Cu-FR4L5.cjs → migrate_ts_type_references-UGIUl7En.cjs} +458 -24
- package/schematics/bundles/{ng_component_template-BkWiUuGG.cjs → ng_component_template-Dsuq1Lw7.cjs} +4 -5
- package/schematics/bundles/{ng_decorators-BI0uV7KI.cjs → ng_decorators-DSFlWYQY.cjs} +2 -2
- package/schematics/bundles/ngclass-to-class-migration.cjs +16 -19
- package/schematics/bundles/ngstyle-to-style-migration.cjs +15 -18
- package/schematics/bundles/nodes-B16H9JUd.cjs +1 -1
- package/schematics/bundles/output-migration.cjs +16 -19
- package/schematics/bundles/{parse_html-C97tKKp3.cjs → parse_html-8VLCL37B.cjs} +5 -5
- package/schematics/bundles/{project_paths-C6g3lqjX.cjs → project_paths-DvD50ouC.cjs} +14 -247
- package/schematics/bundles/project_tsconfig_paths-CDVxT6Ov.cjs +90 -0
- package/schematics/bundles/property_name-BBwFuqMe.cjs +1 -1
- package/schematics/bundles/route-lazy-loading.cjs +9 -25
- package/schematics/bundles/router-current-navigation.cjs +6 -17
- package/schematics/bundles/router-last-successful-navigation.cjs +6 -17
- package/schematics/bundles/router-testing-module-migration.cjs +7 -18
- package/schematics/bundles/self-closing-tags-migration.cjs +14 -17
- package/schematics/bundles/signal-input-migration.cjs +23 -26
- package/schematics/bundles/signal-queries-migration.cjs +22 -25
- package/schematics/bundles/signals.cjs +10 -13
- package/schematics/bundles/standalone-migration.cjs +22 -56
- package/schematics/bundles/symbol-BObKoqes.cjs +1 -1
- package/schematics/collection.json +6 -0
- package/schematics/migrations/common-to-standalone-migration/schema.json +14 -0
- package/types/_api-chunk.d.ts +1 -1
- package/types/_chrome_dev_tools_performance-chunk.d.ts +20 -12
- package/types/_discovery-chunk.d.ts +18 -14
- package/types/_effect-chunk.d.ts +1 -1
- package/types/_event_dispatcher-chunk.d.ts +1 -1
- package/types/_formatter-chunk.d.ts +4 -3
- package/types/_weak_ref-chunk.d.ts +1 -1
- package/types/core.d.ts +49 -100
- package/types/primitives-di.d.ts +1 -1
- package/types/primitives-event-dispatch.d.ts +1 -1
- package/types/primitives-signals.d.ts +2 -2
- package/types/rxjs-interop.d.ts +1 -1
- package/types/testing.d.ts +1 -1
- package/fesm2022/_root_effect_scheduler-chunk.mjs +0 -4630
- package/fesm2022/_root_effect_scheduler-chunk.mjs.map +0 -1
- package/fesm2022/_signal-chunk.mjs +0 -581
- package/fesm2022/_signal-chunk.mjs.map +0 -1
- package/schematics/bundles/index-BnmACOsq.cjs +0 -22319
- package/schematics/bundles/project_tsconfig_paths-CdhVNYMk.cjs +0 -51583
|
@@ -1,625 +1,378 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @license Angular v21.0.0-
|
|
2
|
+
* @license Angular v21.0.0-rc.0
|
|
3
3
|
* (c) 2010-2025 Google LLC. https://angular.dev/
|
|
4
4
|
* License: MIT
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { inject, ErrorHandler, DestroyRef, RuntimeError, formatRuntimeError,
|
|
8
|
-
import { setActiveConsumer, createComputed, SIGNAL
|
|
9
|
-
import { untracked as untracked$1,
|
|
7
|
+
import { inject, ErrorHandler, DestroyRef, RuntimeError, formatRuntimeError, signalAsReadonlyFn, assertInInjectionContext, Injector, effect, PendingTasks, signal } from './_effect-chunk2.mjs';
|
|
8
|
+
import { setActiveConsumer, createComputed, SIGNAL } from './_effect-chunk.mjs';
|
|
9
|
+
import { untracked as untracked$1, createLinkedSignal, linkedSignalSetFn, linkedSignalUpdateFn } from './_untracked-chunk.mjs';
|
|
10
10
|
|
|
11
|
-
/**
|
|
12
|
-
* An `OutputEmitterRef` is created by the `output()` function and can be
|
|
13
|
-
* used to emit values to consumers of your directive or component.
|
|
14
|
-
*
|
|
15
|
-
* Consumers of your directive/component can bind to the output and
|
|
16
|
-
* subscribe to changes via the bound event syntax. For example:
|
|
17
|
-
*
|
|
18
|
-
* ```html
|
|
19
|
-
* <my-comp (valueChange)="processNewValue($event)" />
|
|
20
|
-
* ```
|
|
21
|
-
*
|
|
22
|
-
* @publicAPI
|
|
23
|
-
*/
|
|
24
11
|
class OutputEmitterRef {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
12
|
+
destroyed = false;
|
|
13
|
+
listeners = null;
|
|
14
|
+
errorHandler = inject(ErrorHandler, {
|
|
15
|
+
optional: true
|
|
16
|
+
});
|
|
17
|
+
destroyRef = inject(DestroyRef);
|
|
18
|
+
constructor() {
|
|
19
|
+
this.destroyRef.onDestroy(() => {
|
|
20
|
+
this.destroyed = true;
|
|
21
|
+
this.listeners = null;
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
subscribe(callback) {
|
|
25
|
+
if (this.destroyed) {
|
|
26
|
+
throw new RuntimeError(953, ngDevMode && 'Unexpected subscription to destroyed `OutputRef`. ' + 'The owning directive/component is destroyed.');
|
|
36
27
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
28
|
+
(this.listeners ??= []).push(callback);
|
|
29
|
+
return {
|
|
30
|
+
unsubscribe: () => {
|
|
31
|
+
const idx = this.listeners?.indexOf(callback);
|
|
32
|
+
if (idx !== undefined && idx !== -1) {
|
|
33
|
+
this.listeners?.splice(idx, 1);
|
|
42
34
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
},
|
|
51
|
-
};
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
emit(value) {
|
|
39
|
+
if (this.destroyed) {
|
|
40
|
+
console.warn(formatRuntimeError(953, ngDevMode && 'Unexpected emit for destroyed `OutputRef`. ' + 'The owning directive/component is destroyed.'));
|
|
41
|
+
return;
|
|
52
42
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
if (this.listeners === null) {
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
const previousConsumer = setActiveConsumer(null);
|
|
43
|
+
if (this.listeners === null) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const previousConsumer = setActiveConsumer(null);
|
|
47
|
+
try {
|
|
48
|
+
for (const listenerFn of this.listeners) {
|
|
65
49
|
try {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
catch (err) {
|
|
71
|
-
this.errorHandler?.handleError(err);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
finally {
|
|
76
|
-
setActiveConsumer(previousConsumer);
|
|
50
|
+
listenerFn(value);
|
|
51
|
+
} catch (err) {
|
|
52
|
+
this.errorHandler?.handleError(err);
|
|
77
53
|
}
|
|
54
|
+
}
|
|
55
|
+
} finally {
|
|
56
|
+
setActiveConsumer(previousConsumer);
|
|
78
57
|
}
|
|
58
|
+
}
|
|
79
59
|
}
|
|
80
|
-
/** Gets the owning `DestroyRef` for the given output. */
|
|
81
60
|
function getOutputDestroyRef(ref) {
|
|
82
|
-
|
|
61
|
+
return ref.destroyRef;
|
|
83
62
|
}
|
|
84
63
|
|
|
85
|
-
/**
|
|
86
|
-
* Execute an arbitrary function in a non-reactive (non-tracking) context. The executed function
|
|
87
|
-
* can, optionally, return a value.
|
|
88
|
-
*/
|
|
89
64
|
function untracked(nonReactiveReadsFn) {
|
|
90
|
-
|
|
65
|
+
return untracked$1(nonReactiveReadsFn);
|
|
91
66
|
}
|
|
92
67
|
|
|
93
|
-
/**
|
|
94
|
-
* Create a computed `Signal` which derives a reactive value from an expression.
|
|
95
|
-
*/
|
|
96
68
|
function computed(computation, options) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
69
|
+
const getter = createComputed(computation, options?.equal);
|
|
70
|
+
if (ngDevMode) {
|
|
71
|
+
getter.toString = () => `[Computed: ${getter()}]`;
|
|
72
|
+
getter[SIGNAL].debugName = options?.debugName;
|
|
73
|
+
}
|
|
74
|
+
return getter;
|
|
103
75
|
}
|
|
104
76
|
|
|
105
|
-
|
|
106
|
-
[SIGNAL];
|
|
107
|
-
constructor(node) {
|
|
108
|
-
this[SIGNAL] = node;
|
|
109
|
-
}
|
|
110
|
-
destroy() {
|
|
111
|
-
this[SIGNAL].destroy();
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* Registers an "effect" that will be scheduled & executed whenever the signals that it reads
|
|
116
|
-
* changes.
|
|
117
|
-
*
|
|
118
|
-
* Angular has two different kinds of effect: component effects and root effects. Component effects
|
|
119
|
-
* are created when `effect()` is called from a component, directive, or within a service of a
|
|
120
|
-
* component/directive. Root effects are created when `effect()` is called from outside the
|
|
121
|
-
* component tree, such as in a root service.
|
|
122
|
-
*
|
|
123
|
-
* The two effect types differ in their timing. Component effects run as a component lifecycle
|
|
124
|
-
* event during Angular's synchronization (change detection) process, and can safely read input
|
|
125
|
-
* signals or create/destroy views that depend on component state. Root effects run as microtasks
|
|
126
|
-
* and have no connection to the component tree or change detection.
|
|
127
|
-
*
|
|
128
|
-
* `effect()` must be run in injection context, unless the `injector` option is manually specified.
|
|
129
|
-
*
|
|
130
|
-
* @publicApi 20.0
|
|
131
|
-
*/
|
|
132
|
-
function effect(effectFn, options) {
|
|
133
|
-
ngDevMode &&
|
|
134
|
-
assertNotInReactiveContext(effect, 'Call `effect` outside of a reactive context. For example, schedule the ' +
|
|
135
|
-
'effect inside the component constructor.');
|
|
136
|
-
if (ngDevMode && !options?.injector) {
|
|
137
|
-
assertInInjectionContext(effect);
|
|
138
|
-
}
|
|
139
|
-
if (ngDevMode && options?.allowSignalWrites !== undefined) {
|
|
140
|
-
console.warn(`The 'allowSignalWrites' flag is deprecated and no longer impacts effect() (writes are always allowed)`);
|
|
141
|
-
}
|
|
142
|
-
const injector = options?.injector ?? inject(Injector);
|
|
143
|
-
let destroyRef = options?.manualCleanup !== true ? injector.get(DestroyRef) : null;
|
|
144
|
-
let node;
|
|
145
|
-
const viewContext = injector.get(ViewContext, null, { optional: true });
|
|
146
|
-
const notifier = injector.get(ChangeDetectionScheduler);
|
|
147
|
-
if (viewContext !== null) {
|
|
148
|
-
// This effect was created in the context of a view, and will be associated with the view.
|
|
149
|
-
node = createViewEffect(viewContext.view, notifier, effectFn);
|
|
150
|
-
if (destroyRef instanceof NodeInjectorDestroyRef && destroyRef._lView === viewContext.view) {
|
|
151
|
-
// The effect is being created in the same view as the `DestroyRef` references, so it will be
|
|
152
|
-
// automatically destroyed without the need for an explicit `DestroyRef` registration.
|
|
153
|
-
destroyRef = null;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
else {
|
|
157
|
-
// This effect was created outside the context of a view, and will be scheduled independently.
|
|
158
|
-
node = createRootEffect(effectFn, injector.get(EffectScheduler), notifier);
|
|
159
|
-
}
|
|
160
|
-
node.injector = injector;
|
|
161
|
-
if (destroyRef !== null) {
|
|
162
|
-
// If we need to register for cleanup, do that here.
|
|
163
|
-
node.onDestroyFn = destroyRef.onDestroy(() => node.destroy());
|
|
164
|
-
}
|
|
165
|
-
const effectRef = new EffectRefImpl(node);
|
|
166
|
-
if (ngDevMode) {
|
|
167
|
-
node.debugName = options?.debugName ?? '';
|
|
168
|
-
const prevInjectorProfilerContext = setInjectorProfilerContext({ injector, token: null });
|
|
169
|
-
try {
|
|
170
|
-
emitEffectCreatedEvent(effectRef);
|
|
171
|
-
}
|
|
172
|
-
finally {
|
|
173
|
-
setInjectorProfilerContext(prevInjectorProfilerContext);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
return effectRef;
|
|
177
|
-
}
|
|
178
|
-
const EFFECT_NODE =
|
|
179
|
-
/* @__PURE__ */ (() => ({
|
|
180
|
-
...BASE_EFFECT_NODE,
|
|
181
|
-
cleanupFns: undefined,
|
|
182
|
-
zone: null,
|
|
183
|
-
onDestroyFn: noop,
|
|
184
|
-
run() {
|
|
185
|
-
if (ngDevMode && isInNotificationPhase()) {
|
|
186
|
-
throw new Error(`Schedulers cannot synchronously execute watches while scheduling.`);
|
|
187
|
-
}
|
|
188
|
-
// We clear `setIsRefreshingViews` so that `markForCheck()` within the body of an effect will
|
|
189
|
-
// cause CD to reach the component in question.
|
|
190
|
-
const prevRefreshingViews = setIsRefreshingViews(false);
|
|
191
|
-
try {
|
|
192
|
-
runEffect(this);
|
|
193
|
-
}
|
|
194
|
-
finally {
|
|
195
|
-
setIsRefreshingViews(prevRefreshingViews);
|
|
196
|
-
}
|
|
197
|
-
},
|
|
198
|
-
cleanup() {
|
|
199
|
-
if (!this.cleanupFns?.length) {
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
const prevConsumer = setActiveConsumer(null);
|
|
203
|
-
try {
|
|
204
|
-
// Attempt to run the cleanup functions. Regardless of failure or success, we consider
|
|
205
|
-
// cleanup "completed" and clear the list for the next run of the effect. Note that an error
|
|
206
|
-
// from the cleanup function will still crash the current run of the effect.
|
|
207
|
-
while (this.cleanupFns.length) {
|
|
208
|
-
this.cleanupFns.pop()();
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
finally {
|
|
212
|
-
this.cleanupFns = [];
|
|
213
|
-
setActiveConsumer(prevConsumer);
|
|
214
|
-
}
|
|
215
|
-
},
|
|
216
|
-
}))();
|
|
217
|
-
const ROOT_EFFECT_NODE =
|
|
218
|
-
/* @__PURE__ */ (() => ({
|
|
219
|
-
...EFFECT_NODE,
|
|
220
|
-
consumerMarkedDirty() {
|
|
221
|
-
this.scheduler.schedule(this);
|
|
222
|
-
this.notifier.notify(12 /* NotificationSource.RootEffect */);
|
|
223
|
-
},
|
|
224
|
-
destroy() {
|
|
225
|
-
consumerDestroy(this);
|
|
226
|
-
this.onDestroyFn();
|
|
227
|
-
this.cleanup();
|
|
228
|
-
this.scheduler.remove(this);
|
|
229
|
-
},
|
|
230
|
-
}))();
|
|
231
|
-
const VIEW_EFFECT_NODE =
|
|
232
|
-
/* @__PURE__ */ (() => ({
|
|
233
|
-
...EFFECT_NODE,
|
|
234
|
-
consumerMarkedDirty() {
|
|
235
|
-
this.view[FLAGS] |= 8192 /* LViewFlags.HasChildViewsToRefresh */;
|
|
236
|
-
markAncestorsForTraversal(this.view);
|
|
237
|
-
this.notifier.notify(13 /* NotificationSource.ViewEffect */);
|
|
238
|
-
},
|
|
239
|
-
destroy() {
|
|
240
|
-
consumerDestroy(this);
|
|
241
|
-
this.onDestroyFn();
|
|
242
|
-
this.cleanup();
|
|
243
|
-
this.view[EFFECTS]?.delete(this);
|
|
244
|
-
},
|
|
245
|
-
}))();
|
|
246
|
-
function createViewEffect(view, notifier, fn) {
|
|
247
|
-
const node = Object.create(VIEW_EFFECT_NODE);
|
|
248
|
-
node.view = view;
|
|
249
|
-
node.zone = typeof Zone !== 'undefined' ? Zone.current : null;
|
|
250
|
-
node.notifier = notifier;
|
|
251
|
-
node.fn = createEffectFn(node, fn);
|
|
252
|
-
view[EFFECTS] ??= new Set();
|
|
253
|
-
view[EFFECTS].add(node);
|
|
254
|
-
node.consumerMarkedDirty(node);
|
|
255
|
-
return node;
|
|
256
|
-
}
|
|
257
|
-
function createRootEffect(fn, scheduler, notifier) {
|
|
258
|
-
const node = Object.create(ROOT_EFFECT_NODE);
|
|
259
|
-
node.fn = createEffectFn(node, fn);
|
|
260
|
-
node.scheduler = scheduler;
|
|
261
|
-
node.notifier = notifier;
|
|
262
|
-
node.zone = typeof Zone !== 'undefined' ? Zone.current : null;
|
|
263
|
-
node.scheduler.add(node);
|
|
264
|
-
node.notifier.notify(12 /* NotificationSource.RootEffect */);
|
|
265
|
-
return node;
|
|
266
|
-
}
|
|
267
|
-
function createEffectFn(node, fn) {
|
|
268
|
-
return () => {
|
|
269
|
-
fn((cleanupFn) => (node.cleanupFns ??= []).push(cleanupFn));
|
|
270
|
-
};
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
const identityFn = (v) => v;
|
|
77
|
+
const identityFn = v => v;
|
|
274
78
|
function linkedSignal(optionsOrComputation, options) {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}
|
|
79
|
+
if (typeof optionsOrComputation === 'function') {
|
|
80
|
+
const getter = createLinkedSignal(optionsOrComputation, identityFn, options?.equal);
|
|
81
|
+
return upgradeLinkedSignalGetter(getter, options?.debugName);
|
|
82
|
+
} else {
|
|
83
|
+
const getter = createLinkedSignal(optionsOrComputation.source, optionsOrComputation.computation, optionsOrComputation.equal);
|
|
84
|
+
return upgradeLinkedSignalGetter(getter, optionsOrComputation.debugName);
|
|
85
|
+
}
|
|
283
86
|
}
|
|
284
87
|
function upgradeLinkedSignalGetter(getter, debugName) {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
88
|
+
if (ngDevMode) {
|
|
89
|
+
getter.toString = () => `[LinkedSignal: ${getter()}]`;
|
|
90
|
+
getter[SIGNAL].debugName = debugName;
|
|
91
|
+
}
|
|
92
|
+
const node = getter[SIGNAL];
|
|
93
|
+
const upgradedGetter = getter;
|
|
94
|
+
upgradedGetter.set = newValue => linkedSignalSetFn(node, newValue);
|
|
95
|
+
upgradedGetter.update = updateFn => linkedSignalUpdateFn(node, updateFn);
|
|
96
|
+
upgradedGetter.asReadonly = signalAsReadonlyFn.bind(getter);
|
|
97
|
+
return upgradedGetter;
|
|
295
98
|
}
|
|
296
99
|
|
|
297
|
-
/**
|
|
298
|
-
* Whether a `Resource.value()` should throw an error when the resource is in the error state.
|
|
299
|
-
*
|
|
300
|
-
* This internal flag is being used to gradually roll out this behavior.
|
|
301
|
-
*/
|
|
302
100
|
let RESOURCE_VALUE_THROWS_ERRORS_DEFAULT = true;
|
|
303
101
|
function resource(options) {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
102
|
+
if (ngDevMode && !options?.injector) {
|
|
103
|
+
assertInInjectionContext(resource);
|
|
104
|
+
}
|
|
105
|
+
const oldNameForParams = options.request;
|
|
106
|
+
const params = options.params ?? oldNameForParams ?? (() => null);
|
|
107
|
+
return new ResourceImpl(params, getLoader(options), options.defaultValue, options.equal ? wrapEqualityFn(options.equal) : undefined, options.injector ?? inject(Injector), RESOURCE_VALUE_THROWS_ERRORS_DEFAULT);
|
|
310
108
|
}
|
|
311
|
-
/**
|
|
312
|
-
* Base class which implements `.value` as a `WritableSignal` by delegating `.set` and `.update`.
|
|
313
|
-
*/
|
|
314
109
|
class BaseWritableResource {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
// Check if it's in an error state first to prevent the error from bubbling up.
|
|
331
|
-
if (this.isError()) {
|
|
332
|
-
return false;
|
|
333
|
-
}
|
|
334
|
-
return this.value() !== undefined;
|
|
335
|
-
});
|
|
336
|
-
hasValue() {
|
|
337
|
-
return this.isValueDefined();
|
|
338
|
-
}
|
|
339
|
-
asReadonly() {
|
|
340
|
-
return this;
|
|
110
|
+
value;
|
|
111
|
+
constructor(value) {
|
|
112
|
+
this.value = value;
|
|
113
|
+
this.value.set = this.set.bind(this);
|
|
114
|
+
this.value.update = this.update.bind(this);
|
|
115
|
+
this.value.asReadonly = signalAsReadonlyFn;
|
|
116
|
+
}
|
|
117
|
+
isError = computed(() => this.status() === 'error');
|
|
118
|
+
update(updateFn) {
|
|
119
|
+
this.set(updateFn(untracked(this.value)));
|
|
120
|
+
}
|
|
121
|
+
isLoading = computed(() => this.status() === 'loading' || this.status() === 'reloading');
|
|
122
|
+
isValueDefined = computed(() => {
|
|
123
|
+
if (this.isError()) {
|
|
124
|
+
return false;
|
|
341
125
|
}
|
|
126
|
+
return this.value() !== undefined;
|
|
127
|
+
});
|
|
128
|
+
hasValue() {
|
|
129
|
+
return this.isValueDefined();
|
|
130
|
+
}
|
|
131
|
+
asReadonly() {
|
|
132
|
+
return this;
|
|
133
|
+
}
|
|
342
134
|
}
|
|
343
|
-
/**
|
|
344
|
-
* Implementation for `resource()` which uses a `linkedSignal` to manage the resource's state.
|
|
345
|
-
*/
|
|
346
135
|
class ResourceImpl extends BaseWritableResource {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
return defaultValue;
|
|
372
|
-
}
|
|
373
|
-
// Prevents `hasValue()` from throwing an error when a reload happened in the error state
|
|
374
|
-
if (this.state().status === 'loading' && this.error()) {
|
|
375
|
-
return defaultValue;
|
|
376
|
-
}
|
|
377
|
-
if (!isResolved(streamValue)) {
|
|
378
|
-
if (throwErrorsFromValue) {
|
|
379
|
-
throw new ResourceValueError(this.error());
|
|
380
|
-
}
|
|
381
|
-
else {
|
|
382
|
-
return defaultValue;
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
return streamValue.value;
|
|
386
|
-
}, { equal }));
|
|
387
|
-
this.loaderFn = loaderFn;
|
|
388
|
-
this.equal = equal;
|
|
389
|
-
// Extend `request()` to include a writable reload signal.
|
|
390
|
-
this.extRequest = linkedSignal({
|
|
391
|
-
source: request,
|
|
392
|
-
computation: (request) => ({ request, reload: 0 }),
|
|
393
|
-
});
|
|
394
|
-
// The main resource state is managed in a `linkedSignal`, which allows the resource to change
|
|
395
|
-
// state instantaneously when the request signal changes.
|
|
396
|
-
this.state = linkedSignal({
|
|
397
|
-
// Whenever the request changes,
|
|
398
|
-
source: this.extRequest,
|
|
399
|
-
// Compute the state of the resource given a change in status.
|
|
400
|
-
computation: (extRequest, previous) => {
|
|
401
|
-
const status = extRequest.request === undefined ? 'idle' : 'loading';
|
|
402
|
-
if (!previous) {
|
|
403
|
-
return {
|
|
404
|
-
extRequest,
|
|
405
|
-
status,
|
|
406
|
-
previousStatus: 'idle',
|
|
407
|
-
stream: undefined,
|
|
408
|
-
};
|
|
409
|
-
}
|
|
410
|
-
else {
|
|
411
|
-
return {
|
|
412
|
-
extRequest,
|
|
413
|
-
status,
|
|
414
|
-
previousStatus: projectStatusOfState(previous.value),
|
|
415
|
-
// If the request hasn't changed, keep the previous stream.
|
|
416
|
-
stream: previous.value.extRequest.request === extRequest.request
|
|
417
|
-
? previous.value.stream
|
|
418
|
-
: undefined,
|
|
419
|
-
};
|
|
420
|
-
}
|
|
421
|
-
},
|
|
422
|
-
});
|
|
423
|
-
this.effectRef = effect(this.loadEffect.bind(this), {
|
|
424
|
-
injector,
|
|
425
|
-
manualCleanup: true,
|
|
426
|
-
});
|
|
427
|
-
this.pendingTasks = injector.get(PendingTasks);
|
|
428
|
-
// Cancel any pending request when the resource itself is destroyed.
|
|
429
|
-
this.unregisterOnDestroy = injector.get(DestroyRef).onDestroy(() => this.destroy());
|
|
430
|
-
}
|
|
431
|
-
status = computed(() => projectStatusOfState(this.state()));
|
|
432
|
-
error = computed(() => {
|
|
433
|
-
const stream = this.state().stream?.();
|
|
434
|
-
return stream && !isResolved(stream) ? stream.error : undefined;
|
|
435
|
-
});
|
|
436
|
-
/**
|
|
437
|
-
* Called either directly via `WritableResource.set` or via `.value.set()`.
|
|
438
|
-
*/
|
|
439
|
-
set(value) {
|
|
440
|
-
if (this.destroyed) {
|
|
441
|
-
return;
|
|
136
|
+
loaderFn;
|
|
137
|
+
equal;
|
|
138
|
+
pendingTasks;
|
|
139
|
+
state;
|
|
140
|
+
extRequest;
|
|
141
|
+
effectRef;
|
|
142
|
+
pendingController;
|
|
143
|
+
resolvePendingTask = undefined;
|
|
144
|
+
destroyed = false;
|
|
145
|
+
unregisterOnDestroy;
|
|
146
|
+
constructor(request, loaderFn, defaultValue, equal, injector, throwErrorsFromValue = RESOURCE_VALUE_THROWS_ERRORS_DEFAULT) {
|
|
147
|
+
super(computed(() => {
|
|
148
|
+
const streamValue = this.state().stream?.();
|
|
149
|
+
if (!streamValue) {
|
|
150
|
+
return defaultValue;
|
|
151
|
+
}
|
|
152
|
+
if (this.state().status === 'loading' && this.error()) {
|
|
153
|
+
return defaultValue;
|
|
154
|
+
}
|
|
155
|
+
if (!isResolved(streamValue)) {
|
|
156
|
+
if (throwErrorsFromValue) {
|
|
157
|
+
throw new ResourceValueError(this.error());
|
|
158
|
+
} else {
|
|
159
|
+
return defaultValue;
|
|
442
160
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
161
|
+
}
|
|
162
|
+
return streamValue.value;
|
|
163
|
+
}, {
|
|
164
|
+
equal
|
|
165
|
+
}));
|
|
166
|
+
this.loaderFn = loaderFn;
|
|
167
|
+
this.equal = equal;
|
|
168
|
+
this.extRequest = linkedSignal({
|
|
169
|
+
source: request,
|
|
170
|
+
computation: request => ({
|
|
171
|
+
request,
|
|
172
|
+
reload: 0
|
|
173
|
+
})
|
|
174
|
+
});
|
|
175
|
+
this.state = linkedSignal({
|
|
176
|
+
source: this.extRequest,
|
|
177
|
+
computation: (extRequest, previous) => {
|
|
178
|
+
const status = extRequest.request === undefined ? 'idle' : 'loading';
|
|
179
|
+
if (!previous) {
|
|
180
|
+
return {
|
|
181
|
+
extRequest,
|
|
182
|
+
status,
|
|
183
|
+
previousStatus: 'idle',
|
|
184
|
+
stream: undefined
|
|
185
|
+
};
|
|
186
|
+
} else {
|
|
187
|
+
return {
|
|
188
|
+
extRequest,
|
|
189
|
+
status,
|
|
190
|
+
previousStatus: projectStatusOfState(previous.value),
|
|
191
|
+
stream: previous.value.extRequest.request === extRequest.request ? previous.value.stream : undefined
|
|
192
|
+
};
|
|
451
193
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
this.effectRef = effect(this.loadEffect.bind(this), {
|
|
197
|
+
injector,
|
|
198
|
+
manualCleanup: true
|
|
199
|
+
});
|
|
200
|
+
this.pendingTasks = injector.get(PendingTasks);
|
|
201
|
+
this.unregisterOnDestroy = injector.get(DestroyRef).onDestroy(() => this.destroy());
|
|
202
|
+
}
|
|
203
|
+
status = computed(() => projectStatusOfState(this.state()));
|
|
204
|
+
error = computed(() => {
|
|
205
|
+
const stream = this.state().stream?.();
|
|
206
|
+
return stream && !isResolved(stream) ? stream.error : undefined;
|
|
207
|
+
});
|
|
208
|
+
set(value) {
|
|
209
|
+
if (this.destroyed) {
|
|
210
|
+
return;
|
|
462
211
|
}
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
this.extRequest.update(({ request, reload }) => ({ request, reload: reload + 1 }));
|
|
471
|
-
return true;
|
|
212
|
+
const error = untracked(this.error);
|
|
213
|
+
const state = untracked(this.state);
|
|
214
|
+
if (!error) {
|
|
215
|
+
const current = untracked(this.value);
|
|
216
|
+
if (state.status === 'local' && (this.equal ? this.equal(current, value) : current === value)) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
472
219
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
220
|
+
this.state.set({
|
|
221
|
+
extRequest: state.extRequest,
|
|
222
|
+
status: 'local',
|
|
223
|
+
previousStatus: 'local',
|
|
224
|
+
stream: signal({
|
|
225
|
+
value
|
|
226
|
+
})
|
|
227
|
+
});
|
|
228
|
+
this.abortInProgressLoad();
|
|
229
|
+
}
|
|
230
|
+
reload() {
|
|
231
|
+
const {
|
|
232
|
+
status
|
|
233
|
+
} = untracked(this.state);
|
|
234
|
+
if (status === 'idle' || status === 'loading') {
|
|
235
|
+
return false;
|
|
485
236
|
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
// TODO(alxhub): cleanup after g3 removal of `request` alias.
|
|
521
|
-
request: extRequest.request,
|
|
522
|
-
abortSignal,
|
|
523
|
-
previous: {
|
|
524
|
-
status: previousStatus,
|
|
525
|
-
},
|
|
526
|
-
});
|
|
527
|
-
});
|
|
528
|
-
// If this request has been aborted, or the current request no longer
|
|
529
|
-
// matches this load, then we should ignore this resolution.
|
|
530
|
-
if (abortSignal.aborted || untracked(this.extRequest) !== extRequest) {
|
|
531
|
-
return;
|
|
532
|
-
}
|
|
533
|
-
this.state.set({
|
|
534
|
-
extRequest,
|
|
535
|
-
status: 'resolved',
|
|
536
|
-
previousStatus: 'resolved',
|
|
537
|
-
stream,
|
|
538
|
-
});
|
|
539
|
-
}
|
|
540
|
-
catch (err) {
|
|
541
|
-
if (abortSignal.aborted || untracked(this.extRequest) !== extRequest) {
|
|
542
|
-
return;
|
|
543
|
-
}
|
|
544
|
-
this.state.set({
|
|
545
|
-
extRequest,
|
|
546
|
-
status: 'resolved',
|
|
547
|
-
previousStatus: 'error',
|
|
548
|
-
stream: signal({ error: encapsulateResourceError(err) }),
|
|
549
|
-
});
|
|
550
|
-
}
|
|
551
|
-
finally {
|
|
552
|
-
// Resolve the pending task now that the resource has a value.
|
|
553
|
-
resolvePendingTask?.();
|
|
554
|
-
resolvePendingTask = undefined;
|
|
555
|
-
}
|
|
237
|
+
this.extRequest.update(({
|
|
238
|
+
request,
|
|
239
|
+
reload
|
|
240
|
+
}) => ({
|
|
241
|
+
request,
|
|
242
|
+
reload: reload + 1
|
|
243
|
+
}));
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
destroy() {
|
|
247
|
+
this.destroyed = true;
|
|
248
|
+
this.unregisterOnDestroy();
|
|
249
|
+
this.effectRef.destroy();
|
|
250
|
+
this.abortInProgressLoad();
|
|
251
|
+
this.state.set({
|
|
252
|
+
extRequest: {
|
|
253
|
+
request: undefined,
|
|
254
|
+
reload: 0
|
|
255
|
+
},
|
|
256
|
+
status: 'idle',
|
|
257
|
+
previousStatus: 'idle',
|
|
258
|
+
stream: undefined
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
async loadEffect() {
|
|
262
|
+
const extRequest = this.extRequest();
|
|
263
|
+
const {
|
|
264
|
+
status: currentStatus,
|
|
265
|
+
previousStatus
|
|
266
|
+
} = untracked(this.state);
|
|
267
|
+
if (extRequest.request === undefined) {
|
|
268
|
+
return;
|
|
269
|
+
} else if (currentStatus !== 'loading') {
|
|
270
|
+
return;
|
|
556
271
|
}
|
|
557
|
-
abortInProgressLoad()
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
272
|
+
this.abortInProgressLoad();
|
|
273
|
+
let resolvePendingTask = this.resolvePendingTask = this.pendingTasks.add();
|
|
274
|
+
const {
|
|
275
|
+
signal: abortSignal
|
|
276
|
+
} = this.pendingController = new AbortController();
|
|
277
|
+
try {
|
|
278
|
+
const stream = await untracked(() => {
|
|
279
|
+
return this.loaderFn({
|
|
280
|
+
params: extRequest.request,
|
|
281
|
+
request: extRequest.request,
|
|
282
|
+
abortSignal,
|
|
283
|
+
previous: {
|
|
284
|
+
status: previousStatus
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
if (abortSignal.aborted || untracked(this.extRequest) !== extRequest) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
this.state.set({
|
|
292
|
+
extRequest,
|
|
293
|
+
status: 'resolved',
|
|
294
|
+
previousStatus: 'resolved',
|
|
295
|
+
stream
|
|
296
|
+
});
|
|
297
|
+
} catch (err) {
|
|
298
|
+
if (abortSignal.aborted || untracked(this.extRequest) !== extRequest) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
this.state.set({
|
|
302
|
+
extRequest,
|
|
303
|
+
status: 'resolved',
|
|
304
|
+
previousStatus: 'error',
|
|
305
|
+
stream: signal({
|
|
306
|
+
error: encapsulateResourceError(err)
|
|
307
|
+
})
|
|
308
|
+
});
|
|
309
|
+
} finally {
|
|
310
|
+
resolvePendingTask?.();
|
|
311
|
+
resolvePendingTask = undefined;
|
|
563
312
|
}
|
|
313
|
+
}
|
|
314
|
+
abortInProgressLoad() {
|
|
315
|
+
untracked(() => this.pendingController?.abort());
|
|
316
|
+
this.pendingController = undefined;
|
|
317
|
+
this.resolvePendingTask?.();
|
|
318
|
+
this.resolvePendingTask = undefined;
|
|
319
|
+
}
|
|
564
320
|
}
|
|
565
|
-
/**
|
|
566
|
-
* Wraps an equality function to handle either value being `undefined`.
|
|
567
|
-
*/
|
|
568
321
|
function wrapEqualityFn(equal) {
|
|
569
|
-
|
|
322
|
+
return (a, b) => a === undefined || b === undefined ? a === b : equal(a, b);
|
|
570
323
|
}
|
|
571
324
|
function getLoader(options) {
|
|
572
|
-
|
|
573
|
-
|
|
325
|
+
if (isStreamingResourceOptions(options)) {
|
|
326
|
+
return options.stream;
|
|
327
|
+
}
|
|
328
|
+
return async params => {
|
|
329
|
+
try {
|
|
330
|
+
return signal({
|
|
331
|
+
value: await options.loader(params)
|
|
332
|
+
});
|
|
333
|
+
} catch (err) {
|
|
334
|
+
return signal({
|
|
335
|
+
error: encapsulateResourceError(err)
|
|
336
|
+
});
|
|
574
337
|
}
|
|
575
|
-
|
|
576
|
-
try {
|
|
577
|
-
return signal({ value: await options.loader(params) });
|
|
578
|
-
}
|
|
579
|
-
catch (err) {
|
|
580
|
-
return signal({ error: encapsulateResourceError(err) });
|
|
581
|
-
}
|
|
582
|
-
};
|
|
338
|
+
};
|
|
583
339
|
}
|
|
584
340
|
function isStreamingResourceOptions(options) {
|
|
585
|
-
|
|
341
|
+
return !!options.stream;
|
|
586
342
|
}
|
|
587
|
-
/**
|
|
588
|
-
* Project from a state with `ResourceInternalStatus` to the user-facing `ResourceStatus`
|
|
589
|
-
*/
|
|
590
343
|
function projectStatusOfState(state) {
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
344
|
+
switch (state.status) {
|
|
345
|
+
case 'loading':
|
|
346
|
+
return state.extRequest.reload === 0 ? 'loading' : 'reloading';
|
|
347
|
+
case 'resolved':
|
|
348
|
+
return isResolved(state.stream()) ? 'resolved' : 'error';
|
|
349
|
+
default:
|
|
350
|
+
return state.status;
|
|
351
|
+
}
|
|
599
352
|
}
|
|
600
353
|
function isResolved(state) {
|
|
601
|
-
|
|
354
|
+
return state.error === undefined;
|
|
602
355
|
}
|
|
603
356
|
function encapsulateResourceError(error) {
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
357
|
+
if (error instanceof Error) {
|
|
358
|
+
return error;
|
|
359
|
+
}
|
|
360
|
+
return new ResourceWrappedError(error);
|
|
608
361
|
}
|
|
609
362
|
class ResourceValueError extends Error {
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
363
|
+
constructor(error) {
|
|
364
|
+
super(ngDevMode ? `Resource is currently in an error state (see Error.cause for details): ${error.message}` : error.message, {
|
|
365
|
+
cause: error
|
|
366
|
+
});
|
|
367
|
+
}
|
|
615
368
|
}
|
|
616
369
|
class ResourceWrappedError extends Error {
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
370
|
+
constructor(error) {
|
|
371
|
+
super(ngDevMode ? `Resource returned an error that's not an Error instance: ${String(error)}. Check this error's .cause for the actual error.` : String(error), {
|
|
372
|
+
cause: error
|
|
373
|
+
});
|
|
374
|
+
}
|
|
622
375
|
}
|
|
623
376
|
|
|
624
|
-
export { OutputEmitterRef, ResourceImpl, computed,
|
|
377
|
+
export { OutputEmitterRef, ResourceImpl, computed, encapsulateResourceError, getOutputDestroyRef, linkedSignal, resource, untracked };
|
|
625
378
|
//# sourceMappingURL=_resource-chunk.mjs.map
|