View and Content Queries

Read one Angular interview question at a time, then flip for the complete explanation, example, and code.

0/55 Known filtered set
Level
Read Full Guide Question: 1 of 55

Angular Interview Questions · View and Content Queries Companion Article

View and Content Queries

Angular applications are built from components that often need to interact with child components, directives, DOM elements, and projected content. Angular provides view queries and content queries for finding and accessing these elements safely.

Modern Angular provides signal-based query APIs such as viewChild(), viewChildren(), contentChild(), and contentChildren(). These APIs integrate naturally with Angular Signals and make query results reactive.

Understanding these APIs is especially useful when building reusable UI components, working with forms, controlling child components, accessing template elements, or creating components that accept projected content.

Understanding Angular Queries

A query allows an Angular component to obtain a reference to something that exists inside or around its template.

A query may locate:

  • a child component
  • a directive
  • a DOM element
  • a template reference
  • projected content
  • multiple matching children

Angular queries can broadly be divided into:

Query TypeSearches
View QueryThe component's own template
Content QueryContent projected into the component
viewChild()First matching view child
viewChildren()All matching view children
contentChild()First matching projected child
contentChildren()All matching projected children

The difference between view and content is one of the most important ideas to understand before working with Angular queries.

View Queries

A view query searches inside the template that belongs to the current component.

Suppose a parent component template contains another Angular component:

HTML
<app-profile></app-profile>

The parent component can access the ProfileComponent using a view query.

TypeScript
profile = viewChild(ProfileComponent);

The query searches the component's own view and returns a reactive signal containing the matching component when it is available.

View queries are commonly used for:

  • calling methods on child components
  • accessing template elements
  • interacting with directives
  • reading child component state
  • controlling reusable UI components
  • integrating DOM-based libraries

Simple View Query Example

Child component:

TypeScript
import { Component } from '@angular/core';

@Component({
  selector: 'app-counter',
  template: `<p>Count: {{ count }}</p>`
})
export class CounterComponent {
  count = 0;

  increment(): void {
    this.count++;
  }
}

Parent component:

TypeScript
import { Component, viewChild } from '@angular/core';
import { CounterComponent } from './counter.component';

@Component({
  selector: 'app-root',
  imports: [CounterComponent],
  template: `
    <app-counter></app-counter>
    <button (click)="increase()">Increase</button>
  `
})
export class AppComponent {
  counter = viewChild(CounterComponent);

  increase(): void {
    this.counter()?.increment();
  }
}

Here:

  1. Angular creates the CounterComponent.
  2. viewChild() searches the parent's view.
  3. The query stores the matching child component.
  4. Calling this.counter() reads the query signal.
  5. The parent can call the child's increment() method.

viewChild()

viewChild() returns the first matching element, directive, or component from the component's own view.

Basic syntax:

TypeScript
child = viewChild(SomeComponent);

The result is a signal.

Therefore, you read the value by calling it:

TypeScript
this.child();

Not:

TypeScript
this.child;

This is an important difference between signal-based queries and older decorator-based query APIs.

viewChild() with a Component

Template:

HTML
<app-user-profile></app-user-profile>

Component:

TypeScript
profile = viewChild(UserProfileComponent);

Access the result:

TypeScript
const profileComponent = this.profile();

Because a child may not always exist, the result can initially be undefined.

For example, consider conditional rendering:

HTML
@if (showProfile) {
  <app-user-profile></app-user-profile>
}

When showProfile is false:

TypeScript
this.profile()

returns:

Text
undefined

When the child appears, Angular updates the query automatically.

viewChildren()

viewChildren() is used when multiple matching children may exist inside the component's template.

Example:

HTML
<app-card></app-card>
<app-card></app-card>
<app-card></app-card>

Query:

TypeScript
cards = viewChildren(CardComponent);

Reading the signal:

TypeScript
const cardComponents = this.cards();

The returned value represents all currently matching children.

Example:

TypeScript
showCount(): void {
  console.log(this.cards().length);
}

Output:

Text
3

Dynamic viewChildren() Queries

Signal queries are particularly useful when the template changes dynamically.

HTML
@for (product of products(); track product.id) {
  <app-product-card [product]="product"></app-product-card>
}

Query:

TypeScript
productCards = viewChildren(ProductCardComponent);

If the number of rendered products changes, Angular updates the query result.

