Question

Angular @ViewChild Options: Fix “Expected 2 Arguments” Error

typescriptangularviewchild

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: false means 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: true means 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.

  • #nameInput is 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.

Take Quiz

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:

  1. Angular creates ShoppingEditComponent.
  2. Angular renders its template, including the <input #nameInput> element.
  3. Because the query uses static: false, Angular resolves nameInputRef after the view is initialized.
  4. The user clicks Add.
  5. Angular calls onAddItem().
  6. nameInputRef.nativeElement refers to the actual HTML input element.
  7. Reading .value returns "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() or pause().
  • 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();
}
Take Quiz

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

ChoiceWhen it is availableBest use case
@ViewChild(..., { static: true })Early in the component lifecycle, including ngOnInitThe target always exists in the initial template
@ViewChild(..., { static: false })After the view initializes, such as ngAfterViewInitThe target can change or is inside *ngIf / *ngFor
Template bindingDuring Angular's normal change detectionDisplaying and updating ordinary UI data
Reactive formsThrough form controls and validatorsManaging 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.

Take Quiz

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 #inputRef in the template.
  • Pass { static: false } for dynamic or conditionally rendered targets.
  • A static: false query is safe in ngAfterViewInit and event handlers.
  • Use { static: true } only when the target always exists on first render.
  • Use ! because Angular assigns the property after construction.
  • nativeElement.value is a string; use Number(...) for numeric values.
  • Prefer binding or Angular forms for normal form state; use ElementRef for direct element APIs when needed.
  • Use @ViewChildren for 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.

Take Quiz

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.

Take Quiz

Keep learning

Related questions

View more