In Angular, what is the difference between a view query such as viewChild() and a content query such as contentChild()?
answer
- whose template the node sits in
- own template vs between the tags
- projected nodes belong to the parent
- neither crosses into another component
basics
~10 sA view query (viewChild, viewChildren) searches the component's own template; a content query (contentChild, contentChildren) searches the nodes a parent placed between the component's tags. Neither looks inside another component's template.
solid answer
~40 sAngular has two query families. **View queries**, `viewChild()` and `viewChildren()` (or `@ViewChild`/`@ViewChildren`), look through the elements written in the component's own `template`. **Content queries**, `contentChild()` and `contentChildren()` (or `@ContentChild`/`@ContentChildren`), look through the **content**: the nodes a parent writes between the component's opening and closing tags, typically shown through `<ng-content>`. A projected node belongs to the parent's template, so a view query never finds it, even though it appears inside the component on screen. Neither kind pierces component boundaries: a query never matches nodes inside a child component's template. The signal functions return a `Signal`, with `undefined` or an empty array when nothing matches.
code
ts · 16 linesimport {Component, contentChildren, viewChild, ElementRef} from '@angular/core';
@Component({selector: 'app-option', template: `<ng-content />`})
export class Option {}
@Component({
selector: 'app-select',
template: `<div #list class="list"><ng-content /></div>`,
})
export class Select {
list = viewChild<ElementRef<HTMLDivElement>>('list'); // found: in Select's template
options = contentChildren(Option); // found: written by the parent
}
// Parent template:
// <app-select><app-option>A</app-option><app-option>B</app-option></app-select>go deeper
Remember which query looks where: view queries in your own template, content queries in what the parent put between your tags.
Explain why a projected node is content rather than view, and what the single and plural signal functions return when nothing matches.
Use the boundary rule to design components that expose a small API instead of letting ancestors reach into their internals.
Set conventions for when a library component may depend on content queries, since they couple its behaviour to how consumers write their markup.
## Two places a node can come from An Angular component sees nodes from two sources, and its queries are split the same way. - The **view** is the component's own template: whatever is written in its `template` or `templateUrl`. - The **content** is what a *parent* writes between the component's tags when it uses it, for example `<app-card><h2>Title</h2></app-card>`. The `<h2>` is declared in the parent's template. The card can render it through `<ng-content>`, but it does not own it. A **query** is a declaration on a component or directive class that asks Angular to find matching nodes and keep the results current. The locator is usually a component or directive class, a template reference variable name as a string, or a provider token. CSS selectors are not supported as locators. ## The four signal functions and their decorator twins | Looks in | One result | Many results | Decorator equivalents | |---|---|---|---| | the view (own template) | `viewChild()` | `viewChildren()` | `@ViewChild`, `@ViewChildren` | | the content (parent-supplied nodes) | `contentChild()` | `contentChildren()` | `@ContentChild`, `@ContentChildren` | The signal-based functions, imported from `@angular/core`, are the recommended form for new code in Angular 22.2 and have been public API since 19.0. The decorators remain fully supported and are what most existing codebases use. ## What the results look like - `viewChild()` and `contentChild()` return a `Signal` whose value is the first match, or `undefined` when nothing matches, for example because the element sits inside an `@if` that is false. - `viewChildren()` and `contentChildren()` return a `Signal` of a `ReadonlyArray`, empty when nothing matches. - `viewChild.required()` and `contentChild.required()` drop `undefined` from the type and throw a runtime error if read when there is no result. - The decorator forms instead assign plain properties: a single instance, or a `QueryList` for the plural decorators. Because signal query results are signals, you can use them in the template, in `computed()` and in `effect()`, and Angular keeps them current as `@if` and `@for` blocks add or remove matching nodes. ## The boundary rule **Queries never pierce component boundaries.** A view query sees only the component's template, not the templates of child components rendered in it. A content query sees only the nodes in the parent's template that sit inside this component's tags; it does not look into the templates of components used there. This keeps each template a black box to its ancestors. A useful consequence: if a card projects `<app-button>` supplied by its parent, the card finds it with `contentChild(Button)`, while the parent that wrote it could find the same button with its own `viewChild(Button)`, because to the parent it is part of its view. ## A side-by-side example ```ts import {Component, contentChild, viewChild, ElementRef} from '@angular/core'; @Component({selector: 'app-card-title', template: `<ng-content />`}) export class CardTitle {} @Component({ selector: 'app-card', template: ` <header #head><ng-content select="app-card-title" /></header> <section><ng-content /></section> `, }) export class Card { head = viewChild.required<ElementRef<HTMLElement>>('head'); // own template title = contentChild(CardTitle); // projected by the parent } ``` Used as `<app-card><app-card-title>Plan</app-card-title>...</app-card>`, `head` resolves to the `<header>` element and `title` to the `CardTitle` instance. `viewChild(CardTitle)` would stay `undefined`, because the title was declared in the parent's template. ## The same card with decorators In a codebase that predates signal queries, the card would read: - `@ViewChild('head') head!: ElementRef<HTMLElement>;` for the header element in its own template; - `@ContentChild(CardTitle) title?: CardTitle;` for the projected title. The split between view and content is identical. What changes is the shape of the result: a plain property that Angular assigns, rather than a signal you call. Because nothing notifies you when a decorator property is reassigned, older code reads these properties in lifecycle hooks, while signal queries can be read from a template or `computed()` and simply update. Either way, moving the title from the parent's markup into the card's own template would turn it from a content child into a view child, and the query would have to change with it. ## How interviewers use this question 1. They check that you can say where each query looks without mixing up "inside on screen" with "declared in my template". 2. They follow up on timing: content results exist before view results, which is why the decorator hooks come in the order `ngAfterContentInit` then `ngAfterViewInit`. 3. They probe the modern API: returning signals, the `.required` variants, and that the decorator forms still work. The exact moment results become available, and the options that change what a query returns, are separate questions; this one is about which template each family searches.
- A component renders <app-chart> in its template, and the chart has a <canvas> in its own template. Can the outer component's viewChild find that canvas?No. Queries never pierce component boundaries: the canvas lives in the chart's template, which is a black box to its parent. The outer component can query the `Chart` instance and call a method the chart exposes, or the chart can query its own canvas.
- What value does a signal viewChild() hold when its element is inside an @if that is false?`undefined`, and the signal updates to the match once the `@if` becomes true. That is why the non-required form includes `undefined` in its type, and why `viewChild.required()` suits only elements that are always rendered.
saying these in an interview costs you the question
- Projected content can be found with viewChild because it renders inside the component
- A view query can reach nodes inside a child component's template
- contentChild searches the component's own template
- Queries accept CSS selectors such as '.item' as locators
- Signal queries replaced the decorators, which no longer work