This means query signals work naturally with Angular's reactive rendering model.

Content Queries

A content query searches content that another component passes into the current component.

Angular commonly calls this content projection.

Content projection uses:

HTML
<ng-content></ng-content>

Consider a reusable panel component:

HTML
<app-panel>
  <app-panel-header></app-panel-header>
</app-panel>

The PanelHeaderComponent is written by the consumer of app-panel.

It does not originate from the panel's own template.

Therefore, viewChild() is not the correct query.

The panel can use:

TypeScript
header = contentChild(PanelHeaderComponent);

View Content vs Projected Content

This distinction is essential.

Suppose the component is used like this:

HTML
<app-card>
  <app-user-info></app-user-info>
</app-card>

And app-card contains:

HTML
<div class="card">
  <app-card-header></app-card-header>
  <ng-content></ng-content>
</div>

For CardComponent:

Text
CardComponent
│
├── Own View
│   └── CardHeaderComponent
│
└── Projected Content
    └── UserInfoComponent

Therefore:

TypeScript
viewChild(CardHeaderComponent)

can find CardHeaderComponent.

While:

TypeScript
contentChild(UserInfoComponent)

can find UserInfoComponent.

Remember:

View queries inspect what the component owns. Content queries inspect what the component receives.

contentChild()

contentChild() retrieves the first matching child from projected content.

Example reusable container:

TypeScript
import { Component, contentChild } from '@angular/core';

@Component({
  selector: 'app-panel',
  template: `
    <section>
      <ng-content></ng-content>
    </section>
  `
})
export class PanelComponent {
  title = contentChild(PanelTitleComponent);
}

Usage:

HTML
<app-panel>
  <app-panel-title></app-panel-title>
</app-panel>

The PanelTitleComponent is projected into PanelComponent, so the content query can locate it.

contentChildren()

contentChildren() retrieves multiple matching children from projected content.

Example:

HTML
<app-menu>
  <app-menu-item>Home</app-menu-item>
  <app-menu-item>Products</app-menu-item>
  <app-menu-item>Contact</app-menu-item>
</app-menu>

Inside MenuComponent:

TypeScript
items = contentChildren(MenuItemComponent);

The number of projected menu items can then be obtained with:

TypeScript
const count = this.items().length;

The query remains reactive when the projected content changes.

Query Signals

Modern Angular query APIs return signals.

The following APIs are signal-based:

TypeScript
viewChild()
viewChildren()
contentChild()
contentChildren()

For example:

TypeScript
child = viewChild(ChildComponent);

child is not the actual component instance.

It is a signal containing the query result.

Read it with:

TypeScript
this.child()

Why Query Signals Are Useful

Query signals provide several benefits.

Reactive query results

When the template changes, Angular updates the query signal.

Example:

HTML
@if (showDetails()) {
  <app-details></app-details>
}

Query:

TypeScript
details = viewChild(DetailsComponent);

When showDetails() becomes false, the child disappears and the query changes accordingly.

Query Signals Work with computed()

A query can participate in Angular's reactive computations.

TypeScript
cardCount = computed(() => this.cards().length);

If the number of queried cards changes, cardCount is recalculated.

Query Signals Work with effect()

You can react to query changes:

TypeScript
effect(() => {
  const child = this.child();

  if (child) {
    console.log('Child is available');
  }
});

This can be useful when behavior needs to run whenever a queried child appears or changes.

Single vs Multiple Queries

Choose the query based on how many matches you expect.

RequirementAPI
One child in component viewviewChild()
Multiple children in component viewviewChildren()
One projected childcontentChild()
Multiple projected childrencontentChildren()

Example:

TypeScript
toolbar = viewChild(ToolbarComponent);
buttons = viewChildren(ButtonComponent);
title = contentChild(CardTitleComponent);
items = contentChildren(MenuItemComponent);

Accessing Child Components

One of the most common uses of queries is accessing a child component instance.

Child:

TypeScript
@Component({
  selector: 'app-video-player',
  template: `<video></video>`
})
export class VideoPlayerComponent {
  play(): void {
    console.log('Playing video');
  }

  pause(): void {
    console.log('Video paused');
  }
}

Parent:

TypeScript
player = viewChild(VideoPlayerComponent);

playVideo(): void {
  this.player()?.play();
}

Template:

