626 lines
23 KiB
JavaScript
Executable File
626 lines
23 KiB
JavaScript
Executable File
/**
|
|
* @license Angular v20.3.11
|
|
* (c) 2010-2025 Google LLC. https://angular.dev/
|
|
* License: MIT
|
|
*/
|
|
|
|
import { inject, ErrorHandler, DestroyRef, RuntimeError, formatRuntimeError, assertNotInReactiveContext, assertInInjectionContext, Injector, ViewContext, ChangeDetectionScheduler, EffectScheduler, setInjectorProfilerContext, emitEffectCreatedEvent, EFFECTS, NodeInjectorDestroyRef, FLAGS, markAncestorsForTraversal, noop, setIsRefreshingViews, signalAsReadonlyFn, PendingTasks, signal } from './root_effect_scheduler.mjs';
|
|
import { setActiveConsumer, createComputed, SIGNAL, consumerDestroy, isInNotificationPhase } from './signal.mjs';
|
|
import { untracked as untracked$1, BASE_EFFECT_NODE, runEffect, createLinkedSignal, linkedSignalSetFn, linkedSignalUpdateFn } from './effect.mjs';
|
|
|
|
/**
|
|
* An `OutputEmitterRef` is created by the `output()` function and can be
|
|
* used to emit values to consumers of your directive or component.
|
|
*
|
|
* Consumers of your directive/component can bind to the output and
|
|
* subscribe to changes via the bound event syntax. For example:
|
|
*
|
|
* ```html
|
|
* <my-comp (valueChange)="processNewValue($event)" />
|
|
* ```
|
|
*
|
|
* @publicAPI
|
|
*/
|
|
class OutputEmitterRef {
|
|
destroyed = false;
|
|
listeners = null;
|
|
errorHandler = inject(ErrorHandler, { optional: true });
|
|
/** @internal */
|
|
destroyRef = inject(DestroyRef);
|
|
constructor() {
|
|
// Clean-up all listeners and mark as destroyed upon destroy.
|
|
this.destroyRef.onDestroy(() => {
|
|
this.destroyed = true;
|
|
this.listeners = null;
|
|
});
|
|
}
|
|
subscribe(callback) {
|
|
if (this.destroyed) {
|
|
throw new RuntimeError(953 /* RuntimeErrorCode.OUTPUT_REF_DESTROYED */, ngDevMode &&
|
|
'Unexpected subscription to destroyed `OutputRef`. ' +
|
|
'The owning directive/component is destroyed.');
|
|
}
|
|
(this.listeners ??= []).push(callback);
|
|
return {
|
|
unsubscribe: () => {
|
|
const idx = this.listeners?.indexOf(callback);
|
|
if (idx !== undefined && idx !== -1) {
|
|
this.listeners?.splice(idx, 1);
|
|
}
|
|
},
|
|
};
|
|
}
|
|
/** Emits a new value to the output. */
|
|
emit(value) {
|
|
if (this.destroyed) {
|
|
console.warn(formatRuntimeError(953 /* RuntimeErrorCode.OUTPUT_REF_DESTROYED */, ngDevMode &&
|
|
'Unexpected emit for destroyed `OutputRef`. ' +
|
|
'The owning directive/component is destroyed.'));
|
|
return;
|
|
}
|
|
if (this.listeners === null) {
|
|
return;
|
|
}
|
|
const previousConsumer = setActiveConsumer(null);
|
|
try {
|
|
for (const listenerFn of this.listeners) {
|
|
try {
|
|
listenerFn(value);
|
|
}
|
|
catch (err) {
|
|
this.errorHandler?.handleError(err);
|
|
}
|
|
}
|
|
}
|
|
finally {
|
|
setActiveConsumer(previousConsumer);
|
|
}
|
|
}
|
|
}
|
|
/** Gets the owning `DestroyRef` for the given output. */
|
|
function getOutputDestroyRef(ref) {
|
|
return ref.destroyRef;
|
|
}
|
|
|
|
/**
|
|
* Execute an arbitrary function in a non-reactive (non-tracking) context. The executed function
|
|
* can, optionally, return a value.
|
|
*/
|
|
function untracked(nonReactiveReadsFn) {
|
|
return untracked$1(nonReactiveReadsFn);
|
|
}
|
|
|
|
/**
|
|
* Create a computed `Signal` which derives a reactive value from an expression.
|
|
*/
|
|
function computed(computation, options) {
|
|
const getter = createComputed(computation, options?.equal);
|
|
if (ngDevMode) {
|
|
getter.toString = () => `[Computed: ${getter()}]`;
|
|
getter[SIGNAL].debugName = options?.debugName;
|
|
}
|
|
return getter;
|
|
}
|
|
|
|
class EffectRefImpl {
|
|
[SIGNAL];
|
|
constructor(node) {
|
|
this[SIGNAL] = node;
|
|
}
|
|
destroy() {
|
|
this[SIGNAL].destroy();
|
|
}
|
|
}
|
|
/**
|
|
* Registers an "effect" that will be scheduled & executed whenever the signals that it reads
|
|
* changes.
|
|
*
|
|
* Angular has two different kinds of effect: component effects and root effects. Component effects
|
|
* are created when `effect()` is called from a component, directive, or within a service of a
|
|
* component/directive. Root effects are created when `effect()` is called from outside the
|
|
* component tree, such as in a root service.
|
|
*
|
|
* The two effect types differ in their timing. Component effects run as a component lifecycle
|
|
* event during Angular's synchronization (change detection) process, and can safely read input
|
|
* signals or create/destroy views that depend on component state. Root effects run as microtasks
|
|
* and have no connection to the component tree or change detection.
|
|
*
|
|
* `effect()` must be run in injection context, unless the `injector` option is manually specified.
|
|
*
|
|
* @publicApi 20.0
|
|
*/
|
|
function effect(effectFn, options) {
|
|
ngDevMode &&
|
|
assertNotInReactiveContext(effect, 'Call `effect` outside of a reactive context. For example, schedule the ' +
|
|
'effect inside the component constructor.');
|
|
if (ngDevMode && !options?.injector) {
|
|
assertInInjectionContext(effect);
|
|
}
|
|
if (ngDevMode && options?.allowSignalWrites !== undefined) {
|
|
console.warn(`The 'allowSignalWrites' flag is deprecated and no longer impacts effect() (writes are always allowed)`);
|
|
}
|
|
const injector = options?.injector ?? inject(Injector);
|
|
let destroyRef = options?.manualCleanup !== true ? injector.get(DestroyRef) : null;
|
|
let node;
|
|
const viewContext = injector.get(ViewContext, null, { optional: true });
|
|
const notifier = injector.get(ChangeDetectionScheduler);
|
|
if (viewContext !== null) {
|
|
// This effect was created in the context of a view, and will be associated with the view.
|
|
node = createViewEffect(viewContext.view, notifier, effectFn);
|
|
if (destroyRef instanceof NodeInjectorDestroyRef && destroyRef._lView === viewContext.view) {
|
|
// The effect is being created in the same view as the `DestroyRef` references, so it will be
|
|
// automatically destroyed without the need for an explicit `DestroyRef` registration.
|
|
destroyRef = null;
|
|
}
|
|
}
|
|
else {
|
|
// This effect was created outside the context of a view, and will be scheduled independently.
|
|
node = createRootEffect(effectFn, injector.get(EffectScheduler), notifier);
|
|
}
|
|
node.injector = injector;
|
|
if (destroyRef !== null) {
|
|
// If we need to register for cleanup, do that here.
|
|
node.onDestroyFn = destroyRef.onDestroy(() => node.destroy());
|
|
}
|
|
const effectRef = new EffectRefImpl(node);
|
|
if (ngDevMode) {
|
|
node.debugName = options?.debugName ?? '';
|
|
const prevInjectorProfilerContext = setInjectorProfilerContext({ injector, token: null });
|
|
try {
|
|
emitEffectCreatedEvent(effectRef);
|
|
}
|
|
finally {
|
|
setInjectorProfilerContext(prevInjectorProfilerContext);
|
|
}
|
|
}
|
|
return effectRef;
|
|
}
|
|
const EFFECT_NODE =
|
|
/* @__PURE__ */ (() => ({
|
|
...BASE_EFFECT_NODE,
|
|
cleanupFns: undefined,
|
|
zone: null,
|
|
onDestroyFn: noop,
|
|
run() {
|
|
if (ngDevMode && isInNotificationPhase()) {
|
|
throw new Error(`Schedulers cannot synchronously execute watches while scheduling.`);
|
|
}
|
|
// We clear `setIsRefreshingViews` so that `markForCheck()` within the body of an effect will
|
|
// cause CD to reach the component in question.
|
|
const prevRefreshingViews = setIsRefreshingViews(false);
|
|
try {
|
|
runEffect(this);
|
|
}
|
|
finally {
|
|
setIsRefreshingViews(prevRefreshingViews);
|
|
}
|
|
},
|
|
cleanup() {
|
|
if (!this.cleanupFns?.length) {
|
|
return;
|
|
}
|
|
const prevConsumer = setActiveConsumer(null);
|
|
try {
|
|
// Attempt to run the cleanup functions. Regardless of failure or success, we consider
|
|
// cleanup "completed" and clear the list for the next run of the effect. Note that an error
|
|
// from the cleanup function will still crash the current run of the effect.
|
|
while (this.cleanupFns.length) {
|
|
this.cleanupFns.pop()();
|
|
}
|
|
}
|
|
finally {
|
|
this.cleanupFns = [];
|
|
setActiveConsumer(prevConsumer);
|
|
}
|
|
},
|
|
}))();
|
|
const ROOT_EFFECT_NODE =
|
|
/* @__PURE__ */ (() => ({
|
|
...EFFECT_NODE,
|
|
consumerMarkedDirty() {
|
|
this.scheduler.schedule(this);
|
|
this.notifier.notify(12 /* NotificationSource.RootEffect */);
|
|
},
|
|
destroy() {
|
|
consumerDestroy(this);
|
|
this.onDestroyFn();
|
|
this.cleanup();
|
|
this.scheduler.remove(this);
|
|
},
|
|
}))();
|
|
const VIEW_EFFECT_NODE =
|
|
/* @__PURE__ */ (() => ({
|
|
...EFFECT_NODE,
|
|
consumerMarkedDirty() {
|
|
this.view[FLAGS] |= 8192 /* LViewFlags.HasChildViewsToRefresh */;
|
|
markAncestorsForTraversal(this.view);
|
|
this.notifier.notify(13 /* NotificationSource.ViewEffect */);
|
|
},
|
|
destroy() {
|
|
consumerDestroy(this);
|
|
this.onDestroyFn();
|
|
this.cleanup();
|
|
this.view[EFFECTS]?.delete(this);
|
|
},
|
|
}))();
|
|
function createViewEffect(view, notifier, fn) {
|
|
const node = Object.create(VIEW_EFFECT_NODE);
|
|
node.view = view;
|
|
node.zone = typeof Zone !== 'undefined' ? Zone.current : null;
|
|
node.notifier = notifier;
|
|
node.fn = createEffectFn(node, fn);
|
|
view[EFFECTS] ??= new Set();
|
|
view[EFFECTS].add(node);
|
|
node.consumerMarkedDirty(node);
|
|
return node;
|
|
}
|
|
function createRootEffect(fn, scheduler, notifier) {
|
|
const node = Object.create(ROOT_EFFECT_NODE);
|
|
node.fn = createEffectFn(node, fn);
|
|
node.scheduler = scheduler;
|
|
node.notifier = notifier;
|
|
node.zone = typeof Zone !== 'undefined' ? Zone.current : null;
|
|
node.scheduler.add(node);
|
|
node.notifier.notify(12 /* NotificationSource.RootEffect */);
|
|
return node;
|
|
}
|
|
function createEffectFn(node, fn) {
|
|
return () => {
|
|
fn((cleanupFn) => (node.cleanupFns ??= []).push(cleanupFn));
|
|
};
|
|
}
|
|
|
|
const identityFn = (v) => v;
|
|
function linkedSignal(optionsOrComputation, options) {
|
|
if (typeof optionsOrComputation === 'function') {
|
|
const getter = createLinkedSignal(optionsOrComputation, (identityFn), options?.equal);
|
|
return upgradeLinkedSignalGetter(getter, options?.debugName);
|
|
}
|
|
else {
|
|
const getter = createLinkedSignal(optionsOrComputation.source, optionsOrComputation.computation, optionsOrComputation.equal);
|
|
return upgradeLinkedSignalGetter(getter, optionsOrComputation.debugName);
|
|
}
|
|
}
|
|
function upgradeLinkedSignalGetter(getter, debugName) {
|
|
if (ngDevMode) {
|
|
getter.toString = () => `[LinkedSignal: ${getter()}]`;
|
|
getter[SIGNAL].debugName = debugName;
|
|
}
|
|
const node = getter[SIGNAL];
|
|
const upgradedGetter = getter;
|
|
upgradedGetter.set = (newValue) => linkedSignalSetFn(node, newValue);
|
|
upgradedGetter.update = (updateFn) => linkedSignalUpdateFn(node, updateFn);
|
|
upgradedGetter.asReadonly = signalAsReadonlyFn.bind(getter);
|
|
return upgradedGetter;
|
|
}
|
|
|
|
/**
|
|
* Whether a `Resource.value()` should throw an error when the resource is in the error state.
|
|
*
|
|
* This internal flag is being used to gradually roll out this behavior.
|
|
*/
|
|
let RESOURCE_VALUE_THROWS_ERRORS_DEFAULT = true;
|
|
function resource(options) {
|
|
if (ngDevMode && !options?.injector) {
|
|
assertInInjectionContext(resource);
|
|
}
|
|
const oldNameForParams = options.request;
|
|
const params = (options.params ?? oldNameForParams ?? (() => null));
|
|
return new ResourceImpl(params, getLoader(options), options.defaultValue, options.equal ? wrapEqualityFn(options.equal) : undefined, options.injector ?? inject(Injector), RESOURCE_VALUE_THROWS_ERRORS_DEFAULT);
|
|
}
|
|
/**
|
|
* Base class which implements `.value` as a `WritableSignal` by delegating `.set` and `.update`.
|
|
*/
|
|
class BaseWritableResource {
|
|
value;
|
|
constructor(value) {
|
|
this.value = value;
|
|
this.value.set = this.set.bind(this);
|
|
this.value.update = this.update.bind(this);
|
|
this.value.asReadonly = signalAsReadonlyFn;
|
|
}
|
|
isError = computed(() => this.status() === 'error');
|
|
update(updateFn) {
|
|
this.set(updateFn(untracked(this.value)));
|
|
}
|
|
isLoading = computed(() => this.status() === 'loading' || this.status() === 'reloading');
|
|
// Use a computed here to avoid triggering reactive consumers if the value changes while staying
|
|
// either defined or undefined.
|
|
isValueDefined = computed(() => {
|
|
// Check if it's in an error state first to prevent the error from bubbling up.
|
|
if (this.isError()) {
|
|
return false;
|
|
}
|
|
return this.value() !== undefined;
|
|
});
|
|
hasValue() {
|
|
return this.isValueDefined();
|
|
}
|
|
asReadonly() {
|
|
return this;
|
|
}
|
|
}
|
|
/**
|
|
* Implementation for `resource()` which uses a `linkedSignal` to manage the resource's state.
|
|
*/
|
|
class ResourceImpl extends BaseWritableResource {
|
|
loaderFn;
|
|
equal;
|
|
pendingTasks;
|
|
/**
|
|
* The current state of the resource. Status, value, and error are derived from this.
|
|
*/
|
|
state;
|
|
/**
|
|
* Combines the current request with a reload counter which allows the resource to be reloaded on
|
|
* imperative command.
|
|
*/
|
|
extRequest;
|
|
effectRef;
|
|
pendingController;
|
|
resolvePendingTask = undefined;
|
|
destroyed = false;
|
|
unregisterOnDestroy;
|
|
constructor(request, loaderFn, defaultValue, equal, injector, throwErrorsFromValue = RESOURCE_VALUE_THROWS_ERRORS_DEFAULT) {
|
|
super(
|
|
// Feed a computed signal for the value to `BaseWritableResource`, which will upgrade it to a
|
|
// `WritableSignal` that delegates to `ResourceImpl.set`.
|
|
computed(() => {
|
|
const streamValue = this.state().stream?.();
|
|
if (!streamValue) {
|
|
return defaultValue;
|
|
}
|
|
// Prevents `hasValue()` from throwing an error when a reload happened in the error state
|
|
if (this.state().status === 'loading' && this.error()) {
|
|
return defaultValue;
|
|
}
|
|
if (!isResolved(streamValue)) {
|
|
if (throwErrorsFromValue) {
|
|
throw new ResourceValueError(this.error());
|
|
}
|
|
else {
|
|
return defaultValue;
|
|
}
|
|
}
|
|
return streamValue.value;
|
|
}, { equal }));
|
|
this.loaderFn = loaderFn;
|
|
this.equal = equal;
|
|
// Extend `request()` to include a writable reload signal.
|
|
this.extRequest = linkedSignal({
|
|
source: request,
|
|
computation: (request) => ({ request, reload: 0 }),
|
|
});
|
|
// The main resource state is managed in a `linkedSignal`, which allows the resource to change
|
|
// state instantaneously when the request signal changes.
|
|
this.state = linkedSignal({
|
|
// Whenever the request changes,
|
|
source: this.extRequest,
|
|
// Compute the state of the resource given a change in status.
|
|
computation: (extRequest, previous) => {
|
|
const status = extRequest.request === undefined ? 'idle' : 'loading';
|
|
if (!previous) {
|
|
return {
|
|
extRequest,
|
|
status,
|
|
previousStatus: 'idle',
|
|
stream: undefined,
|
|
};
|
|
}
|
|
else {
|
|
return {
|
|
extRequest,
|
|
status,
|
|
previousStatus: projectStatusOfState(previous.value),
|
|
// If the request hasn't changed, keep the previous stream.
|
|
stream: previous.value.extRequest.request === extRequest.request
|
|
? previous.value.stream
|
|
: undefined,
|
|
};
|
|
}
|
|
},
|
|
});
|
|
this.effectRef = effect(this.loadEffect.bind(this), {
|
|
injector,
|
|
manualCleanup: true,
|
|
});
|
|
this.pendingTasks = injector.get(PendingTasks);
|
|
// Cancel any pending request when the resource itself is destroyed.
|
|
this.unregisterOnDestroy = injector.get(DestroyRef).onDestroy(() => this.destroy());
|
|
}
|
|
status = computed(() => projectStatusOfState(this.state()));
|
|
error = computed(() => {
|
|
const stream = this.state().stream?.();
|
|
return stream && !isResolved(stream) ? stream.error : undefined;
|
|
});
|
|
/**
|
|
* Called either directly via `WritableResource.set` or via `.value.set()`.
|
|
*/
|
|
set(value) {
|
|
if (this.destroyed) {
|
|
return;
|
|
}
|
|
const error = untracked(this.error);
|
|
const state = untracked(this.state);
|
|
if (!error) {
|
|
const current = untracked(this.value);
|
|
if (state.status === 'local' &&
|
|
(this.equal ? this.equal(current, value) : current === value)) {
|
|
return;
|
|
}
|
|
}
|
|
// Enter Local state with the user-defined value.
|
|
this.state.set({
|
|
extRequest: state.extRequest,
|
|
status: 'local',
|
|
previousStatus: 'local',
|
|
stream: signal({ value }),
|
|
});
|
|
// We're departing from whatever state the resource was in previously, so cancel any in-progress
|
|
// loading operations.
|
|
this.abortInProgressLoad();
|
|
}
|
|
reload() {
|
|
// We don't want to restart in-progress loads.
|
|
const { status } = untracked(this.state);
|
|
if (status === 'idle' || status === 'loading') {
|
|
return false;
|
|
}
|
|
// Increment the request reload to trigger the `state` linked signal to switch us to `Reload`
|
|
this.extRequest.update(({ request, reload }) => ({ request, reload: reload + 1 }));
|
|
return true;
|
|
}
|
|
destroy() {
|
|
this.destroyed = true;
|
|
this.unregisterOnDestroy();
|
|
this.effectRef.destroy();
|
|
this.abortInProgressLoad();
|
|
// Destroyed resources enter Idle state.
|
|
this.state.set({
|
|
extRequest: { request: undefined, reload: 0 },
|
|
status: 'idle',
|
|
previousStatus: 'idle',
|
|
stream: undefined,
|
|
});
|
|
}
|
|
async loadEffect() {
|
|
const extRequest = this.extRequest();
|
|
// Capture the previous status before any state transitions. Note that this is `untracked` since
|
|
// we do not want the effect to depend on the state of the resource, only on the request.
|
|
const { status: currentStatus, previousStatus } = untracked(this.state);
|
|
if (extRequest.request === undefined) {
|
|
// Nothing to load (and we should already be in a non-loading state).
|
|
return;
|
|
}
|
|
else if (currentStatus !== 'loading') {
|
|
// We're not in a loading or reloading state, so this loading request is stale.
|
|
return;
|
|
}
|
|
// Cancel any previous loading attempts.
|
|
this.abortInProgressLoad();
|
|
// Capturing _this_ load's pending task in a local variable is important here. We may attempt to
|
|
// resolve it twice:
|
|
//
|
|
// 1. when the loading function promise resolves/rejects
|
|
// 2. when cancelling the loading operation
|
|
//
|
|
// After the loading operation is cancelled, `this.resolvePendingTask` no longer represents this
|
|
// particular task, but this `await` may eventually resolve/reject. Thus, when we cancel in
|
|
// response to (1) below, we need to cancel the locally saved task.
|
|
let resolvePendingTask = (this.resolvePendingTask =
|
|
this.pendingTasks.add());
|
|
const { signal: abortSignal } = (this.pendingController = new AbortController());
|
|
try {
|
|
// The actual loading is run through `untracked` - only the request side of `resource` is
|
|
// reactive. This avoids any confusion with signals tracking or not tracking depending on
|
|
// which side of the `await` they are.
|
|
const stream = await untracked(() => {
|
|
return this.loaderFn({
|
|
params: extRequest.request,
|
|
// TODO(alxhub): cleanup after g3 removal of `request` alias.
|
|
request: extRequest.request,
|
|
abortSignal,
|
|
previous: {
|
|
status: previousStatus,
|
|
},
|
|
});
|
|
});
|
|
// If this request has been aborted, or the current request no longer
|
|
// matches this load, then we should ignore this resolution.
|
|
if (abortSignal.aborted || untracked(this.extRequest) !== extRequest) {
|
|
return;
|
|
}
|
|
this.state.set({
|
|
extRequest,
|
|
status: 'resolved',
|
|
previousStatus: 'resolved',
|
|
stream,
|
|
});
|
|
}
|
|
catch (err) {
|
|
if (abortSignal.aborted || untracked(this.extRequest) !== extRequest) {
|
|
return;
|
|
}
|
|
this.state.set({
|
|
extRequest,
|
|
status: 'resolved',
|
|
previousStatus: 'error',
|
|
stream: signal({ error: encapsulateResourceError(err) }),
|
|
});
|
|
}
|
|
finally {
|
|
// Resolve the pending task now that the resource has a value.
|
|
resolvePendingTask?.();
|
|
resolvePendingTask = undefined;
|
|
}
|
|
}
|
|
abortInProgressLoad() {
|
|
untracked(() => this.pendingController?.abort());
|
|
this.pendingController = undefined;
|
|
// Once the load is aborted, we no longer want to block stability on its resolution.
|
|
this.resolvePendingTask?.();
|
|
this.resolvePendingTask = undefined;
|
|
}
|
|
}
|
|
/**
|
|
* Wraps an equality function to handle either value being `undefined`.
|
|
*/
|
|
function wrapEqualityFn(equal) {
|
|
return (a, b) => (a === undefined || b === undefined ? a === b : equal(a, b));
|
|
}
|
|
function getLoader(options) {
|
|
if (isStreamingResourceOptions(options)) {
|
|
return options.stream;
|
|
}
|
|
return async (params) => {
|
|
try {
|
|
return signal({ value: await options.loader(params) });
|
|
}
|
|
catch (err) {
|
|
return signal({ error: encapsulateResourceError(err) });
|
|
}
|
|
};
|
|
}
|
|
function isStreamingResourceOptions(options) {
|
|
return !!options.stream;
|
|
}
|
|
/**
|
|
* Project from a state with `ResourceInternalStatus` to the user-facing `ResourceStatus`
|
|
*/
|
|
function projectStatusOfState(state) {
|
|
switch (state.status) {
|
|
case 'loading':
|
|
return state.extRequest.reload === 0 ? 'loading' : 'reloading';
|
|
case 'resolved':
|
|
return isResolved(state.stream()) ? 'resolved' : 'error';
|
|
default:
|
|
return state.status;
|
|
}
|
|
}
|
|
function isResolved(state) {
|
|
return state.error === undefined;
|
|
}
|
|
function encapsulateResourceError(error) {
|
|
if (error instanceof Error) {
|
|
return error;
|
|
}
|
|
return new ResourceWrappedError(error);
|
|
}
|
|
class ResourceValueError extends Error {
|
|
constructor(error) {
|
|
super(ngDevMode
|
|
? `Resource is currently in an error state (see Error.cause for details): ${error.message}`
|
|
: error.message, { cause: error });
|
|
}
|
|
}
|
|
class ResourceWrappedError extends Error {
|
|
constructor(error) {
|
|
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), { cause: error });
|
|
}
|
|
}
|
|
|
|
export { OutputEmitterRef, ResourceImpl, computed, effect, encapsulateResourceError, getOutputDestroyRef, linkedSignal, resource, untracked };
|
|
//# sourceMappingURL=resource.mjs.map
|