When working with Angular components, you may encounter the NG0950: Input is required but no value is available yet
error.
This error occurs when an @Input()
or input()
property is used before the framework has a value available for it.
Let’s consider the following code:
export class ClientAutocompleteComponent {
private readonly _client = inject(ClientsService);
readonly control = input.required<FormControl>();
readonly clients = toSignal(
this.control().valueChanges.pipe(
takeUntilDestroyed(),
debounceTime(300),
distinctUntilChanged(),
switchMap((query) => this._client.list(query)),
),
);
}
At line 4, we’re using the input.required
method to mark the control
property as required. This means that the control
input must be provided by the parent component.
At line 6, we’re using the control
property to create an observable that fetches a list of clients based on the user’s input.
This code is concise and easy to understand, but if we run it, we’ll get the following error:
NG0950: Input 'control' is required but no value is available yet.
As stated in the official Angular documentation, the trick is to move the clients
property initialization inside the ngOnInit
lifecycle hook.
export class ClientControlComponent implements OnInit {
private readonly _client = inject(ClientsService);
private readonly destroyRef = inject(DestroyRef);
readonly control = input.required<FormControl>();
clients: Signal<Client[] | undefined> | undefined;
ngOnInit(): void {
this.clients = toSignal(
this.control().valueChanges.pipe(
takeUntilDestroyed(this.destroyRef),
debounceTime(300),
distinctUntilChanged(),
switchMap((query) => this._client.list(query)),
),
);
}
}
This change ensures that the control
property is accessed after in the ngOnInit
lifecycle hook, when the value is available.
However, this will raise another error:
NG0203: toSignal() can only be used within an injection context
To fix every error, we need to modify a little bit the template.
<input
#clientInput
matInput
type="text"
[formControl]="control()"
(input)="query.set(clientInput.value)"
placeholder="Search..."
/>
At line 6, we’re using the input
event to update the query
signal when the user types something new.
export class ClientControlComponent {
private readonly _client = inject(ClientsService);
readonly control = input.required<FormControl>();
readonly query = signal<string>('');
readonly clients = toSignal(
toObservable(this.query).pipe(
takeUntilDestroyed(),
debounceTime(300),
distinctUntilChanged(),
tap((query) => console.log('query changed', query)),
switchMap((query) => this._client.list(query)),
),
);
}
At line 5, we introduce a new query
signal that will be updated from the template.
At line 8, we convert the signal to an observable so that we can have a stream of values to work with.
With this implementation we get rid of:
NG0950
error and the NG0203
errordestroyRef
injection, as we’re using the takeUntilDestroyed
operator directly in an injection contextclients
propertyAltough the solution leads to a more verbose code, it’s possible that in the near future this behavior will be modified to work as expected.