HTML
<app-video-player></app-video-player>

<button (click)="playVideo()">
  Play
</button>

The parent obtains the child component instance and calls its public method.

When Accessing Child Components Makes Sense

Direct child access can be appropriate for imperative actions such as:

  • focus an input
  • open a dialog
  • close a menu
  • start or stop media
  • reset a custom component
  • scroll a component
  • call functionality exposed by a reusable UI widget

For ordinary application data flow, Angular inputs, outputs, models, signals, or shared services are usually better architectural choices.

For example, avoid using queries simply to constantly copy state between a parent and child when the same relationship could be represented declaratively with inputs and outputs.

Accessing DOM Elements

Angular queries can also access native DOM elements.

Suppose the template contains:

HTML
<input #searchInput type="text">

The component can query the element using its template reference:

TypeScript
searchInput = viewChild<ElementRef<HTMLInputElement>>('searchInput');

The actual browser element is available through:

TypeScript
this.searchInput()?.nativeElement;

For example:

TypeScript
focusSearch(): void {
  this.searchInput()?.nativeElement.focus();
}

ElementRef

ElementRef is an Angular wrapper around a native DOM element.

Import it from Angular core:

TypeScript
import { ElementRef } from '@angular/core';

A simplified mental model is:

Text
ElementRef
    ↓
nativeElement
    ↓
Actual browser DOM element

Example:

TypeScript
input = viewChild<ElementRef<HTMLInputElement>>('username');

focusInput(): void {
  this.input()?.nativeElement.focus();
}

nativeElement

The nativeElement property provides direct access to the underlying DOM element.

Example:

TypeScript
const element = this.input()?.nativeElement;

For an input element, the type may be:

TypeScript
HTMLInputElement

This allows browser APIs such as:

TypeScript
element.focus();
element.select();
element.scrollIntoView();

Type ElementRef Correctly

Instead of using:

TypeScript
viewChild<ElementRef>('searchInput');

you can provide the underlying HTML element type:

TypeScript
viewChild<ElementRef<HTMLInputElement>>('searchInput');

This improves TypeScript support.

You then receive proper autocomplete for properties such as:

TypeScript
nativeElement.value
nativeElement.focus()
nativeElement.select()

For a button:

TypeScript
button = viewChild<ElementRef<HTMLButtonElement>>('submitButton');

For a div:

TypeScript
container = viewChild<ElementRef<HTMLDivElement>>('container');

When to Avoid Direct DOM Manipulation

Although ElementRef provides direct DOM access, it should not become the default way to update the UI.

Avoid code such as:

TypeScript
this.title()?.nativeElement.textContent = 'New Title';

when Angular binding can perform the same job:

HTML
<h2>{{ title }}</h2>

Similarly, avoid:

TypeScript
this.box()?.nativeElement.style.display = 'none';

when template control flow can represent the state:

HTML
@if (showBox()) {
  <div>Content</div>
}

Angular's declarative APIs make application state easier to understand and maintain.

Direct DOM access is more appropriate when interacting with browser functionality or APIs that Angular does not directly abstract.

Security Consideration with ElementRef

Be careful when using nativeElement to insert arbitrary HTML.

Avoid taking untrusted user input and manually assigning it as HTML through native DOM APIs.

For application rendering, prefer Angular templates and binding mechanisms so Angular's normal security model remains involved.

Template References

A template reference variable provides a name for an element, component, or directive inside a template.

Syntax:

HTML
<input #username>

Here:

Text
#username

creates a template reference named username.

It can be queried with:

TypeScript
username = viewChild<ElementRef<HTMLInputElement>>('username');

Template Reference Example

Template:

HTML
<input #emailInput type="email">

<button (click)="focusEmail()">
  Focus Email
</button>

Component:

TypeScript
emailInput = viewChild<ElementRef<HTMLInputElement>>('emailInput');

focusEmail(): void {
  this.emailInput()?.nativeElement.focus();
}

Flow:

Text
#emailInput
      ↓
viewChild('emailInput')
      ↓
ElementRef
      ↓
nativeElement
      ↓
<input>

Template References Can Refer to Components

A template reference is not limited to native HTML elements.

Example:

HTML
<app-counter #counter></app-counter>

The reference can represent the component instance.

Query:

TypeScript
counter = viewChild<CounterComponent>('counter');

