@angular/core 20.0.0-next.4 → 20.0.0-next.6
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/api.d-DQLNOR5l.d.ts +297 -0
- package/discovery.d-CFs2MaLO.d.ts +7383 -0
- package/{event_dispatcher.d-pVP0-wST.d.ts → event_dispatcher.d-DlbccpYq.d.ts} +3 -2
- package/fesm2022/attribute-BWp59EjE.mjs +24 -0
- package/fesm2022/attribute-BWp59EjE.mjs.map +1 -0
- package/fesm2022/core.mjs +586 -36873
- package/fesm2022/core.mjs.map +1 -1
- package/fesm2022/debug_node-z_3NG8qT.mjs +32079 -0
- package/fesm2022/debug_node-z_3NG8qT.mjs.map +1 -0
- package/fesm2022/primitives/di.mjs +18 -4
- package/fesm2022/primitives/di.mjs.map +1 -1
- package/fesm2022/primitives/event-dispatch.mjs +2 -16
- package/fesm2022/primitives/event-dispatch.mjs.map +1 -1
- package/fesm2022/primitives/signals.mjs +5 -3
- package/fesm2022/primitives/signals.mjs.map +1 -1
- package/fesm2022/resource-CPPwEcg7.mjs +619 -0
- package/fesm2022/resource-CPPwEcg7.mjs.map +1 -0
- package/fesm2022/root_effect_scheduler-VSXfCzDX.mjs +3847 -0
- package/fesm2022/root_effect_scheduler-VSXfCzDX.mjs.map +1 -0
- package/fesm2022/rxjs-interop.mjs +16 -9
- package/fesm2022/rxjs-interop.mjs.map +1 -1
- package/fesm2022/{untracked-DkcXpNb_.mjs → signal-B6pMq7KS.mjs} +16 -114
- package/fesm2022/signal-B6pMq7KS.mjs.map +1 -0
- package/fesm2022/testing.mjs +265 -201
- package/fesm2022/testing.mjs.map +1 -1
- package/fesm2022/untracked-Bz5WMeU1.mjs +117 -0
- package/fesm2022/untracked-Bz5WMeU1.mjs.map +1 -0
- package/fesm2022/weak_ref-BaIq-pgY.mjs +12 -0
- package/fesm2022/weak_ref-BaIq-pgY.mjs.map +1 -0
- package/{weak_ref.d-BZ7gyRag.d.ts → graph.d-BcIOep_B.d.ts} +3 -24
- package/index.d.ts +2624 -10909
- package/ng_i18n_closure_mode.d-C9d2CaSt.d.ts +832 -0
- package/package.json +3 -3
- package/primitives/di/index.d.ts +3 -2
- package/primitives/event-dispatch/index.d.ts +3 -3
- package/primitives/signals/index.d.ts +8 -4
- package/rxjs-interop/index.d.ts +10 -7
- package/schematics/bundles/{apply_import_manager-CeNv8GIG.js → apply_import_manager-DnMqg1pY.js} +6 -6
- package/schematics/bundles/{compiler_host-DwM3ugW3.js → change_tracker-UMPkv-eH.js} +3 -121
- package/schematics/bundles/checker-BFBQyesT.js +17719 -0
- package/schematics/bundles/cleanup-unused-imports.js +25 -19
- package/schematics/bundles/{checker-k591b6WQ.js → compiler-BQ7R7w2v.js} +1325 -18286
- package/schematics/bundles/compiler_host-CAfDJO3W.js +129 -0
- package/schematics/bundles/control-flow-migration.js +28 -40
- package/schematics/bundles/document-core.js +96 -0
- package/schematics/bundles/imports-CIX-JgAN.js +1 -1
- package/schematics/bundles/{index-B4OAlHh8.js → index-Cv4Q415G.js} +641 -547
- package/schematics/bundles/{index-BhELUmYx.js → index-D8tMJPKa.js} +35 -34
- package/schematics/bundles/inject-flags.js +14 -13
- package/schematics/bundles/inject-migration.js +29 -10
- package/schematics/bundles/leading_space-D9nQ8UQC.js +1 -1
- package/schematics/bundles/{migrate_ts_type_references-Be0TNYen.js → migrate_ts_type_references-Cq_ZBuT4.js} +21 -20
- package/schematics/bundles/ng_decorators-DznZ5jMl.js +1 -1
- package/schematics/bundles/nodes-B16H9JUd.js +1 -1
- package/schematics/bundles/output-migration.js +88 -25
- package/schematics/bundles/{run_in_devkit-CkvEksWP.js → project_paths-ql6qcf_c.js} +254 -243
- package/schematics/bundles/project_tsconfig_paths-CDVxT6Ov.js +1 -1
- package/schematics/bundles/property_name-BBwFuqMe.js +1 -1
- package/schematics/bundles/route-lazy-loading.js +7 -5
- package/schematics/bundles/self-closing-tags-migration.js +25 -19
- package/schematics/bundles/signal-input-migration.js +26 -20
- package/schematics/bundles/signal-queries-migration.js +51 -33
- package/schematics/bundles/signals.js +8 -7
- package/schematics/bundles/standalone-migration.js +11 -9
- package/schematics/bundles/symbol-VPWguRxr.js +1 -1
- package/schematics/bundles/test-bed-get.js +13 -12
- package/schematics/collection.json +0 -6
- package/schematics/migrations.json +11 -0
- package/signal.d-E0e5nW1p.d.ts +31 -0
- package/testing/index.d.ts +16 -28
- package/weak_ref.d-eGOEP9S1.d.ts +9 -0
- package/fesm2022/injector-BlLwZ2sr.mjs +0 -24
- package/fesm2022/injector-BlLwZ2sr.mjs.map +0 -1
- package/fesm2022/untracked-DkcXpNb_.mjs.map +0 -1
- package/navigation_types.d-DgDrF5rp.d.ts +0 -121
- package/schematics/ng-generate/control-flow-migration/schema.json +0 -20
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @license Angular v20.0.0-next.
|
|
2
|
+
* @license Angular v20.0.0-next.6
|
|
3
3
|
* (c) 2010-2025 Google LLC. https://angular.io/
|
|
4
4
|
* License: MIT
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
export {
|
|
7
|
+
import { consumerMarkDirty, SIGNAL, consumerDestroy, isInNotificationPhase, consumerPollProducersForChange, consumerBeforeComputation, consumerAfterComputation, REACTIVE_NODE } from '../signal-B6pMq7KS.mjs';
|
|
8
|
+
export { SIGNAL_NODE, createComputed, createSignal, defaultEquals, getActiveConsumer, isReactive, producerAccessed, producerIncrementEpoch, producerMarkClean, producerNotifyConsumers, producerUpdateValueVersion, producerUpdatesAllowed, runPostProducerCreatedFn, runPostSignalSetFn, setActiveConsumer, setPostProducerCreatedFn, setPostSignalSetFn, setThrowInvalidWriteToSignalError, signalGetFn, signalSetFn, signalUpdateFn } from '../signal-B6pMq7KS.mjs';
|
|
9
|
+
export { createLinkedSignal, linkedSignalSetFn, linkedSignalUpdateFn, untracked } from '../untracked-Bz5WMeU1.mjs';
|
|
10
|
+
export { setAlternateWeakRefImpl } from '../weak_ref-BaIq-pgY.mjs';
|
|
9
11
|
|
|
10
12
|
function createWatch(fn, schedule, allowSignalWrites) {
|
|
11
13
|
const node = Object.create(WATCH_NODE);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signals.mjs","sources":["../../../../../../../packages/core/primitives/signals/src/watch.ts"],"sourcesContent":["/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {\n consumerAfterComputation,\n consumerBeforeComputation,\n consumerDestroy,\n consumerMarkDirty,\n consumerPollProducersForChange,\n isInNotificationPhase,\n REACTIVE_NODE,\n ReactiveNode,\n SIGNAL,\n} from './graph';\n\n/**\n * A cleanup function that can be optionally registered from the watch logic. If registered, the\n * cleanup logic runs before the next watch execution.\n */\nexport type WatchCleanupFn = () => void;\n\n/**\n * A callback passed to the watch function that makes it possible to register cleanup logic.\n */\nexport type WatchCleanupRegisterFn = (cleanupFn: WatchCleanupFn) => void;\n\nexport interface Watch {\n notify(): void;\n\n /**\n * Execute the reactive expression in the context of this `Watch` consumer.\n *\n * Should be called by the user scheduling algorithm when the provided\n * `schedule` hook is called by `Watch`.\n */\n run(): void;\n\n cleanup(): void;\n\n /**\n * Destroy the watcher:\n * - disconnect it from the reactive graph;\n * - mark it as destroyed so subsequent run and notify operations are noop.\n */\n destroy(): void;\n\n [SIGNAL]: WatchNode;\n}\nexport interface WatchNode extends ReactiveNode {\n hasRun: boolean;\n fn: ((onCleanup: WatchCleanupRegisterFn) => void) | null;\n schedule: ((watch: Watch) => void) | null;\n cleanupFn: WatchCleanupFn;\n ref: Watch;\n}\n\nexport function createWatch(\n fn: (onCleanup: WatchCleanupRegisterFn) => void,\n schedule: (watch: Watch) => void,\n allowSignalWrites: boolean,\n): Watch {\n const node: WatchNode = Object.create(WATCH_NODE);\n if (allowSignalWrites) {\n node.consumerAllowSignalWrites = true;\n }\n\n node.fn = fn;\n node.schedule = schedule;\n\n const registerOnCleanup = (cleanupFn: WatchCleanupFn) => {\n node.cleanupFn = cleanupFn;\n };\n\n function isWatchNodeDestroyed(node: WatchNode) {\n return node.fn === null && node.schedule === null;\n }\n\n function destroyWatchNode(node: WatchNode) {\n if (!isWatchNodeDestroyed(node)) {\n consumerDestroy(node); // disconnect watcher from the reactive graph\n node.cleanupFn();\n\n // nullify references to the integration functions to mark node as destroyed\n node.fn = null;\n node.schedule = null;\n node.cleanupFn = NOOP_CLEANUP_FN;\n }\n }\n\n const run = () => {\n if (node.fn === null) {\n // trying to run a destroyed watch is noop\n return;\n }\n\n if (isInNotificationPhase()) {\n throw new Error(`Schedulers cannot synchronously execute watches while scheduling.`);\n }\n\n node.dirty = false;\n if (node.hasRun && !consumerPollProducersForChange(node)) {\n return;\n }\n node.hasRun = true;\n\n const prevConsumer = consumerBeforeComputation(node);\n try {\n node.cleanupFn();\n node.cleanupFn = NOOP_CLEANUP_FN;\n node.fn(registerOnCleanup);\n } finally {\n consumerAfterComputation(node, prevConsumer);\n }\n };\n\n node.ref = {\n notify: () => consumerMarkDirty(node),\n run,\n cleanup: () => node.cleanupFn(),\n destroy: () => destroyWatchNode(node),\n [SIGNAL]: node,\n };\n\n return node.ref;\n}\n\nconst NOOP_CLEANUP_FN: WatchCleanupFn = () => {};\n\n// Note: Using an IIFE here to ensure that the spread assignment is not considered\n// a side-effect, ending up preserving `COMPUTED_NODE` and `REACTIVE_NODE`.\n// TODO: remove when https://github.com/evanw/esbuild/issues/3392 is resolved.\nconst WATCH_NODE: Partial<WatchNode> = /* @__PURE__ */ (() => {\n return {\n ...REACTIVE_NODE,\n consumerIsAlwaysLive: true,\n consumerAllowSignalWrites: false,\n consumerMarkedDirty: (node: WatchNode) => {\n if (node.schedule !== null) {\n node.schedule(node.ref);\n }\n },\n hasRun: false,\n cleanupFn: NOOP_CLEANUP_FN,\n };\n})();\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"signals.mjs","sources":["../../../../../../../packages/core/primitives/signals/src/watch.ts"],"sourcesContent":["/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {\n consumerAfterComputation,\n consumerBeforeComputation,\n consumerDestroy,\n consumerMarkDirty,\n consumerPollProducersForChange,\n isInNotificationPhase,\n REACTIVE_NODE,\n ReactiveNode,\n SIGNAL,\n} from './graph';\n\n/**\n * A cleanup function that can be optionally registered from the watch logic. If registered, the\n * cleanup logic runs before the next watch execution.\n */\nexport type WatchCleanupFn = () => void;\n\n/**\n * A callback passed to the watch function that makes it possible to register cleanup logic.\n */\nexport type WatchCleanupRegisterFn = (cleanupFn: WatchCleanupFn) => void;\n\nexport interface Watch {\n notify(): void;\n\n /**\n * Execute the reactive expression in the context of this `Watch` consumer.\n *\n * Should be called by the user scheduling algorithm when the provided\n * `schedule` hook is called by `Watch`.\n */\n run(): void;\n\n cleanup(): void;\n\n /**\n * Destroy the watcher:\n * - disconnect it from the reactive graph;\n * - mark it as destroyed so subsequent run and notify operations are noop.\n */\n destroy(): void;\n\n [SIGNAL]: WatchNode;\n}\nexport interface WatchNode extends ReactiveNode {\n hasRun: boolean;\n fn: ((onCleanup: WatchCleanupRegisterFn) => void) | null;\n schedule: ((watch: Watch) => void) | null;\n cleanupFn: WatchCleanupFn;\n ref: Watch;\n}\n\nexport function createWatch(\n fn: (onCleanup: WatchCleanupRegisterFn) => void,\n schedule: (watch: Watch) => void,\n allowSignalWrites: boolean,\n): Watch {\n const node: WatchNode = Object.create(WATCH_NODE);\n if (allowSignalWrites) {\n node.consumerAllowSignalWrites = true;\n }\n\n node.fn = fn;\n node.schedule = schedule;\n\n const registerOnCleanup = (cleanupFn: WatchCleanupFn) => {\n node.cleanupFn = cleanupFn;\n };\n\n function isWatchNodeDestroyed(node: WatchNode) {\n return node.fn === null && node.schedule === null;\n }\n\n function destroyWatchNode(node: WatchNode) {\n if (!isWatchNodeDestroyed(node)) {\n consumerDestroy(node); // disconnect watcher from the reactive graph\n node.cleanupFn();\n\n // nullify references to the integration functions to mark node as destroyed\n node.fn = null;\n node.schedule = null;\n node.cleanupFn = NOOP_CLEANUP_FN;\n }\n }\n\n const run = () => {\n if (node.fn === null) {\n // trying to run a destroyed watch is noop\n return;\n }\n\n if (isInNotificationPhase()) {\n throw new Error(`Schedulers cannot synchronously execute watches while scheduling.`);\n }\n\n node.dirty = false;\n if (node.hasRun && !consumerPollProducersForChange(node)) {\n return;\n }\n node.hasRun = true;\n\n const prevConsumer = consumerBeforeComputation(node);\n try {\n node.cleanupFn();\n node.cleanupFn = NOOP_CLEANUP_FN;\n node.fn(registerOnCleanup);\n } finally {\n consumerAfterComputation(node, prevConsumer);\n }\n };\n\n node.ref = {\n notify: () => consumerMarkDirty(node),\n run,\n cleanup: () => node.cleanupFn(),\n destroy: () => destroyWatchNode(node),\n [SIGNAL]: node,\n };\n\n return node.ref;\n}\n\nconst NOOP_CLEANUP_FN: WatchCleanupFn = () => {};\n\n// Note: Using an IIFE here to ensure that the spread assignment is not considered\n// a side-effect, ending up preserving `COMPUTED_NODE` and `REACTIVE_NODE`.\n// TODO: remove when https://github.com/evanw/esbuild/issues/3392 is resolved.\nconst WATCH_NODE: Partial<WatchNode> = /* @__PURE__ */ (() => {\n return {\n ...REACTIVE_NODE,\n consumerIsAlwaysLive: true,\n consumerAllowSignalWrites: false,\n consumerMarkedDirty: (node: WatchNode) => {\n if (node.schedule !== null) {\n node.schedule(node.ref);\n }\n },\n hasRun: false,\n cleanupFn: NOOP_CLEANUP_FN,\n };\n})();\n"],"names":[],"mappings":";;;;;;;;;;;SA6DgB,WAAW,CACzB,EAA+C,EAC/C,QAAgC,EAChC,iBAA0B,EAAA;IAE1B,MAAM,IAAI,GAAc,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC;IACjD,IAAI,iBAAiB,EAAE;AACrB,QAAA,IAAI,CAAC,yBAAyB,GAAG,IAAI;;AAGvC,IAAA,IAAI,CAAC,EAAE,GAAG,EAAE;AACZ,IAAA,IAAI,CAAC,QAAQ,GAAG,QAAQ;AAExB,IAAA,MAAM,iBAAiB,GAAG,CAAC,SAAyB,KAAI;AACtD,QAAA,IAAI,CAAC,SAAS,GAAG,SAAS;AAC5B,KAAC;IAED,SAAS,oBAAoB,CAAC,IAAe,EAAA;QAC3C,OAAO,IAAI,CAAC,EAAE,KAAK,IAAI,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI;;IAGnD,SAAS,gBAAgB,CAAC,IAAe,EAAA;AACvC,QAAA,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,EAAE;AAC/B,YAAA,eAAe,CAAC,IAAI,CAAC,CAAC;YACtB,IAAI,CAAC,SAAS,EAAE;;AAGhB,YAAA,IAAI,CAAC,EAAE,GAAG,IAAI;AACd,YAAA,IAAI,CAAC,QAAQ,GAAG,IAAI;AACpB,YAAA,IAAI,CAAC,SAAS,GAAG,eAAe;;;IAIpC,MAAM,GAAG,GAAG,MAAK;AACf,QAAA,IAAI,IAAI,CAAC,EAAE,KAAK,IAAI,EAAE;;YAEpB;;QAGF,IAAI,qBAAqB,EAAE,EAAE;AAC3B,YAAA,MAAM,IAAI,KAAK,CAAC,CAAA,iEAAA,CAAmE,CAAC;;AAGtF,QAAA,IAAI,CAAC,KAAK,GAAG,KAAK;QAClB,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC,8BAA8B,CAAC,IAAI,CAAC,EAAE;YACxD;;AAEF,QAAA,IAAI,CAAC,MAAM,GAAG,IAAI;AAElB,QAAA,MAAM,YAAY,GAAG,yBAAyB,CAAC,IAAI,CAAC;AACpD,QAAA,IAAI;YACF,IAAI,CAAC,SAAS,EAAE;AAChB,YAAA,IAAI,CAAC,SAAS,GAAG,eAAe;AAChC,YAAA,IAAI,CAAC,EAAE,CAAC,iBAAiB,CAAC;;gBAClB;AACR,YAAA,wBAAwB,CAAC,IAAI,EAAE,YAAY,CAAC;;AAEhD,KAAC;IAED,IAAI,CAAC,GAAG,GAAG;AACT,QAAA,MAAM,EAAE,MAAM,iBAAiB,CAAC,IAAI,CAAC;QACrC,GAAG;AACH,QAAA,OAAO,EAAE,MAAM,IAAI,CAAC,SAAS,EAAE;AAC/B,QAAA,OAAO,EAAE,MAAM,gBAAgB,CAAC,IAAI,CAAC;QACrC,CAAC,MAAM,GAAG,IAAI;KACf;IAED,OAAO,IAAI,CAAC,GAAG;AACjB;AAEA,MAAM,eAAe,GAAmB,MAAK,GAAG;AAEhD;AACA;AACA;AACA,MAAM,UAAU,mBAAuC,CAAC,MAAK;IAC3D,OAAO;AACL,QAAA,GAAG,aAAa;AAChB,QAAA,oBAAoB,EAAE,IAAI;AAC1B,QAAA,yBAAyB,EAAE,KAAK;AAChC,QAAA,mBAAmB,EAAE,CAAC,IAAe,KAAI;AACvC,YAAA,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI,EAAE;AAC1B,gBAAA,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC;;SAE1B;AACD,QAAA,MAAM,EAAE,KAAK;AACb,QAAA,SAAS,EAAE,eAAe;KAC3B;AACH,CAAC,GAAG;;;;"}
|
|
@@ -0,0 +1,619 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Angular v20.0.0-next.6
|
|
3
|
+
* (c) 2010-2025 Google LLC. https://angular.io/
|
|
4
|
+
* License: MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { inject, RuntimeError, formatRuntimeError, ErrorHandler, DestroyRef, assertNotInReactiveContext, assertInInjectionContext, Injector, ViewContext, ChangeDetectionScheduler, EffectScheduler, setInjectorProfilerContext, emitEffectCreatedEvent, EFFECTS, noop, FLAGS, markAncestorsForTraversal, setIsRefreshingViews, NodeInjectorDestroyRef, InjectionToken, signalAsReadonlyFn, PendingTasks, signal } from './root_effect_scheduler-VSXfCzDX.mjs';
|
|
8
|
+
import { setActiveConsumer, createComputed, SIGNAL, REACTIVE_NODE, consumerDestroy, isInNotificationPhase, consumerPollProducersForChange, consumerBeforeComputation, consumerAfterComputation } from './signal-B6pMq7KS.mjs';
|
|
9
|
+
import { untracked as untracked$1, createLinkedSignal, linkedSignalSetFn, linkedSignalUpdateFn } from './untracked-Bz5WMeU1.mjs';
|
|
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
|
+
class OutputEmitterRef {
|
|
25
|
+
destroyed = false;
|
|
26
|
+
listeners = null;
|
|
27
|
+
errorHandler = inject(ErrorHandler, { optional: true });
|
|
28
|
+
/** @internal */
|
|
29
|
+
destroyRef = inject(DestroyRef);
|
|
30
|
+
constructor() {
|
|
31
|
+
// Clean-up all listeners and mark as destroyed upon destroy.
|
|
32
|
+
this.destroyRef.onDestroy(() => {
|
|
33
|
+
this.destroyed = true;
|
|
34
|
+
this.listeners = null;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
subscribe(callback) {
|
|
38
|
+
if (this.destroyed) {
|
|
39
|
+
throw new RuntimeError(953 /* RuntimeErrorCode.OUTPUT_REF_DESTROYED */, ngDevMode &&
|
|
40
|
+
'Unexpected subscription to destroyed `OutputRef`. ' +
|
|
41
|
+
'The owning directive/component is destroyed.');
|
|
42
|
+
}
|
|
43
|
+
(this.listeners ??= []).push(callback);
|
|
44
|
+
return {
|
|
45
|
+
unsubscribe: () => {
|
|
46
|
+
const idx = this.listeners?.indexOf(callback);
|
|
47
|
+
if (idx !== undefined && idx !== -1) {
|
|
48
|
+
this.listeners?.splice(idx, 1);
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/** Emits a new value to the output. */
|
|
54
|
+
emit(value) {
|
|
55
|
+
if (this.destroyed) {
|
|
56
|
+
console.warn(formatRuntimeError(953 /* RuntimeErrorCode.OUTPUT_REF_DESTROYED */, ngDevMode &&
|
|
57
|
+
'Unexpected emit for destroyed `OutputRef`. ' +
|
|
58
|
+
'The owning directive/component is destroyed.'));
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (this.listeners === null) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const previousConsumer = setActiveConsumer(null);
|
|
65
|
+
try {
|
|
66
|
+
for (const listenerFn of this.listeners) {
|
|
67
|
+
try {
|
|
68
|
+
listenerFn(value);
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
this.errorHandler?.handleError(err);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
setActiveConsumer(previousConsumer);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/** Gets the owning `DestroyRef` for the given output. */
|
|
81
|
+
function getOutputDestroyRef(ref) {
|
|
82
|
+
return ref.destroyRef;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Execute an arbitrary function in a non-reactive (non-tracking) context. The executed function
|
|
87
|
+
* can, optionally, return a value.
|
|
88
|
+
*/
|
|
89
|
+
function untracked(nonReactiveReadsFn) {
|
|
90
|
+
return untracked$1(nonReactiveReadsFn);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Create a computed `Signal` which derives a reactive value from an expression.
|
|
95
|
+
*/
|
|
96
|
+
function computed(computation, options) {
|
|
97
|
+
const getter = createComputed(computation, options?.equal);
|
|
98
|
+
if (ngDevMode) {
|
|
99
|
+
getter.toString = () => `[Computed: ${getter()}]`;
|
|
100
|
+
getter[SIGNAL].debugName = options?.debugName;
|
|
101
|
+
}
|
|
102
|
+
return getter;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
class EffectRefImpl {
|
|
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
|
+
function effect(effectFn, options) {
|
|
131
|
+
ngDevMode &&
|
|
132
|
+
assertNotInReactiveContext(effect, 'Call `effect` outside of a reactive context. For example, schedule the ' +
|
|
133
|
+
'effect inside the component constructor.');
|
|
134
|
+
!options?.injector && assertInInjectionContext(effect);
|
|
135
|
+
if (ngDevMode && options?.allowSignalWrites !== undefined) {
|
|
136
|
+
console.warn(`The 'allowSignalWrites' flag is deprecated and no longer impacts effect() (writes are always allowed)`);
|
|
137
|
+
}
|
|
138
|
+
const injector = options?.injector ?? inject(Injector);
|
|
139
|
+
let destroyRef = options?.manualCleanup !== true ? injector.get(DestroyRef) : null;
|
|
140
|
+
let node;
|
|
141
|
+
const viewContext = injector.get(ViewContext, null, { optional: true });
|
|
142
|
+
const notifier = injector.get(ChangeDetectionScheduler);
|
|
143
|
+
if (viewContext !== null) {
|
|
144
|
+
// This effect was created in the context of a view, and will be associated with the view.
|
|
145
|
+
node = createViewEffect(viewContext.view, notifier, effectFn);
|
|
146
|
+
if (destroyRef instanceof NodeInjectorDestroyRef && destroyRef._lView === viewContext.view) {
|
|
147
|
+
// The effect is being created in the same view as the `DestroyRef` references, so it will be
|
|
148
|
+
// automatically destroyed without the need for an explicit `DestroyRef` registration.
|
|
149
|
+
destroyRef = null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
// This effect was created outside the context of a view, and will be scheduled independently.
|
|
154
|
+
node = createRootEffect(effectFn, injector.get(EffectScheduler), notifier);
|
|
155
|
+
}
|
|
156
|
+
node.injector = injector;
|
|
157
|
+
if (destroyRef !== null) {
|
|
158
|
+
// If we need to register for cleanup, do that here.
|
|
159
|
+
node.onDestroyFn = destroyRef.onDestroy(() => node.destroy());
|
|
160
|
+
}
|
|
161
|
+
const effectRef = new EffectRefImpl(node);
|
|
162
|
+
if (ngDevMode) {
|
|
163
|
+
node.debugName = options?.debugName ?? '';
|
|
164
|
+
const prevInjectorProfilerContext = setInjectorProfilerContext({ injector, token: null });
|
|
165
|
+
try {
|
|
166
|
+
emitEffectCreatedEvent(effectRef);
|
|
167
|
+
}
|
|
168
|
+
finally {
|
|
169
|
+
setInjectorProfilerContext(prevInjectorProfilerContext);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return effectRef;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Not public API, which guarantees `EffectScheduler` only ever comes from the application root
|
|
176
|
+
* injector.
|
|
177
|
+
*/
|
|
178
|
+
/* @__PURE__ */ new InjectionToken('', {
|
|
179
|
+
providedIn: 'root',
|
|
180
|
+
factory: () => inject(EffectScheduler),
|
|
181
|
+
});
|
|
182
|
+
const BASE_EFFECT_NODE =
|
|
183
|
+
/* @__PURE__ */ (() => ({
|
|
184
|
+
...REACTIVE_NODE,
|
|
185
|
+
consumerIsAlwaysLive: true,
|
|
186
|
+
consumerAllowSignalWrites: true,
|
|
187
|
+
dirty: true,
|
|
188
|
+
hasRun: false,
|
|
189
|
+
cleanupFns: undefined,
|
|
190
|
+
zone: null,
|
|
191
|
+
kind: 'effect',
|
|
192
|
+
onDestroyFn: noop,
|
|
193
|
+
run() {
|
|
194
|
+
this.dirty = false;
|
|
195
|
+
if (ngDevMode && isInNotificationPhase()) {
|
|
196
|
+
throw new Error(`Schedulers cannot synchronously execute watches while scheduling.`);
|
|
197
|
+
}
|
|
198
|
+
if (this.hasRun && !consumerPollProducersForChange(this)) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
this.hasRun = true;
|
|
202
|
+
const registerCleanupFn = (cleanupFn) => (this.cleanupFns ??= []).push(cleanupFn);
|
|
203
|
+
const prevNode = consumerBeforeComputation(this);
|
|
204
|
+
// We clear `setIsRefreshingViews` so that `markForCheck()` within the body of an effect will
|
|
205
|
+
// cause CD to reach the component in question.
|
|
206
|
+
const prevRefreshingViews = setIsRefreshingViews(false);
|
|
207
|
+
try {
|
|
208
|
+
this.maybeCleanup();
|
|
209
|
+
this.fn(registerCleanupFn);
|
|
210
|
+
}
|
|
211
|
+
finally {
|
|
212
|
+
setIsRefreshingViews(prevRefreshingViews);
|
|
213
|
+
consumerAfterComputation(this, prevNode);
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
maybeCleanup() {
|
|
217
|
+
if (!this.cleanupFns?.length) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const prevConsumer = setActiveConsumer(null);
|
|
221
|
+
try {
|
|
222
|
+
// Attempt to run the cleanup functions. Regardless of failure or success, we consider
|
|
223
|
+
// cleanup "completed" and clear the list for the next run of the effect. Note that an error
|
|
224
|
+
// from the cleanup function will still crash the current run of the effect.
|
|
225
|
+
while (this.cleanupFns.length) {
|
|
226
|
+
this.cleanupFns.pop()();
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
finally {
|
|
230
|
+
this.cleanupFns = [];
|
|
231
|
+
setActiveConsumer(prevConsumer);
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
}))();
|
|
235
|
+
const ROOT_EFFECT_NODE =
|
|
236
|
+
/* @__PURE__ */ (() => ({
|
|
237
|
+
...BASE_EFFECT_NODE,
|
|
238
|
+
consumerMarkedDirty() {
|
|
239
|
+
this.scheduler.schedule(this);
|
|
240
|
+
this.notifier.notify(12 /* NotificationSource.RootEffect */);
|
|
241
|
+
},
|
|
242
|
+
destroy() {
|
|
243
|
+
consumerDestroy(this);
|
|
244
|
+
this.onDestroyFn();
|
|
245
|
+
this.maybeCleanup();
|
|
246
|
+
this.scheduler.remove(this);
|
|
247
|
+
},
|
|
248
|
+
}))();
|
|
249
|
+
const VIEW_EFFECT_NODE =
|
|
250
|
+
/* @__PURE__ */ (() => ({
|
|
251
|
+
...BASE_EFFECT_NODE,
|
|
252
|
+
consumerMarkedDirty() {
|
|
253
|
+
this.view[FLAGS] |= 8192 /* LViewFlags.HasChildViewsToRefresh */;
|
|
254
|
+
markAncestorsForTraversal(this.view);
|
|
255
|
+
this.notifier.notify(13 /* NotificationSource.ViewEffect */);
|
|
256
|
+
},
|
|
257
|
+
destroy() {
|
|
258
|
+
consumerDestroy(this);
|
|
259
|
+
this.onDestroyFn();
|
|
260
|
+
this.maybeCleanup();
|
|
261
|
+
this.view[EFFECTS]?.delete(this);
|
|
262
|
+
},
|
|
263
|
+
}))();
|
|
264
|
+
function createViewEffect(view, notifier, fn) {
|
|
265
|
+
const node = Object.create(VIEW_EFFECT_NODE);
|
|
266
|
+
node.view = view;
|
|
267
|
+
node.zone = typeof Zone !== 'undefined' ? Zone.current : null;
|
|
268
|
+
node.notifier = notifier;
|
|
269
|
+
node.fn = fn;
|
|
270
|
+
view[EFFECTS] ??= new Set();
|
|
271
|
+
view[EFFECTS].add(node);
|
|
272
|
+
node.consumerMarkedDirty(node);
|
|
273
|
+
return node;
|
|
274
|
+
}
|
|
275
|
+
function createRootEffect(fn, scheduler, notifier) {
|
|
276
|
+
const node = Object.create(ROOT_EFFECT_NODE);
|
|
277
|
+
node.fn = fn;
|
|
278
|
+
node.scheduler = scheduler;
|
|
279
|
+
node.notifier = notifier;
|
|
280
|
+
node.zone = typeof Zone !== 'undefined' ? Zone.current : null;
|
|
281
|
+
node.scheduler.add(node);
|
|
282
|
+
node.notifier.notify(12 /* NotificationSource.RootEffect */);
|
|
283
|
+
return node;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Status of a `Resource`.
|
|
288
|
+
*
|
|
289
|
+
* @experimental
|
|
290
|
+
*/
|
|
291
|
+
var ResourceStatus;
|
|
292
|
+
(function (ResourceStatus) {
|
|
293
|
+
/**
|
|
294
|
+
* The resource has no valid request and will not perform any loading.
|
|
295
|
+
*
|
|
296
|
+
* `value()` will be `undefined`.
|
|
297
|
+
*/
|
|
298
|
+
ResourceStatus[ResourceStatus["Idle"] = 0] = "Idle";
|
|
299
|
+
/**
|
|
300
|
+
* Loading failed with an error.
|
|
301
|
+
*
|
|
302
|
+
* `value()` will be `undefined`.
|
|
303
|
+
*/
|
|
304
|
+
ResourceStatus[ResourceStatus["Error"] = 1] = "Error";
|
|
305
|
+
/**
|
|
306
|
+
* The resource is currently loading a new value as a result of a change in its `request`.
|
|
307
|
+
*
|
|
308
|
+
* `value()` will be `undefined`.
|
|
309
|
+
*/
|
|
310
|
+
ResourceStatus[ResourceStatus["Loading"] = 2] = "Loading";
|
|
311
|
+
/**
|
|
312
|
+
* The resource is currently reloading a fresh value for the same request.
|
|
313
|
+
*
|
|
314
|
+
* `value()` will continue to return the previously fetched value during the reloading operation.
|
|
315
|
+
*/
|
|
316
|
+
ResourceStatus[ResourceStatus["Reloading"] = 3] = "Reloading";
|
|
317
|
+
/**
|
|
318
|
+
* Loading has completed and the resource has the value returned from the loader.
|
|
319
|
+
*/
|
|
320
|
+
ResourceStatus[ResourceStatus["Resolved"] = 4] = "Resolved";
|
|
321
|
+
/**
|
|
322
|
+
* The resource's value was set locally via `.set()` or `.update()`.
|
|
323
|
+
*/
|
|
324
|
+
ResourceStatus[ResourceStatus["Local"] = 5] = "Local";
|
|
325
|
+
})(ResourceStatus || (ResourceStatus = {}));
|
|
326
|
+
|
|
327
|
+
const identityFn = (v) => v;
|
|
328
|
+
function linkedSignal(optionsOrComputation, options) {
|
|
329
|
+
if (typeof optionsOrComputation === 'function') {
|
|
330
|
+
const getter = createLinkedSignal(optionsOrComputation, (identityFn), options?.equal);
|
|
331
|
+
return upgradeLinkedSignalGetter(getter);
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
const getter = createLinkedSignal(optionsOrComputation.source, optionsOrComputation.computation, optionsOrComputation.equal);
|
|
335
|
+
return upgradeLinkedSignalGetter(getter);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
function upgradeLinkedSignalGetter(getter) {
|
|
339
|
+
if (ngDevMode) {
|
|
340
|
+
getter.toString = () => `[LinkedSignal: ${getter()}]`;
|
|
341
|
+
}
|
|
342
|
+
const node = getter[SIGNAL];
|
|
343
|
+
const upgradedGetter = getter;
|
|
344
|
+
upgradedGetter.set = (newValue) => linkedSignalSetFn(node, newValue);
|
|
345
|
+
upgradedGetter.update = (updateFn) => linkedSignalUpdateFn(node, updateFn);
|
|
346
|
+
upgradedGetter.asReadonly = signalAsReadonlyFn.bind(getter);
|
|
347
|
+
return upgradedGetter;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function resource(options) {
|
|
351
|
+
options?.injector || assertInInjectionContext(resource);
|
|
352
|
+
const request = (options.request ?? (() => null));
|
|
353
|
+
return new ResourceImpl(request, getLoader(options), options.defaultValue, options.equal ? wrapEqualityFn(options.equal) : undefined, options.injector ?? inject(Injector));
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Base class which implements `.value` as a `WritableSignal` by delegating `.set` and `.update`.
|
|
357
|
+
*/
|
|
358
|
+
class BaseWritableResource {
|
|
359
|
+
value;
|
|
360
|
+
constructor(value) {
|
|
361
|
+
this.value = value;
|
|
362
|
+
this.value.set = this.set.bind(this);
|
|
363
|
+
this.value.update = this.update.bind(this);
|
|
364
|
+
this.value.asReadonly = signalAsReadonlyFn;
|
|
365
|
+
}
|
|
366
|
+
update(updateFn) {
|
|
367
|
+
this.set(updateFn(untracked(this.value)));
|
|
368
|
+
}
|
|
369
|
+
isLoading = computed(() => this.status() === ResourceStatus.Loading || this.status() === ResourceStatus.Reloading);
|
|
370
|
+
hasValue() {
|
|
371
|
+
return this.value() !== undefined;
|
|
372
|
+
}
|
|
373
|
+
asReadonly() {
|
|
374
|
+
return this;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Implementation for `resource()` which uses a `linkedSignal` to manage the resource's state.
|
|
379
|
+
*/
|
|
380
|
+
class ResourceImpl extends BaseWritableResource {
|
|
381
|
+
loaderFn;
|
|
382
|
+
defaultValue;
|
|
383
|
+
equal;
|
|
384
|
+
pendingTasks;
|
|
385
|
+
/**
|
|
386
|
+
* The current state of the resource. Status, value, and error are derived from this.
|
|
387
|
+
*/
|
|
388
|
+
state;
|
|
389
|
+
/**
|
|
390
|
+
* Combines the current request with a reload counter which allows the resource to be reloaded on
|
|
391
|
+
* imperative command.
|
|
392
|
+
*/
|
|
393
|
+
extRequest;
|
|
394
|
+
effectRef;
|
|
395
|
+
pendingController;
|
|
396
|
+
resolvePendingTask = undefined;
|
|
397
|
+
destroyed = false;
|
|
398
|
+
constructor(request, loaderFn, defaultValue, equal, injector) {
|
|
399
|
+
super(
|
|
400
|
+
// Feed a computed signal for the value to `BaseWritableResource`, which will upgrade it to a
|
|
401
|
+
// `WritableSignal` that delegates to `ResourceImpl.set`.
|
|
402
|
+
computed(() => {
|
|
403
|
+
const streamValue = this.state().stream?.();
|
|
404
|
+
return streamValue && isResolved(streamValue) ? streamValue.value : this.defaultValue;
|
|
405
|
+
}, { equal }));
|
|
406
|
+
this.loaderFn = loaderFn;
|
|
407
|
+
this.defaultValue = defaultValue;
|
|
408
|
+
this.equal = equal;
|
|
409
|
+
// Extend `request()` to include a writable reload signal.
|
|
410
|
+
this.extRequest = linkedSignal({
|
|
411
|
+
source: request,
|
|
412
|
+
computation: (request) => ({ request, reload: 0 }),
|
|
413
|
+
});
|
|
414
|
+
// The main resource state is managed in a `linkedSignal`, which allows the resource to change
|
|
415
|
+
// state instantaneously when the request signal changes.
|
|
416
|
+
this.state = linkedSignal({
|
|
417
|
+
// Whenever the request changes,
|
|
418
|
+
source: this.extRequest,
|
|
419
|
+
// Compute the state of the resource given a change in status.
|
|
420
|
+
computation: (extRequest, previous) => {
|
|
421
|
+
const status = extRequest.request === undefined ? ResourceStatus.Idle : ResourceStatus.Loading;
|
|
422
|
+
if (!previous) {
|
|
423
|
+
return {
|
|
424
|
+
extRequest,
|
|
425
|
+
status,
|
|
426
|
+
previousStatus: ResourceStatus.Idle,
|
|
427
|
+
stream: undefined,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
else {
|
|
431
|
+
return {
|
|
432
|
+
extRequest,
|
|
433
|
+
status,
|
|
434
|
+
previousStatus: projectStatusOfState(previous.value),
|
|
435
|
+
// If the request hasn't changed, keep the previous stream.
|
|
436
|
+
stream: previous.value.extRequest.request === extRequest.request
|
|
437
|
+
? previous.value.stream
|
|
438
|
+
: undefined,
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
},
|
|
442
|
+
});
|
|
443
|
+
this.effectRef = effect(this.loadEffect.bind(this), {
|
|
444
|
+
injector,
|
|
445
|
+
manualCleanup: true,
|
|
446
|
+
});
|
|
447
|
+
this.pendingTasks = injector.get(PendingTasks);
|
|
448
|
+
// Cancel any pending request when the resource itself is destroyed.
|
|
449
|
+
injector.get(DestroyRef).onDestroy(() => this.destroy());
|
|
450
|
+
}
|
|
451
|
+
status = computed(() => projectStatusOfState(this.state()));
|
|
452
|
+
error = computed(() => {
|
|
453
|
+
const stream = this.state().stream?.();
|
|
454
|
+
return stream && !isResolved(stream) ? stream.error : undefined;
|
|
455
|
+
});
|
|
456
|
+
/**
|
|
457
|
+
* Called either directly via `WritableResource.set` or via `.value.set()`.
|
|
458
|
+
*/
|
|
459
|
+
set(value) {
|
|
460
|
+
if (this.destroyed) {
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
const current = untracked(this.value);
|
|
464
|
+
const state = untracked(this.state);
|
|
465
|
+
if (state.status === ResourceStatus.Local &&
|
|
466
|
+
(this.equal ? this.equal(current, value) : current === value)) {
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
// Enter Local state with the user-defined value.
|
|
470
|
+
this.state.set({
|
|
471
|
+
extRequest: state.extRequest,
|
|
472
|
+
status: ResourceStatus.Local,
|
|
473
|
+
previousStatus: ResourceStatus.Local,
|
|
474
|
+
stream: signal({ value }),
|
|
475
|
+
});
|
|
476
|
+
// We're departing from whatever state the resource was in previously, so cancel any in-progress
|
|
477
|
+
// loading operations.
|
|
478
|
+
this.abortInProgressLoad();
|
|
479
|
+
}
|
|
480
|
+
reload() {
|
|
481
|
+
// We don't want to restart in-progress loads.
|
|
482
|
+
const { status } = untracked(this.state);
|
|
483
|
+
if (status === ResourceStatus.Idle || status === ResourceStatus.Loading) {
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
486
|
+
// Increment the request reload to trigger the `state` linked signal to switch us to `Reload`
|
|
487
|
+
this.extRequest.update(({ request, reload }) => ({ request, reload: reload + 1 }));
|
|
488
|
+
return true;
|
|
489
|
+
}
|
|
490
|
+
destroy() {
|
|
491
|
+
this.destroyed = true;
|
|
492
|
+
this.effectRef.destroy();
|
|
493
|
+
this.abortInProgressLoad();
|
|
494
|
+
// Destroyed resources enter Idle state.
|
|
495
|
+
this.state.set({
|
|
496
|
+
extRequest: { request: undefined, reload: 0 },
|
|
497
|
+
status: ResourceStatus.Idle,
|
|
498
|
+
previousStatus: ResourceStatus.Idle,
|
|
499
|
+
stream: undefined,
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
async loadEffect() {
|
|
503
|
+
const extRequest = this.extRequest();
|
|
504
|
+
// Capture the previous status before any state transitions. Note that this is `untracked` since
|
|
505
|
+
// we do not want the effect to depend on the state of the resource, only on the request.
|
|
506
|
+
const { status: currentStatus, previousStatus } = untracked(this.state);
|
|
507
|
+
if (extRequest.request === undefined) {
|
|
508
|
+
// Nothing to load (and we should already be in a non-loading state).
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
else if (currentStatus !== ResourceStatus.Loading) {
|
|
512
|
+
// We're not in a loading or reloading state, so this loading request is stale.
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
// Cancel any previous loading attempts.
|
|
516
|
+
this.abortInProgressLoad();
|
|
517
|
+
// Capturing _this_ load's pending task in a local variable is important here. We may attempt to
|
|
518
|
+
// resolve it twice:
|
|
519
|
+
//
|
|
520
|
+
// 1. when the loading function promise resolves/rejects
|
|
521
|
+
// 2. when cancelling the loading operation
|
|
522
|
+
//
|
|
523
|
+
// After the loading operation is cancelled, `this.resolvePendingTask` no longer represents this
|
|
524
|
+
// particular task, but this `await` may eventually resolve/reject. Thus, when we cancel in
|
|
525
|
+
// response to (1) below, we need to cancel the locally saved task.
|
|
526
|
+
let resolvePendingTask = (this.resolvePendingTask =
|
|
527
|
+
this.pendingTasks.add());
|
|
528
|
+
const { signal: abortSignal } = (this.pendingController = new AbortController());
|
|
529
|
+
try {
|
|
530
|
+
// The actual loading is run through `untracked` - only the request side of `resource` is
|
|
531
|
+
// reactive. This avoids any confusion with signals tracking or not tracking depending on
|
|
532
|
+
// which side of the `await` they are.
|
|
533
|
+
const stream = await untracked(() => {
|
|
534
|
+
return this.loaderFn({
|
|
535
|
+
request: extRequest.request,
|
|
536
|
+
abortSignal,
|
|
537
|
+
previous: {
|
|
538
|
+
status: previousStatus,
|
|
539
|
+
},
|
|
540
|
+
});
|
|
541
|
+
});
|
|
542
|
+
// If this request has been aborted, or the current request no longer
|
|
543
|
+
// matches this load, then we should ignore this resolution.
|
|
544
|
+
if (abortSignal.aborted || untracked(this.extRequest) !== extRequest) {
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
this.state.set({
|
|
548
|
+
extRequest,
|
|
549
|
+
status: ResourceStatus.Resolved,
|
|
550
|
+
previousStatus: ResourceStatus.Resolved,
|
|
551
|
+
stream,
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
catch (err) {
|
|
555
|
+
if (abortSignal.aborted || untracked(this.extRequest) !== extRequest) {
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
this.state.set({
|
|
559
|
+
extRequest,
|
|
560
|
+
status: ResourceStatus.Resolved,
|
|
561
|
+
previousStatus: ResourceStatus.Error,
|
|
562
|
+
stream: signal({ error: err }),
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
finally {
|
|
566
|
+
// Resolve the pending task now that the resource has a value.
|
|
567
|
+
resolvePendingTask?.();
|
|
568
|
+
resolvePendingTask = undefined;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
abortInProgressLoad() {
|
|
572
|
+
untracked(() => this.pendingController?.abort());
|
|
573
|
+
this.pendingController = undefined;
|
|
574
|
+
// Once the load is aborted, we no longer want to block stability on its resolution.
|
|
575
|
+
this.resolvePendingTask?.();
|
|
576
|
+
this.resolvePendingTask = undefined;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Wraps an equality function to handle either value being `undefined`.
|
|
581
|
+
*/
|
|
582
|
+
function wrapEqualityFn(equal) {
|
|
583
|
+
return (a, b) => (a === undefined || b === undefined ? a === b : equal(a, b));
|
|
584
|
+
}
|
|
585
|
+
function getLoader(options) {
|
|
586
|
+
if (isStreamingResourceOptions(options)) {
|
|
587
|
+
return options.stream;
|
|
588
|
+
}
|
|
589
|
+
return async (params) => {
|
|
590
|
+
try {
|
|
591
|
+
return signal({ value: await options.loader(params) });
|
|
592
|
+
}
|
|
593
|
+
catch (err) {
|
|
594
|
+
return signal({ error: err });
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
function isStreamingResourceOptions(options) {
|
|
599
|
+
return !!options.stream;
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Project from a state with `ResourceInternalStatus` to the user-facing `ResourceStatus`
|
|
603
|
+
*/
|
|
604
|
+
function projectStatusOfState(state) {
|
|
605
|
+
switch (state.status) {
|
|
606
|
+
case ResourceStatus.Loading:
|
|
607
|
+
return state.extRequest.reload === 0 ? ResourceStatus.Loading : ResourceStatus.Reloading;
|
|
608
|
+
case ResourceStatus.Resolved:
|
|
609
|
+
return isResolved(untracked(state.stream)) ? ResourceStatus.Resolved : ResourceStatus.Error;
|
|
610
|
+
default:
|
|
611
|
+
return state.status;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
function isResolved(state) {
|
|
615
|
+
return state.error === undefined;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
export { OutputEmitterRef, ResourceImpl, ResourceStatus, computed, effect, getOutputDestroyRef, linkedSignal, resource, untracked };
|
|
619
|
+
//# sourceMappingURL=resource-CPPwEcg7.mjs.map
|