Interogările îți dau acces la elemente, componente sau directive din template. Din v19 sunt semnale: viewChild() caută în propriul template, contentChild() caută în conținutul proiectat de părinte (ce e pus între tag-urile componentei). Pentru că sunt semnale, le poți folosi direct în computed() și effect(), fără ngAfterViewInit și fără QueryList.changes.
| Caută în | Rezultat | |
|---|---|---|
viewChild | template-ul componentei | Signal<T | undefined> |
viewChildren | template-ul componentei | Signal<readonly T[]> |
contentChild | conținutul proiectat (ng-content) | Signal<T | undefined> |
contentChildren | conținutul proiectat | Signal<readonly T[]> |
viewChild și viewChildren
import { Component, ElementRef, viewChild, viewChildren, afterNextRender } from '@angular/core';
@Component({
selector: 'app-search-box',
template: `
<input #search />
<app-results />
@for (item of items(); track item.id) { <li #row>{{ item.name }}</li> }
`,
})
export class SearchBox {
// după template reference variable
private readonly search = viewChild.required<ElementRef<HTMLInputElement>>('search');
// după tipul componentei
private readonly results = viewChild(Results); // Signal<Results | undefined>
// toate potrivirile
private readonly rows = viewChildren<ElementRef<HTMLLIElement>>('row'); // Signal<readonly ElementRef[]>
constructor() {
// ✗ În constructor view-ul nu există încă: this.search() ar arunca (required)
afterNextRender(() => this.search().nativeElement.focus()); // ✓ după prima randare
}
protected readonly rowCount = computed(() => this.rows().length); // ✓ reactiv
}Opțiunea read
// `read` alege CE obții de la elementul găsit
readonly input = viewChild('field', { read: ElementRef }); // elementul DOM
readonly model = viewChild('field', { read: NgModel }); // o directivă de pe el
readonly host = viewChild('slot', { read: ViewContainerRef }); // pentru componente dinamice
readonly tpl = viewChild('tpl', { read: TemplateRef }); // un <ng-template>
readonly inj = viewChild(Child, { read: Injector }); // v22.2: injectorul copiluluicontentChildren: componente care colaborează
Pattern-ul clasic: un container (Tabs) și copii declarativi (Tab). Părintele îi scrie în HTML, containerul îi descoperă cu contentChildren și decide cum îi afișează.
// Tabs + Tab: containerul descoperă copiii pe care i-a pus PĂRINTELE
@Component({
selector: 'app-tab',
template: `<ng-template #content><ng-content /></ng-template>`,
})
export class Tab {
readonly title = input.required<string>();
readonly content = viewChild.required<TemplateRef<unknown>>('content');
}
@Component({
selector: 'app-tabs',
imports: [NgTemplateOutlet],
template: `
@for (tab of tabs(); track tab; let i = $index) {
<button [class.active]="i === active()" (click)="active.set(i)">{{ tab.title() }}</button>
}
@if (current(); as tab) {
<ng-container [ngTemplateOutlet]="tab.content()" />
}
`,
})
export class Tabs {
protected readonly tabs = contentChildren(Tab); // Signal<readonly Tab[]>
protected readonly active = signal(0);
protected readonly current = computed(() => this.tabs()[this.active()]);
}Numele, emailul și avatarul utilizatorului.
descendants și contentChild
// contentChildren caută implicit doar copiii DIRECȚI din conținutul proiectat.
// Cu descendants: true caută la orice adâncime (de ex. în interiorul unui <div> wrapper).
readonly items = contentChildren(MenuItem, { descendants: true });
// contentChild pentru un singur element (ex. un template opțional pentru header)
readonly headerTpl = contentChild<TemplateRef<unknown>>('header');Înainte: decoratori și QueryList
// ✗ API-ul vechi
@ViewChild('search') search!: ElementRef<HTMLInputElement>;
@ViewChildren(RowComponent) rows!: QueryList<RowComponent>;
@ContentChildren(Tab) tabs!: QueryList<Tab>;
ngAfterViewInit() {
this.search.nativeElement.focus();
this.rows.changes.subscribe(() => this.updateCount()); // abonare manuală la schimbări
}Pentru conținutul primit din exterior, vezi Content projection; pentru lucrul cu DOM-ul după randare, afterNextRender din Lifecycle & DestroyRef.
Când DA, când NU
- Focus, scroll, măsurători DOM (cu
afterNextRender). - Integrare cu librării care au nevoie de un element (grafice, editoare).
- Componente compuse: Tabs/Tab, Menu/MenuItem, Accordion/Panel.
- Obținerea unui
ViewContainerRefpentru componente dinamice.
- Ca să citești/scrii starea unui copil → folosește input/output/model.
- Ca să apelezi metode pe copil pentru lucruri care pot fi declarative (ex. „copil.refresh()” la fiecare schimbare) → trimite un input.
- Ca să modifici clase/stiluri → binding-uri în template.
Capcane frecvente
- Citire prea devreme: în constructor interogările nu au valoare; un
.requiredaruncăNG0951. - Element sub
@if:viewChilddevineundefinedcând blocul e ascuns — nu folosi.requiredacolo. - Măsurători DOM în
computed(ca în demo) funcționează, dar nu sunt reactive la redimensionări — pentru asta ai nevoie deResizeObserver.
Fișă rapidă
viewChild('ref') // Signal<T | undefined>
viewChild.required('ref') // Signal<T> (aruncă dacă e citit înainte să existe)
viewChild(MyComp) // după tip
viewChildren('ref' | MyComp) // Signal<readonly T[]>
contentChild(...) / contentChildren(..., { descendants })
{ read: ElementRef | TemplateRef | ViewContainerRef | Directivă | Injector }
// migrare: ng generate @angular/core:signal-queries