Then:

TypeScript
this.counter()?.increment();

The value represented by a template reference depends on what the reference is attached to.

Using the read Option

Sometimes the element that matches a query is not the exact object you want to retrieve.

Angular query APIs support reading a different token from the matching element.

For example:

TypeScript
input = viewChild('username', {
  read: ElementRef
});

This asks Angular to locate the element associated with username and return its ElementRef.

This capability becomes especially useful when working with directives, providers, template references, and advanced reusable component APIs.

Required Queries

A normal single-result query may temporarily have no matching value.

Therefore:

TypeScript
child = viewChild(ChildComponent);

can produce undefined.

When your component design guarantees that the child must exist, Angular also supports required queries.

Example:

TypeScript
child = viewChild.required(ChildComponent);

Now the query is expected to have a matching value.

Similarly, a required content child can be declared with:

TypeScript
title = contentChild.required(CardTitleComponent);

Use required queries only when the component's structure genuinely guarantees the queried item exists.

Optional vs Required Query

Optional:

TypeScript
child = viewChild(ChildComponent);

Usage:

TypeScript
this.child()?.doSomething();

Required:

TypeScript
child = viewChild.required(ChildComponent);

Usage can be simpler because the query's type does not include undefined once the required query contract is satisfied:

TypeScript
this.child().doSomething();

Do not mark optional UI as required.

For example, if this component is conditionally rendered:

HTML
@if (showChild()) {
  <app-child></app-child>
}

a normal query is generally more appropriate.

Query Results and Conditional Rendering

Queries automatically reflect the rendered template.

Example:

TypeScript
showMessage = signal(false);

message = viewChild(MessageComponent);

Template:

HTML
@if (showMessage()) {
  <app-message></app-message>
}

Initially:

TypeScript
this.message()

may be:

Text
undefined

After:

TypeScript
this.showMessage.set(true);

Angular renders MessageComponent, and the query receives the child.

This reactive behavior is an important advantage of signal-based queries.

viewChild() vs viewChildren()

Consider:

HTML
<app-item></app-item>
<app-item></app-item>
<app-item></app-item>

Using:

TypeScript
item = viewChild(ItemComponent);

is intended for retrieving a single matching child.

Using:

TypeScript
items = viewChildren(ItemComponent);

retrieves the collection of matches.

Use the API that represents the actual component relationship rather than relying on a single query where multiple children are conceptually important.

contentChild() vs contentChildren()

Consider projected content:

HTML
<app-list>
  <app-list-item>Java</app-list-item>
  <app-list-item>Angular</app-list-item>
</app-list>

One projected child:

TypeScript
item = contentChild(ListItemComponent);

Multiple projected children:

TypeScript
items = contentChildren(ListItemComponent);

For reusable list, tabs, menu, accordion, or navigation components, contentChildren() is often particularly useful.

Practical Example: Reusable Tabs

Suppose developers use a tabs component like this:

HTML
<app-tabs>
  <app-tab title="Profile">
    Profile content
  </app-tab>

  <app-tab title="Settings">
    Settings content
  </app-tab>
</app-tabs>

The individual app-tab components are projected into app-tabs.

Therefore, TabsComponent can query them using:

TypeScript
tabs = contentChildren(TabComponent);

It can then determine how many tabs exist:

TypeScript
tabCount = computed(() => this.tabs().length);

This is a typical real-world use case for content queries.

Practical Example: Search Box Focus

Template:

HTML
<input
  #search
  type="search"
  placeholder="Search products">

<button (click)="focusSearch()">
  Search
</button>

Component:

TypeScript
search = viewChild<ElementRef<HTMLInputElement>>('search');

focusSearch(): void {
  this.search()?.nativeElement.focus();
}

This is a reasonable use of direct DOM access because focus() is an imperative browser action.

Practical Example: Resetting a Child Component

Child:

TypeScript
export class FilterComponent {
  reset(): void {
    // Reset filter state
  }
}

Parent query:

TypeScript
filter = viewChild(FilterComponent);

Parent method:

TypeScript
clearFilters(): void {
  this.filter()?.reset();
}

Here, the parent controls a specific child behavior without manually manipulating the child's DOM.

Queries and Component Encapsulation

Queries should not be treated as a way to bypass component design.

Suppose a child component contains:

HTML
<input #internalInput>

