Skip to content

WritableResource composition #71089

Description

@nathan-prijot

Which @angular/* package(s) are relevant/related to the feature request?

core

Description

This stems a bit from the thing discussed in 58602.

I want to make my (in this case) an rxResource with the withPreviousValue. We have the example on how to do it at : Composing resources with snapshots.

The problem is that insead of having a ResourceRef, you get a Resource which is not writable. So you don't actually get the complete resource with that new behavior, you just gets part of it.

You can also have a custom interface and recompose some set into it. But it's just such a sad solution when I just when to specify a behavior on the ResourceRef.

Proposed solution

I would be really great if we had a way to compose a ResourceRef/WritableResource. Maybe something like writableResourceFromSnapshots() or resourceRefFromSnapshots() where you pass the snapshot and the original resource to be able to write it.

However, I get how that could be a bit of a confusing behavior, it would have been obviously better if we could handle the reset or not of undefined on the source resource.

Alternatives considered

This very dirty alternative that works fine but uses such hoops for a behavior that I feel is really missing from the Resource.

export function withPreviousValue<T>(
  input: ResourceRef<T>,
  options?: {
    when?: (previous: T) => boolean;
  }
): ResourceRef<T> {
  const derived = linkedSignal<ResourceSnapshot<T>, ResourceSnapshot<T>>({
    source: input.snapshot,
    computation: (snap, previous) => {
      if (
        snap.status === 'loading' &&
        previous &&
        previous.value.status !== 'error' &&
        (!options?.when || options.when(previous.value.value))
      ) {
        // When the input resource enters loading state, we keep the value
        // from its previous state, if any.
        return { status: 'loading' as const, value: previous.value.value };
      }
      // Otherwise we simply forward the state of the input resource.
      return snap;
    },
  });

  const readOnly = resourceFromSnapshots(derived);
  const value = linkedSignal(readOnly.value);
  value.set = input.set.bind(input);
  value.update = input.update.bind(input);
  value.asReadonly = () => readOnly.value;
  return {
    value,
    status: readOnly.status,
    error: readOnly.error,
    isLoading: readOnly.isLoading,
    snapshot: readOnly.snapshot,
    hasValue: readOnly.hasValue.bind(readOnly) as ResourceRef<T>['hasValue'],
    set: input.set.bind(input),
    update: input.update.bind(input),
    reload: input.reload.bind(input),
    destroy: input.destroy.bind(input),
    asReadonly: () => readOnly,
  };
}

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: coreIssues related to the framework runtimegemini-triagedLabel noting that an issue has been triaged by gemini

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions