Which @angular/* package(s) are the source of the bug?
core
Is this a regression?
Yes
Description
Description
When using resource with a defaultValue and guarding access via hasValue, the resource can become type never in subsequent else if branches on the same variable.
Without a defaultValue, the accompanying undefined in the value type prevents this: e.g. ResourceRef<number | undefined> narrows to ResourceRef<number> in the hasValue branch, but for the other branches remains ResourceRef<number | undefined>.
With a defaultValue, we start from ResourceRef<number>. hasValue stays with the same type, so another branch is impossible and narrows down to never. isLoading or error throws a compilation error.
Using a default value is quite common, especially with arrays ([]) or the null object pattern.
It is not really a bug per se, but not the best DX.
Minimal repro
import { resource } from '@angular/core';
describe('type inference resources', () => {
const loader = () => Promise.resolve(1);
const params = () => 1;
describe('fails when default value is provided', () => {
it('implicit type + default', () => {
const n = resource({ loader, params, defaultValue: 0 });
if (n.hasValue()) {
} else if (n.isLoading()) { // TS error here when defaultValue is present
} else if (n.error()) { // TS error here when defaultValue is present
}
});
it('explicit type + default', () => {
const n = resource<number, number>({ loader, params, defaultValue: 0 });
if (n.hasValue()) {
} else if (n.isLoading()) { // TS error here when defaultValue is present
} else if (n.error()) { // TS error here when defaultValue is present
}
});
});
});
Possible Solutions
If you see potential in one of these options, I’d be happy to create some prototypes (they could also serve as good first issues). My personal favourites are option 1 & 2, depending on how the implementation would look like.
-
Keep the current approach
Developers must remember that when dealing with resources that have default values, they cannot rely on hasValue. Instead, they should directly check the status property or use error/isLoading before calling hasValue. This limitation should be clearly documented.
-
Introduce discriminated types for resource variants
We could define distinct types for each resource state. Something along the lines of:
// Pseudo-code to transport the idea
type ResourceResolvedRef<T> = { status: signal('resolved'); value(): T };
type ResourceLoadingRef = { status: signal('loading') };
type ResourceErrorRef = { status: 'error'; error(): unknown };
type ResourceRef<T> = ResourceResolvedRef<T> | ResourceLoadingRef | ResourceErrorRef;
-
Refine hasValue typing
Similar to option 2, but instead of full discriminators, we could adjust typing so that hasValue is not a type predicate when the resource’s type does not contain undefined. This might be achievable either with conditional types or by introducing additional resource interfaces, depending on which path proves simpler.
Please provide a link to a minimal reproduction of the bug
https://stackblitz.com/github/rainerhahnekamp/bug-resource-has-value
Please provide the exception or error you saw
Typical TypeScript Compilation error for `never`
Please provide the environment you discovered this bug in (run ng version)
Angular CLI: 20.3.1
Node: 22.15.1
Package Manager: pnpm 10.11.0
OS: darwin arm64
Angular: 20.3.0
... common, compiler, compiler-cli, core, forms
... platform-browser, router
Package Version
------------------------------------
@angular-devkit/architect 0.2003.1
@angular-devkit/core 20.3.1
@angular-devkit/schematics 20.3.1
@angular/build 20.3.1
@angular/cli 20.3.1
@schematics/angular 20.3.1
rxjs 7.8.2
typescript 5.9.2
Anything else?
No response
Which @angular/* package(s) are the source of the bug?
core
Is this a regression?
Yes
Description
Description
When using
resourcewith adefaultValueand guarding access viahasValue, the resource can become typeneverin subsequentelse ifbranches on the same variable.Without a
defaultValue, the accompanyingundefinedin the value type prevents this: e.g.ResourceRef<number | undefined>narrows toResourceRef<number>in thehasValuebranch, but for the other branches remainsResourceRef<number | undefined>.With a
defaultValue, we start fromResourceRef<number>.hasValuestays with the same type, so another branch is impossible and narrows down tonever.isLoadingorerrorthrows a compilation error.Using a default value is quite common, especially with arrays (
[]) or the null object pattern.It is not really a bug per se, but not the best DX.
Minimal repro
Possible Solutions
If you see potential in one of these options, I’d be happy to create some prototypes (they could also serve as good first issues). My personal favourites are option 1 & 2, depending on how the implementation would look like.
Keep the current approach
Developers must remember that when dealing with resources that have default values, they cannot rely on
hasValue. Instead, they should directly check thestatusproperty or useerror/isLoadingbefore callinghasValue. This limitation should be clearly documented.Introduce discriminated types for resource variants
We could define distinct types for each resource state. Something along the lines of:
Refine
hasValuetypingSimilar to option 2, but instead of full discriminators, we could adjust typing so that
hasValueis not a type predicate when the resource’s type does not containundefined. This might be achievable either with conditional types or by introducing additional resource interfaces, depending on which path proves simpler.Please provide a link to a minimal reproduction of the bug
https://stackblitz.com/github/rainerhahnekamp/bug-resource-has-value
Please provide the exception or error you saw
Please provide the environment you discovered this bug in (run
ng version)Anything else?
No response