Why does an Angular component throw NG0950 when a field initializer reads its input.required() signal, and how would you restructure it?
answer
- constructor runs before bindings
- required inputs start unset
- optional inputs give a stale default
- defer the read with computed()
basics
~20 sField initializers run in the constructor, before Angular sets any input, so reading an unset required input throws NG0950. Derive the value with computed(), read it in the template, or move one-time setup to ngOnInit.
solid answer
~40 sAngular constructs the component, including every field initializer, before it applies the parent's bindings; inputs are guaranteed only from `ngOnInit` on. An `input.required()` signal starts unset, and calling it then throws NG0950, *Input is required but no value is available yet*. The quieter sibling bug is an optional input read in an initializer: `px = this.size() * 2` succeeds with the default and never updates when the parent binds a new size. The fix is to defer the read: `px = computed(() => this.size() * 2)` reads the input when first called, during the template check, and stays current afterwards; template-only values can read the input directly; one-time imperative setup belongs in `ngOnInit`. In tests, call `fixture.componentRef.setInput()` before the first `detectChanges()`.
code
ts · 17 linesimport { Component, computed, input } from '@angular/core';
@Component({
selector: 'app-avatar',
template: `<img [src]="url()" [width]="px()" [height]="px()" />`,
})
export class Avatar {
readonly src = input.required<string>();
readonly size = input(40);
// Wrong: runs in the constructor, before inputs are set.
// readonly url = `${this.src()}?s=${this.size()}`; // NG0950
// readonly px = this.size() * 2; // stuck at 80
readonly url = computed(() => `${this.src()}?s=${this.size()}`);
readonly px = computed(() => this.size() * 2);
}go deeper
Remember that inputs are not available in the constructor and are reliable from ngOnInit onwards.
Explain the creation order, why an unset required input throws NG0950, and why computed() defers the read safely.
Diagnose both the loud NG0950 and the silent stale-snapshot bug, and set test habits that set inputs before the first detectChanges().
Treat input timing as a codebase rule: lint for input reads in initializers and document the constructor-for-injection convention.
## The symptom A team converts an avatar component to signal inputs. Two bugs appear: ```ts import { Component, input } from '@angular/core'; @Component({ selector: 'app-avatar', template: `<img [src]="url" [width]="px" />` }) export class Avatar { readonly src = input.required<string>(); readonly size = input(40); readonly url = `${this.src()}?s=${this.size()}`; // throws NG0950 readonly px = this.size() * 2; // stuck at 80 } ``` 1. Creating the component throws **NG0950**: *Input is required but no value is available yet.* 2. After removing the `url` line, `px` is always `80`, even when the parent binds `[size]="64"`. Both come from the same fact: **field initializers run in the constructor, before Angular has set any input.** ## When Angular sets inputs Angular creates a component in this order: 1. It runs the class **constructor**, which includes every **field initializer**. At this point no parent binding has been applied. 2. It sets the initial **input values** from the parent's bindings. 3. It calls **`ngOnChanges`** (if implemented), then **`ngOnInit`**. 4. It checks the component's template, and later bindings update inputs again. So inputs are *guaranteed* to be available from `ngOnInit` onwards, as the NG0950 documentation says, and in the constructor they are not. ## Why each bug happens **NG0950 on a required input.** An `input.required()` signal starts in an internal *unset* state. Reading it, by calling `this.src()`, while unset throws `NG0950`. The error exists precisely so that a required input never silently reads as `undefined`. **The stale value on an optional input.** `input(40)` starts with its default, so `this.size()` in a field initializer succeeds and returns `40`. But `px` is a **plain number**, computed once. When the parent binds `64` later, the input signal updates and nothing recomputes `px`. The bug is quiet, which makes it the worse of the two. ## The fixes | Need | Fix | |---|---| | A value derived from inputs | `computed(() => ...)`, read in the template as `url()` | | Only the template needs it | read the input directly in the template: `[width]="size() * 2"` | | One-time imperative setup using inputs | move it into `ngOnInit` | | A side effect whenever an input changes | an effect, a separate subject | The fixed component: ```ts import { Component, computed, input } from '@angular/core'; @Component({ selector: 'app-avatar', template: `<img [src]="url()" [width]="px()" />` }) export class Avatar { readonly src = input.required<string>(); readonly size = input(40); readonly url = computed(() => `${this.src()}?s=${this.size()}`); readonly px = computed(() => this.size() * 2); } ``` `computed()` does not read its inputs when it is *declared*; it reads them when first *called*, which happens during the template check, after inputs are set. That is why wrapping the expression removes both the error and the staleness. ## The same trap with decorator inputs With `@Input()` fields the constructor sees each field's initializer value (often `undefined`) and there is no error at all: code that reads inputs in the constructor quietly works with the wrong values. The fix is the same, move the logic to `ngOnInit` or to a setter, or better, convert to signal inputs and use `computed()`. This is the classic interview point that the **constructor is for dependency injection and trivial field setup, and `ngOnInit` is where inputs are first reliable**. ## Diagnosing it in a real codebase - Search for input signals **called** in field initializers, constructors, or `inject()`-time factory code; each is either an NG0950 waiting to happen (required) or a stale snapshot (optional). - In tests, remember that `TestBed.createComponent()` constructs the component immediately; set required inputs with `fixture.componentRef.setInput()` **before** the first `fixture.detectChanges()`, which runs `ngOnInit` and the first template check. - If NG0950 appears only when one particular host creates the component from code, check that the host sets the required input before the first check; a template that omits it would already have failed the build with NG8008.
- Why does the optional-input version not throw but still misbehave?`input(40)` holds its default from the start, so reading it in a field initializer returns 40. The result is stored in a plain field that nothing recomputes, so later bindings update the input signal but never the field. Wrapping it in `computed()` makes it a derivation instead of a snapshot.
- How do you avoid NG0950 in a TestBed test of this component?Create the fixture, then call `fixture.componentRef.setInput('src', '/u/7.png')` before the first `fixture.detectChanges()`. Construction does not read the input once reads are inside `computed()`, and the first change detection then finds a value.
A field initializer reading an input is like photocopying a notice board the moment it is put up, before anyone has pinned a notice: a required notice is simply missing, and an optional one shows the placeholder forever. computed() is reading the board each time you walk past it.
saying these in an interview costs you the question
- Inputs are already set when the constructor runs.
- An optional input read in a field initializer stays in sync automatically.
- NG0950 means the parent forgot to bind the input in its template.
- Declaring computed(() => this.src()) in a field initializer also throws NG0950.
- The fix for NG0950 is to give the required input a default value.