A parent should normally interact with the public API of the child rather than trying to reach deeply into its internal implementation.

A better child API might be:

TypeScript
focus(): void {
  this.internalInput()?.nativeElement.focus();
}

Then the parent can use:

TypeScript
child = viewChild(CustomInputComponent);

focusInput(): void {
  this.child()?.focus();
}

This keeps implementation details inside the child component.

Queries vs Inputs

Queries and inputs solve different problems.

Use an input when the parent needs to provide data to the child.

TypeScript
product = input<Product>();

Example:

HTML
<app-product [product]="selectedProduct"></app-product>

Use a view query when the parent needs a reference to the actual child component or related object.

TypeScript
productComponent = viewChild(ProductComponent);

In most situations, ordinary data should flow through inputs rather than queries.

Queries vs Outputs

Use outputs when the child needs to communicate an event to the parent.

Example:

TypeScript
saved = output<void>();

Parent:

HTML
<app-editor (saved)="handleSave()"></app-editor>

Do not replace normal child-to-parent event communication with repeated inspection of the child through viewChild().

Queries vs Shared Services

A query represents a structural relationship between components.

A shared service is more suitable when components:

  • are not directly related
  • need shared application state
  • need communication across different parts of the application
  • need reusable business or data logic

Example:

Text
Direct Parent → Child
        ↓
View Query may be useful

Compared with:

Text
Unrelated Component A
        ↓
    Shared Service
        ↑
Unrelated Component B

Choosing the correct mechanism improves component architecture.

View Query vs Content Query Comparison

FeatureView QueryContent Query
Searches own templateYesNo
Searches projected contentNoYes
Single query APIviewChild()contentChild()
Multiple query APIviewChildren()contentChildren()
Can access componentsYesYes
Can access directivesYesYes
Can work with template referencesYesYes, depending on projected structure
Reactive signal resultYesYes

Queries and Lifecycle Timing

A query result depends on whether Angular has created the corresponding view or projected content.

Older Angular code often accesses decorator-based view queries in lifecycle hooks such as:

TypeScript
ngAfterViewInit()

and content queries in:

TypeScript
ngAfterContentInit()

Signal queries provide a more reactive model because the application can respond when the query value becomes available or changes.

For example:

TypeScript
child = viewChild(ChildComponent);

constructor() {
  effect(() => {
    const currentChild = this.child();

    if (currentChild) {
      console.log('Child available');
    }
  });
}

The important idea is not to assume that an optional query always contains a value immediately during component construction.

Query Signal Mental Model

Think of a query signal like this:

Text
Template structure changes
          ↓
Angular evaluates the query
          ↓
Query signal changes
          ↓
computed()/effect()/template consumers react

This is different from treating a query as a one-time lookup.

Querying by Component Type

One of the clearest query styles is querying directly by component type.

TypeScript
profile = viewChild(ProfileComponent);

This tells Angular:

Find the matching ProfileComponent in my view.

For multiple components:

TypeScript
profiles = viewChildren(ProfileComponent);

This approach is usually preferable when the child type itself represents what you need.

Querying by Template Reference

Sometimes you need a specific element instead.

Template:

HTML
<input #firstName>
<input #lastName>

Queries:

TypeScript
firstName = viewChild<ElementRef<HTMLInputElement>>('firstName');

lastName = viewChild<ElementRef<HTMLInputElement>>('lastName');

Using reference names makes it possible to distinguish otherwise similar elements.

Querying Directives

Queries are also useful for locating directives.

Example:

TypeScript
highlight = viewChild(HighlightDirective);

This provides access to the directive instance rather than the native DOM element.

That is useful when your directive exposes public methods or state that another component legitimately needs.

Querying Providers

Angular queries can retrieve values associated with providers available on matched elements.

This capability is useful in advanced component libraries where children expose behavior through dependency-injection tokens rather than requiring the parent to know a concrete implementation class.

For normal application code, querying components and directives directly is usually easier to understand.

Descendants and Content Queries

Content query behavior is affected by the projected content hierarchy.

When designing reusable components, consider whether the component should work only with directly projected children or should discover matching descendants deeper in projected content.

This matters for APIs such as tabs, menus, trees, accordions, and composite form controls.

Avoid making an overly broad query unless the component genuinely needs to manage nested descendants.

Common Mistake: Using View Query for Projected Content

