From 603d2d714da1d3ed7bc0fca2f043599747a336a3 Mon Sep 17 00:00:00 2001 From: suraj Date: Mon, 13 Apr 2026 19:33:28 +0530 Subject: [PATCH] fix(core): derive possible null value in rxResource stream params When an optional params function returns undefined, Angular's rxResource internally defaults the params loader value to null at runtime. This change updates `ResourceLoaderParams` to correctly allow `null` alongside the resolved type, ensuring developers get accurate TypeScript feedback when omitted. Fixes #62724 --- .../rxjs-interop/test/rx_resource_spec.ts | 47 +++++++++++++++++-- packages/core/src/resource/api.ts | 2 +- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/packages/core/rxjs-interop/test/rx_resource_spec.ts b/packages/core/rxjs-interop/test/rx_resource_spec.ts index 143d3fa351ba..adc7b74c1cfa 100644 --- a/packages/core/rxjs-interop/test/rx_resource_spec.ts +++ b/packages/core/rxjs-interop/test/rx_resource_spec.ts @@ -29,10 +29,10 @@ describe('rxResource()', () => { const appRef = TestBed.inject(ApplicationRef); const request = signal(1); let unsub = false; - let lastSeenRequest: number = 0; + let lastSeenRequest: number | null = 0; rxResource({ params: request, - stream: ({params: request}) => { + stream: ({ params: request }) => { lastSeenRequest = request; return new Observable((sub) => { if (request === 2) { @@ -75,7 +75,7 @@ describe('rxResource()', () => { expect(res.value()).toBe(3); response.error('fail'); - expect(res.error()).toEqual(jasmine.objectContaining({cause: 'fail'})); + expect(res.error()).toEqual(jasmine.objectContaining({ cause: 'fail' })); expect(res.error()!.message).toContain('Resource'); }); @@ -114,6 +114,47 @@ describe('rxResource()', () => { expect(() => rxRes.value()).toThrowError(/This is a FooError/); }); + + it('should receive null params at runtime when params option is not provided', async () => { + const injector = TestBed.inject(Injector); + const appRef = TestBed.inject(ApplicationRef); + let receivedParams: unknown = 'NOT_SET'; + + const res = rxResource({ + // No `params` option — defaults to () => null! internally + stream: ({ params }) => { + receivedParams = params; + return of('result'); + }, + injector, + }); + + await appRef.whenStable(); + // When no params function is provided, the resource defaults to `() => null!` internally, + // so params inside the stream callback should be null at runtime. + expect(receivedParams).toBeNull(); + expect(res.value()).toBe('result'); + }); + + it('should type params as including null when params option can be undefined (issue #62724)', async () => { + const injector = TestBed.inject(Injector); + + // This should compile without TypeScript errors. + // Before the fix, `params` was typed as `string` — missing `null`. + // After the fix, `params` is typed as `string | null`. + const getParamsFn = (): (() => string) | undefined => undefined; + + // The key assertion: assigning `params` to `string | null` must compile. + rxResource({ + params: getParamsFn(), + stream: ({ params }) => { + // TypeScript must allow: params is `string | null`, not just `string` + const _typeCheck: string | null = params; + return of(_typeCheck ?? 'fallback'); + }, + injector, + }); + }); }); async function waitFor(fn: () => boolean): Promise { diff --git a/packages/core/src/resource/api.ts b/packages/core/src/resource/api.ts index b4333239a0d5..0181435b12d3 100644 --- a/packages/core/src/resource/api.ts +++ b/packages/core/src/resource/api.ts @@ -179,7 +179,7 @@ export interface ResourceRef extends WritableResource { * @experimental */ export interface ResourceLoaderParams { - params: NoInfer>; + params: NoInfer | null>; abortSignal: AbortSignal; previous: { status: ResourceStatus;