signal inputs seem to not work with `numberAttribute` (generic is needed, or needs inference)
Which @angular/* package(s) are the source of the bug?
core
Is this a regression?
Yes
Description
@Directive({
selector: 'auto-focus',
standalone: true,
})
export class AutoFocus {
delay = input<number>(300, { transform: numberAttribute });
}
Throw error:
Error in src/main.ts (10:32)
Type '(value: unknown, fallbackValue?: number | undefined) => number' is not assignable to type 'undefined'.
Please provide a link to a minimal reproduction of the bug
https://stackblitz.com/edit/stackblitz-starters-fv1yrc?file=src%2Fmain.ts
Please provide the exception or error you saw
No response
Please provide the environment you discovered this bug in (run ng version)
Angular CLI: 17.1.0
Node: 18.16.0
Package Manager: yarn 4.0.2
OS: darwin x64
Angular: 17.1.0
... animations, cdk, cli, common, compiler, compiler-cli, core
... elements, forms, language-service, platform-browser
... platform-browser-dynamic, platform-server, router
... service-worker, ssr
Package Version
---------------------------------------------------------
@angular-devkit/architect 0.1701.0
@angular-devkit/build-angular 17.1.0
@angular-devkit/core 17.1.0
@angular-devkit/schematics 17.1.0
@schematics/angular 17.1.0
ng-packagr 17.1.0
rxjs 7.8.1
typescript 5.3.3
zone.js 0.14.3
Anything else?
No response
This is a bit of a gotcha, it currently needs to be
input<number, unknown>(300, { transform: numberAttribute });
The reason is that a transform introduces a divergence in the signal's type: it accepts unknown value whereas it contains number value.
I am not sure if this can be improved upon, as TypeScript currently requires all generic types to be set instead of inferring some of them (microsoft/TypeScript#54047)
Also, in this particular case, you can opt not to specify any type arguments at all. In that case everything is fully inferred. It's only the partial inference that doesn't work.
~Still, the WriteT being the first type parameter may be undesirable if partial type inference ever becomes a thing.~ <- This was inaccurate, thanks Paul!
Thanks Joost for capturing all of this. We did consider this aspect and talked about it a couple of times. We don't see a good way around this, without other more impactful trade-offs. One correction, the type needs to be:
delay = input<number, unknown>(300, { transform: numberAttribute });
The first type parameter is actually the ReadT (what you as the author of the directive are dealing with), while the second parameter is the WriteT (i.e. what values can be bound to the input, and the transform converts to ReadT).
We are good, if TS supports partial type inference, the following will work as well. Right now, either need to be explicit, or fully rely on inference (which is safe here):
// will works when TS supports partial type inference
delay = input<number>(300, { transform: numberAttribute });
// works right now
delay = input<number, unknown>(300, { transform: numberAttribute });
delay = input(300, { transform: numberAttribute });