Incorrect idea:

TypeScript
item = viewChild(ProjectedItemComponent);

when the component comes from:

HTML
<app-container>
  <app-projected-item></app-projected-item>
</app-container>

The item belongs to projected content.

Use:

TypeScript
item = contentChild(ProjectedItemComponent);

Common Mistake: Forgetting That a Query Is a Signal

Incorrect:

TypeScript
this.child.doSomething();

With signal-based queries, read the signal:

TypeScript
this.child()?.doSomething();

For a guaranteed required query:

TypeScript
this.child().doSomething();

Common Mistake: Assuming an Optional Child Always Exists

Consider:

HTML
@if (loggedIn()) {
  <app-user-menu></app-user-menu>
}

The following query may have no match:

TypeScript
menu = viewChild(UserMenuComponent);

Therefore, code should account for absence:

TypeScript
this.menu()?.open();

Common Mistake: Excessive Direct DOM Manipulation

Avoid turning Angular components into manual DOM scripts.

For example, avoid using ElementRef for routine UI state such as:

TypeScript
element.style.color = 'red';
element.style.display = 'none';
element.textContent = 'Updated';

when Angular bindings can represent the same state.

Prefer:

HTML
<p [class.error]="hasError()">
  {{ message() }}
</p>

Use direct DOM access primarily when an imperative browser API is actually required.

Common Mistake: Making Components Too Tightly Coupled

If a parent frequently calls many internal methods of a child through:

TypeScript
viewChild()

the two components may become tightly coupled.

For example:

TypeScript
this.child()?.load();
this.child()?.validate();
this.child()?.calculate();
this.child()?.save();
this.child()?.refresh();

This can indicate that responsibilities should be reconsidered.

Depending on the use case, inputs, outputs, signals, services, or a clearer public component API may produce cleaner architecture.

Common Mistake: Using Queries for Normal Data Transfer

Avoid:

TypeScript
child = viewChild(UserComponent);

getUserName(): string {
  return this.child()?.userName ?? '';
}

simply because the parent needs user data.

If the parent owns the data, pass it through an input.

If the child produces an event, expose it through an output.

Queries are best suited for obtaining actual references rather than replacing normal Angular data flow.

Common Mistake: Querying the Wrong Template Reference

Template reference names are strings:

HTML
<input #username>

Correct:

TypeScript
username = viewChild('username');

A mismatch such as:

TypeScript
viewChild('userName')

will not find #username.

The names must match.

Query Selection Guide

Use this decision process:

Text
What do you need to access?
        |
        +-- Something inside my component template?
        |          |
        |          +-- One item → viewChild()
        |          |
        |          +-- Multiple items → viewChildren()
        |
        +-- Something projected using ng-content?
                   |
                   +-- One item → contentChild()
                   |
                   +-- Multiple items → contentChildren()

When to Use viewChild()

Use viewChild() when:

  • one child component must be accessed
  • one directive instance is required
  • one template element is required
  • focus must be moved to an element
  • a child exposes an imperative public API
  • a DOM-based integration requires access to an element

Example:

TypeScript
dialog = viewChild(DialogComponent);

When to Use viewChildren()

Use viewChildren() when:

  • multiple children of the same type exist
  • a repeated component collection must be inspected
  • dynamically rendered children must be tracked
  • a parent manages a group of child UI components

Example:

TypeScript
rows = viewChildren(TableRowComponent);

When to Use contentChild()

Use contentChild() when:

  • a reusable component expects one projected child
  • a card accepts a projected title
  • a form container accepts a custom projected control
  • a wrapper needs to interact with content supplied by its consumer

Example:

TypeScript
header = contentChild(CardHeaderComponent);

When to Use contentChildren()

Use contentChildren() when:

  • a component manages multiple projected tabs
  • a menu accepts projected menu items
  • an accordion accepts projected panels
  • a toolbar accepts projected actions
  • a reusable container needs information about a set of projected children

Example:

TypeScript
tabs = contentChildren(TabComponent);

Real-World Component Library Example

Consider a reusable accordion API:

HTML
<app-accordion>
  <app-accordion-item title="Account">
    Account information
  </app-accordion-item>

  <app-accordion-item title="Security">
    Security settings
  </app-accordion-item>

  <app-accordion-item title="Billing">
    Billing information
  </app-accordion-item>
