Signal Queries (v19)
📚 Signal-based Queries
Signal queries replace decorator-based queries with reactive signals:
- viewChild(): Query single element in view (replaces @ViewChild)
- viewChildren(): Query multiple elements (replaces @ViewChildren)
- contentChild(): Query projected content (replaces @ContentChild)
- contentChildren(): Query multiple projected (replaces @ContentChildren)
- Signal-based: Results are signals, reactive to changes
- No need for ngAfterViewInit: Use computed/effect instead
🎯 Interview Questions
- Q1: What is the advantage of signal queries over decorator queries?
- A: Signal queries are reactive - you can use them in computed() and effect(). No need for ngAfterViewInit timing issues.
- Q2: What does viewChild.required() return?
- A: A signal guaranteed to have a value. Throws error if element not found.
- Q3: When are signal query results available?
- A: After view initialization, just like decorators. But with signals, you use computed/effect instead of lifecycle hooks.
- Q4: Can you query by template reference variable?
- A: Yes! viewChild('myRef') or viewChild.required('myRef') queries #myRef.
🔧 viewChild() Demo
Query by Template Reference
Button uses viewChild('searchInput') to access the input element
Query by Component Type
First Panel
First PanelItem component queried via viewChild(PanelItemComponent)
🔧 viewChildren() Demo
Query Multiple Elements
Item 1
Item 2
Item 3
Panel count (from viewChildren signal): 0
📋 Query Types Reference
| Old (Decorator) | New (Signal) | Return Type |
|---|---|---|
| @ViewChild('ref') | viewChild('ref') | Signal<T | undefined> |
| @ViewChild(Type) | viewChild(Type) | Signal<T | undefined> |
| @ViewChild('ref', { static: true }) | viewChild.required('ref') | Signal<T> |
| @ViewChildren(Type) | viewChildren(Type) | Signal<readonly T[]> |
| @ContentChild(Type) | contentChild(Type) | Signal<T | undefined> |
| @ContentChildren(Type) | contentChildren(Type) | Signal<readonly T[]> |
💻 Signal Queries Code
import {
viewChild,
viewChildren,
contentChild,
contentChildren,
computed,
effect
} from '@angular/core';
@Component({
template: `
<input #searchBox />
<app-item />
<app-item />
`
})
export class MyComponent {
// ===== viewChild - Single Element =====
// Query by template reference
searchBox = viewChild<ElementRef>('searchBox');
// Returns: Signal<ElementRef | undefined>
// Required - guaranteed to exist
searchBoxRequired = viewChild.required<ElementRef>('searchBox');
// Returns: Signal<ElementRef>
// Query by component type
firstItem = viewChild(ItemComponent);
// Returns: Signal<ItemComponent | undefined>
// With read option (get ElementRef instead of component)
itemElement = viewChild(ItemComponent, { read: ElementRef });
// ===== viewChildren - Multiple Elements =====
allItems = viewChildren(ItemComponent);
// Returns: Signal<readonly ItemComponent[]>
allInputs = viewChildren<ElementRef>('input');
// Returns: Signal<readonly ElementRef[]>
// ===== contentChild - Projected Content =====
// In parent template: <my-container><app-header /></my-container>
header = contentChild(HeaderComponent);
// Returns: Signal<HeaderComponent | undefined>
headerRequired = contentChild.required(HeaderComponent);
// Returns: Signal<HeaderComponent>
// ===== contentChildren - Multiple Projected =====
tabs = contentChildren(TabComponent);
// Returns: Signal<readonly TabComponent[]>
// ===== Using with computed/effect =====
// Computed based on query results
itemCount = computed(() => this.allItems().length);
hasItems = computed(() => this.allItems().length > 0);
firstItemLabel = computed(() =>
this.firstItem()?.label() ?? 'No item'
);
// Effect when query results change
constructor() {
effect(() => {
const items = this.allItems();
console.log('Items changed:', items.length);
});
}
// ===== Methods using queries =====
focusSearch() {
this.searchBox()?.nativeElement.focus();
}
highlightAllItems() {
for (const item of this.allItems()) {
item.highlight();
}
}
}
// ===== No more ngAfterViewInit for queries! =====
// OLD way with decorators:
@Component({...})
export class OldComponent implements AfterViewInit {
@ViewChild('input') input!: ElementRef;
ngAfterViewInit() {
this.input.nativeElement.focus(); // Had to wait for this hook
}
}
// NEW way with signal queries:
@Component({...})
export class NewComponent {
input = viewChild.required<ElementRef>('input');
constructor() {
// Use effect - runs when view is ready and input exists
effect(() => {
const el = this.input();
el.nativeElement.focus();
});
}
}