[Angular 19] injectStore() runs before input signals are set.
Describe the bug
injectStore() runs before input signals are set which makes it throws an error at runtime if the form is provided as an input signal.
main.ts:8 ERROR RuntimeError: NG0950: Input is required but no value is available yet
Your minimal, reproducible example
https://codesandbox.io/p/devbox/q9w9mv
Steps to reproduce
Open the shared link and see the error in the web dev tools.
The error is caused by the following code:
isValid = injectStore(
this.form(),
(state) => state.fieldMeta[this.name()].errors.length === 0
);
In the component: FirstNameComponent
Expected behavior
The injectStore should call the business logic after input signals are set like Angular Query does.
How often does this bug happen?
Every time
Screenshots or Videos
No response
Platform
- OS: MacOS
- Browser: Safari
TanStack Form adapter
None
TanStack Form version
@tanstack/angular-form v1.3.0
TypeScript version
v5.8.3
Additional context
No response
This is a common problem with Angular signals. Field initialization runs at "constructor" time, while inputs are set after the component is constructed. So you cannot read an input signal in field initialization. The usual workaround is to defer the injection (injectStore in this case) to ngOnInit providing the injector (injected at construction time). There may be other ways.
The reading of your form signal happens outside (before) injectStore.
These are two possible solutions, both with tradeoffs.
readonly #injector = inject(Injector);
// Non-Null-Assertion operator :(
// You have to be careful to not access this property too early
isValid!: Signal<boolean>;
ngOnInit(): void {
this.isValid = runInInjectionContext(this.#injector, () =>
injectStore(
this.form(),
(state) => state.fieldMeta[this.name()].errors.length === 0
)
);
}
readonly #injector = inject(Injector);
// Use nested signals
isValid = computed(() => {
const form = this.form();
const $isValid = runInInjectionContext(this.#injector, () =>
injectStore(
form,
(state) => state.fieldMeta[this.name()].errors.length === 0
)
);
return $isValid();
});
Possible enhancement for injectStore: accept an Injector, so we don't have to use runInInjectionContext.
It's not an uncommon pattern in Angular "inject" functions.
(the injectStore function in @tanstack/store package already supports this)