</app-accordion>

The accordion items are content supplied by the consumer.

Therefore:

TypeScript
items = contentChildren(AccordionItemComponent);

is appropriate.

The accordion can calculate:

TypeScript
itemCount = computed(() => this.items().length);

It might also coordinate public methods exposed by its projected child components.

This pattern is common in reusable Angular component libraries.

Real-World DOM Example: Selecting Input Text

Template:

HTML
<input #couponCode value="WELCOME10">

<button (click)="selectCode()">
  Select Code
</button>

Component:

TypeScript
couponCode = viewChild<ElementRef<HTMLInputElement>>('couponCode');

selectCode(): void {
  this.couponCode()?.nativeElement.select();
}

This is another practical example where accessing the native browser element is justified.

Prefer Component APIs Over Native DOM Access

Suppose you have:

HTML
<app-dialog></app-dialog>

Instead of locating internal dialog HTML and changing its styles manually, expose a public component method:

TypeScript
export class DialogComponent {
  open(): void {
    // Manage dialog state
  }
}

Parent:

TypeScript
dialog = viewChild(DialogComponent);

showDialog(): void {
  this.dialog()?.open();
}

This preserves component encapsulation and reduces dependencies on internal markup.

Query API Naming Pattern

The modern APIs follow a simple naming convention.

Text
view + Child       → viewChild()
view + Children    → viewChildren()

content + Child    → contentChild()
content + Children → contentChildren()

So you only need to answer two questions:

  1. Is the target in the component's view or projected content?
  2. Do you need one match or multiple matches?

Practical Best Practices

Prefer signal-based queries in modern Angular code

Use APIs such as:

TypeScript
viewChild()
viewChildren()
contentChild()
contentChildren()

when building new Angular applications using the modern signal-based approach.

Keep child APIs small

Expose only meaningful public behavior from child components.

Use queries for references, not ordinary state management

Inputs, outputs, signals, and services should continue to handle most application data flow.

Treat optional queries as potentially absent

Use safe access where appropriate:

TypeScript
this.child()?.run();

Use required queries only when structurally guaranteed

TypeScript
child = viewChild.required(ChildComponent);

should reflect a genuine component contract.

Type DOM elements

Prefer:

TypeScript
ElementRef<HTMLInputElement>

over untyped DOM access.

Minimize nativeElement usage

Use Angular bindings whenever the same behavior can be represented declaratively.

Preserve encapsulation

A parent should use the child's public component API instead of manipulating the child's internal DOM.

Quick Reference

RequirementRecommended API
Access one child componentviewChild()
Access multiple child componentsviewChildren()
Access one projected componentcontentChild()
Access multiple projected componentscontentChildren()
Access a DOM elementviewChild() + ElementRef
Access element by #referenceviewChild('reference')
Require a view childviewChild.required()
Require a projected childcontentChild.required()

Key Takeaways

  • Angular queries provide references to components, directives, elements, and projected content.
  • View queries search the component's own template.
  • Content queries search content projected into the component.
  • viewChild() retrieves a single item from the component view.
  • viewChildren() retrieves multiple items from the component view.
  • contentChild() retrieves a single projected item.
  • contentChildren() retrieves multiple projected items.
  • Modern Angular query APIs return signals.
  • Query signals automatically reflect changes to the rendered component structure.
  • ElementRef provides access to an underlying native DOM element.
  • nativeElement should be used carefully and only when direct browser interaction is genuinely necessary.
  • Template reference variables such as #searchInput can be used as query locators.
  • Queries are useful for imperative component interaction but should not replace normal input/output data flow.
  • Well-designed components expose small public APIs instead of allowing parents to depend heavily on internal implementation details.

Practical Revision Notes

Remember the four-query rule:

Text
Own template + one child       = viewChild()
Own template + many children   = viewChildren()

Projected content + one child  = contentChild()
Projected content + many       = contentChildren()

And remember the signal rule:

TypeScript
child = viewChild(ChildComponent);

Read it with:

TypeScript
this.child();

For native elements:

Text
Template Reference
       ↓
viewChild()
       ↓
ElementRef
       ↓
nativeElement
       ↓
Browser DOM Element

These concepts form the foundation for working with child references, reusable component APIs, content projection, and controlled DOM interaction in modern Angular applications.

Question Hint