Question
In an Angular component, @ViewChild('nameInput') and @ViewChild('amountInput') produce this TypeScript error:
TS2554: Expected 2 arguments, but got 1.
An argument for 'opts' was not provided.
How should @ViewChild be declared so Angular can access these template reference variables?
@ViewChild('nameInput') nameInputRef: ElementRef;
@ViewChild('amountInput') amountInputRef: ElementRef;
Short Answer
You will learn how Angular's @ViewChild decorator finds elements or child components in a template, why some Angular versions require an options object, and how { static: true } and { static: false } affect when the query is available.
Concept
@ViewChild is an Angular property decorator used to get a reference to something in a component's own template. That "something" can be:
- A native DOM element marked with a template reference such as
#nameInput - A child component
- A directive
- A
TemplateRef
For example, if the template contains:
<input #nameInput type="text">
Angular can assign that input element to a class property:
@ViewChild('nameInput', { static: false })
nameInputRef!: ElementRef<HTMLInputElement>;
The second argument is the query options object. In Angular versions whose ViewChild type definition requires it, omitting that object causes TypeScript error TS2554.
The most important option is static:
static: falsemeans the query is resolved after Angular has rendered and checked the view. This is the usual choice for elements that might be inside*ngIf,*ngFor, or other dynamic template blocks.static: truemeans Angular resolves the query earlier, before change detection runs. Use it only when the target is always present in the initial template.
This matters because attempting to use a view child before Angular has created it can result in an undefined reference.
Mental Model
Think of a component template as a room and @ViewChild as asking Angular for the location of a particular object in that room.
#nameInputis a label attached to an object.@ViewChild('nameInput', ...)is your request to Angular: “Find the object with this label.”{ static: false }means: “Give me the location once the room has been set up and rendered.”{ static: true }means: “This object will definitely already be in the room, so find it immediately.”
If the object is behind a conditional door such as *ngIf, it may not exist at first. In that case, static: false is the safer choice.
Syntax and Examples
Use a template reference variable in HTML, then query it from the component class.
<input #nameInput type="text" placeholder="Ingredient name">
<input #amountInput type="number" placeholder="Amount">
<button type="button" (click)="onAddItem()">Add</button>
import { Component, ElementRef, EventEmitter, Output, ViewChild } from '@angular/core';
import { Ingredient } from 'src/app/shared/ingredient.model';
@Component({
selector: 'app-shopping-edit',
templateUrl: './shopping-edit.component.html',
styleUrls: ['./shopping-edit.component.css']
})
export class ShoppingEditComponent {
@ViewChild('nameInput', { static: false })
nameInputRef!: ElementRef<HTMLInputElement>;
@ViewChild('amountInput', { static: false })
amountInputRef!: ElementRef<HTMLInputElement>;
@Output() ingredientAdded = new EventEmitter<Ingredient>();
onAddItem(): void {
const ingName = this.nameInputRef.nativeElement.value;
const ingAmount = Number(this.amountInputRef.nativeElement.value);
this.ingredientAdded.emit(new Ingredient(ingName, ingAmount));
}
}
ElementRef<HTMLInputElement> describes the native element more precisely than plain ElementRef. The ! is TypeScript's definite-assignment assertion: Angular assigns the property after it creates the view, even though TypeScript cannot see that assignment directly.
For a normal input that is always present, this also works:
@ViewChild('nameInput', { static: true })
nameInputRef!: ElementRef<HTMLInputElement>;
However, choose static: true only when the input is guaranteed not to be conditionally rendered.
Step by Step Execution
Consider this template and component method:
<input #nameInput value="Apples">
<button type="button" (click)="onAddItem()">Add</button>
@ViewChild('nameInput', { static: false })
nameInputRef!: ElementRef<HTMLInputElement>;
onAddItem(): void {
const name = this.nameInputRef.nativeElement.value;
console.log(name);
}
Execution flow:
- Angular creates
ShoppingEditComponent. - Angular renders its template, including the
<input #nameInput>element. - Because the query uses
static: false, Angular resolvesnameInputRefafter the view is initialized. - The user clicks Add.
- Angular calls
onAddItem(). nameInputRef.nativeElementrefers to the actual HTML input element.- Reading
.valuereturns"Apples", which is logged.
For a static: false query, use it in ngAfterViewInit or in event handlers such as click handlers. Do not depend on it inside ngOnInit.
import { AfterViewInit } from '@angular/core';
export class ShoppingEditComponent implements AfterViewInit {
@ViewChild('nameInput', { static: false })
nameInputRef!: ElementRef<HTMLInputElement>;
ngAfterViewInit(): void {
console.log(this.nameInputRef.nativeElement.value);
}
}
Real World Use Cases
@ViewChild is useful when Angular data binding alone is not enough or when a direct element or component API is needed.
- Focus a form field: Put the cursor in a search box after a dialog opens.
- Read or set native element state: Access a video element to call
play()orpause(). - Call a child component method: Ask a reusable modal, editor, or chart component to reset or refresh itself.
- Integrate browser or third-party APIs: Pass a canvas element to a charting library after the view exists.
- Measure layout: Read element dimensions after rendering for positioning logic.
Example: focusing an input after the view is available:
@ViewChild('searchBox', { static: false })
searchBox!: ElementRef<HTMLInputElement>;
ngAfterViewInit(): void {
this.searchBox.nativeElement.focus();
}
Real Codebase Usage
In production Angular applications, prefer declarative binding for ordinary form values:
<input [(ngModel)]="ingredientName">
or reactive forms:
form = new FormGroup({
name: new FormControl('', { nonNullable: true })
});
Use @ViewChild when you specifically need the element, a child component instance, or a browser API.
A common pattern is querying a child component instead of querying its DOM:
@ViewChild(IngredientFormComponent, { static: false })
formComponent!: IngredientFormComponent;
save(): void {
if (!this.formComponent.isValid()) {
return;
}
this.formComponent.submit();
}
This keeps parent components dependent on a child's public methods rather than its internal HTML structure.
For input values, validate before emitting data:
onAddItem(): void {
const name = this.nameInputRef.nativeElement.value.trim();
const amount = Number(this.amountInputRef.nativeElement.value);
if (!name || !Number.isFinite(amount) || amount <= 0) {
return;
}
this.ingredientAdded.emit(new Ingredient(name, amount));
}
Avoid direct DOM manipulation for styling or visibility when Angular bindings such as [class.active], [hidden], and *ngIf can express the same behavior.
Common Mistakes
Omitting required query options
In Angular versions where the decorator signature requires options, this produces TS2554:
// Broken when the installed Angular typings require options
@ViewChild('nameInput')
nameInputRef!: ElementRef;
Pass an options object:
@ViewChild('nameInput', { static: false })
nameInputRef!: ElementRef<HTMLInputElement>;
Using a dynamic query in ngOnInit
A static: false query is not ready during ngOnInit.
// Broken: nameInputRef may be undefined here
ngOnInit(): void {
this.nameInputRef.nativeElement.focus();
}
Use ngAfterViewInit instead:
ngAfterViewInit(): void {
this.nameInputRef.nativeElement.focus();
}
Choosing static: true for an element under *ngIf
<input *ngIf="showInput" #nameInput>
The input may not exist during initial rendering. Use static: false for this situation.
Forgetting the template reference variable
This query cannot find anything unless the template contains #nameInput:
@ViewChild('nameInput', { static: false })
nameInputRef!: ElementRef<HTMLInputElement>;
<!-- Required matching reference -->
<input #nameInput>
Treating numeric input values as numbers
Even <input type="number"> returns a string through .value.
const amount = this.amountInputRef.nativeElement.value; // string
Convert and validate it:
const amount = Number(this.amountInputRef.nativeElement.value);
Comparisons
| Choice | When it is available | Best use case |
|---|---|---|
@ViewChild(..., { static: true }) | Early in the component lifecycle, including ngOnInit | The target always exists in the initial template |
@ViewChild(..., { static: false }) | After the view initializes, such as ngAfterViewInit | The target can change or is inside *ngIf / *ngFor |
| Template binding | During Angular's normal change detection | Displaying and updating ordinary UI data |
| Reactive forms | Through form controls and validators | Managing form fields, validation, and submission |
@ViewChild returns one matching item. If a template has multiple matching items, use @ViewChildren instead:
@ViewChildren('item') itemElements!: QueryList<ElementRef<HTMLLIElement>>;
Use @ContentChild when the target is projected into a component through <ng-content>, rather than declared directly in that component's own template.
Cheat Sheet
import { AfterViewInit, ElementRef, ViewChild } from '@angular/core';
@ViewChild('inputRef', { static: false })
inputRef!: ElementRef<HTMLInputElement>;
<input #inputRef>
- The string in
@ViewChild('inputRef', ...)must match#inputRefin the template. - Pass
{ static: false }for dynamic or conditionally rendered targets. - A
static: falsequery is safe inngAfterViewInitand event handlers. - Use
{ static: true }only when the target always exists on first render. - Use
!because Angular assigns the property after construction. nativeElement.valueis a string; useNumber(...)for numeric values.- Prefer binding or Angular forms for normal form state; use
ElementReffor direct element APIs when needed. - Use
@ViewChildrenfor multiple matches.
FAQ
Why does @ViewChild say Expected 2 arguments but got 1?
Your installed Angular type definitions require the query options argument. Add an options object, usually { static: false }.
Should I use static: true or static: false with @ViewChild?
Use static: false by default, especially for elements controlled by *ngIf or *ngFor. Use static: true only if the target always exists in the initial template and you need it in ngOnInit.
Why is my @ViewChild undefined in ngOnInit?
A query with static: false is resolved after the view is initialized. Access it in ngAfterViewInit or later.
Do I need ElementRef for @ViewChild?
Only when querying a native DOM element. When querying a child component, type the property as that component class instead.
Why use ElementRef<HTMLInputElement> instead of ElementRef?
The generic type tells TypeScript which native element you expect, improving editor suggestions and type checking.
Can I access two inputs with @ViewChild?
Yes. Give each input a different template reference variable and declare one @ViewChild property for each one.
Does an HTML number input return a number?
No. Its .value property is still a string. Convert it with Number(value) and validate the result.
When should I avoid direct access through nativeElement?
Avoid it for normal UI state and styling. Prefer Angular bindings, directives, and reactive forms. Direct access is appropriate for focused browser APIs such as focus(), media controls, canvas, or integrations.
Mini Project
Description
Build a small ingredient-entry component that reads two input elements through @ViewChild, validates the values, and emits a new ingredient object. This demonstrates how view queries work after Angular renders a template.
Goal
Create an ingredient form that emits a valid ingredient when the user clicks Add Ingredient.
Requirements
Include text and numeric inputs with template reference variables.
Keep learning
Related questions
@Directive vs @Component in Angular: Differences, Use Cases, and When to Use Each
Learn the difference between @Directive and @Component in Angular, including use cases, examples, and when to choose each.
Accessing Input Value from EventTarget in TypeScript
Learn why EventTarget has no value property in TypeScript and safely read values from HTML input events in Angular applications.
Angular (change) vs (ngModelChange): What’s the Difference?
Learn the difference between Angular (change) and (ngModelChange), when each fires, and which one to use in forms and inputs.