Repository: angular/angular
File: packages/common/http/src/resource.ts (TypeScript)

Code before:
 *
 * This is used to parse the response appropriately before returning it to
 * the requestee.
 */
type ResponseType = 'arraybuffer' | 'blob' | 'json' | 'text';
type RawRequestType = (() => string | undefined) | (() => HttpResourceRequest | undefined);

function makeHttpResourceFn<TRaw>(responseType: ResponseType) {
  return function httpResource<TResult = TRaw>(
    request: RawRequestType,
    options?: HttpResourceOptions<TResult, TRaw>,
  ): HttpResourceRef<TResult> {
    if (ngDevMode && !options?.injector) {
      assertInInjectionContext(httpResource);
    }
    const injector = options?.injector ?? inject(Injector);

    let defaultValue: TResult | undefined = options?.defaultValue;
    const cacheOptions = injector.get(CACHE_OPTIONS, null, {optional: true});
    const transferState = injector.get(TransferState, null, {optional: true});
    const originMap = injector.get(HTTP_TRANSFER_CACHE_ORIGIN_MAP, null, {optional: true});

    let initialStream: Signal<ResourceStreamItem<TResult>> | undefined;

    if (cacheOptions && transferState) {
      const req = untracked(() => normalizeRequest(request, responseType));
      if (req) {
        const cachedResponse = StateFromCache(req, cacheOptions, transferState, originMap);
        if (cachedResponse) {
          try {
            const body = cachedResponse.body as TRaw;
            defaultValue = options?.parse ? options.parse(body) : (body as unknown as TResult);
            initialStream = signal({value: defaultValue});
          } catch (e) {
            if (typeof ngDevMode === 'undefined' || ngDevMode) {
              console.warn(
                `Angular detected an error while parsing the cached response for the httpResource at \`${req.url}\`. ` +
                  `The resource will fall back to its default value and try again asynchronously.`,
                e,
              );
            }
          }
        }
      }
    }

    return new HttpResourceImpl(
      injector,
      () => normalizeRequest(request, responseType),
      defaultValue as TResult,
      options?.debugName,

Review comment (bug):
This evaluates the `request` at creation time of the `httpResource`. If the request depends on things like `input()` signals, it will attempt to read them before change detection sets their value, causing a crash.

The whole calculation of the initial value for transfer state needs to be deferred / pushed into the resource's internal state `linkedSignal`.

We should also have a test for this :)

Code after:
})();

/**
 * The expected response type of the server.
 *
 * This is used to parse the response appropriately before returning it to
 * the requestee.
 */
type ResponseType = 'arraybuffer' | 'blob' | 'json' | 'text';
type RawRequestType = (() => string | undefined) | (() => HttpResourceRequest | undefined);

function makeHttpResourceFn<TRaw>(responseType: ResponseType) {
  return function httpResource<TResult = TRaw>(
    request: RawRequestType,
    options?: HttpResourceOptions<TResult, TRaw>,
  ): HttpResourceRef<TResult> {
    if (ngDevMode && !options?.injector) {
      assertInInjectionContext(httpResource);
    }
    const injector = options?.injector ?? inject(Injector);

    const cacheOptions = injector.get(CACHE_OPTIONS, null, {optional: true});
    const transferState = injector.get(TransferState, null, {optional: true});
    const originMap = injector.get(HTTP_TRANSFER_CACHE_ORIGIN_MAP, null, {optional: true});

    const getInitialStream = (req: HttpRequest<unknown> | undefined) => {
      if (cacheOptions && transferState && req) {
        const cachedResponse = retrieveStateFromCache(req, cacheOptions, transferState, originMap);
        if (cachedResponse) {
          try {
            const body = cachedResponse.body as TRaw;
            const parsed = options?.parse ? options.parse(body) : (body as unknown as TResult);
            return signal({value: parsed});
          } catch (e) {
            if (typeof ngDevMode === 'undefined' || ngDevMode) {
              console.warn(
                `Angular detected an error while parsing the cached response for the httpResource at \`${req.url}\`. ` +
                  `The resource will fall back to its default value and try again asynchronously.`,
                e,
              );
            }
          }
        }
      }
      return undefined;
    };

    return new HttpResourceImpl(
      injector,
      () => normalizeRequest(request, responseType),
      options?.defaultValue as TResult,