Manchmal reicht es nicht aus, Daten nur über Inputs in eine Child-Komponente zu geben. Wenn du Methoden einer Child-Komponente direkt aufrufen oder ein nacktes DOM-Element (wie ein <canvas> oder <input>) manipulieren musst, verwendest du in Angular Queries. Mit dem Einzug der Signals gibt es nun eine moderne, reaktive API dafür.
View Queries vs. Content Queries
Bevor wir in den Code schauen, ist es wichtig, die zwei Arten von Queries in Angular zu verstehen:
- View Queries (
viewChild/viewChildren): Suchen nach Elementen, die direkt im Template (der View) der aktuellen Komponente definiert sind. - Content Queries (
contentChild/contentChildren): Suchen nach Elementen, die von außen über Content Projection (<ng-content>) in die Komponente projiziert wurden.
Die moderne Signal-API
Angular bietet die Funktionen viewChild(), viewChildren(), contentChild() und contentChildren(). Sie alle geben ein reaktives Signal zurück. Das bedeutet, ihr Wert aktualisiert sich automatisch (z.B. wenn ein Element durch @if plötzlich erscheint), und du kannst sie problemlos in computed() oder effect() verwenden.
Ein Element abfragen (viewChild)
Du kannst nach dem Typ einer Child-Komponente suchen oder eine sogenannte Template Reference Variable (ein String, beginnend mit # im HTML) verwenden.
import { Component, viewChild, ElementRef, effect } from '@angular/core';
import { AlertComponent } from './alert.component';
@Component({
selector: 'app-dashboard',
template: `
<!-- 1. Suche über den Typen -->
<app-alert></app-alert>
<!-- 2. Suche über eine Template Reference Variable (#myCanvas) -->
<canvas #myCanvas></canvas>
`,
standalone: true,
imports: [AlertComponent]
})
export class DashboardComponent {
// Sucht nach der ersten Komponente vom Typ "AlertComponent"
// Typ: Signal<AlertComponent | undefined>
alertCmp = viewChild(AlertComponent);
// Sucht nach dem Element mit "#myCanvas"
canvasEl = viewChild<ElementRef<HTMLCanvasElement>>('myCanvas');
constructor() {
// Ein effect führt sich aus, sobald das Signal einen Wert hat
effect(() => {
const canvas = this.canvasEl()?.nativeElement;
if (canvas) {
const ctx = canvas.getContext('2d');
// ... zeichnen
}
});
}
closeAlert() {
// Direkter Aufruf einer Methode auf der Child-Komponente
this.alertCmp()?.close();
}
}Erzwungene Queries (.required)
Da ein Element durch ein @if versteckt sein könnte, ist der Rückgabetyp eines normalen viewChild immer optional (undefined). Weißt du jedoch sicher, dass das Element immer da ist (da es z.B. nicht in einem @if liegt), kannst du .required() anhängen. Angular wirft dann einen Fehler, falls es fehlt, und du sparst dir ständige ?. Checks.
// Typ: Signal<AlertComponent> (kein undefined mehr!)
alertCmp = viewChild.required(AlertComponent);Mehrere Elemente abfragen (viewChildren)
Sucht nach allen passenden Elementen und gibt ein Signal zurück, das ein Array enthält.
import { Component, viewChildren, computed } from '@angular/core';
@Component({
selector: 'app-list',
template: `
<app-item text="Item 1" />
<app-item text="Item 2" />
`
})
export class ListComponent {
// Signal<readonly ItemComponent[]>
items = viewChildren(ItemComponent);
// Reagiert automatisch, wenn Items hinzukommen oder verschwinden
itemCount = computed(() => this.items().length);
}Content Queries (contentChild)
Content Queries greifen auf Elemente zu, die via <ng-content> eingebunden werden. Beachte, dass View Queries nicht in projizierten Inhalten suchen können!
import { Component, contentChild, contentChildren } from '@angular/core';
@Component({
selector: 'app-accordion',
template: `
<div class="accordion-wrapper">
<ng-content></ng-content>
</div>
`
})
export class AccordionComponent {
// Greift auf die projizierten Elemente zu,
// z.B. <app-accordion> <app-panel /> <app-panel /> </app-accordion>
firstPanel = contentChild(PanelComponent);
allPanels = contentChildren(PanelComponent);
}Standardmäßig suchen Content Queries nur nach direkten Kind-Elementen. Willst du auch tiefere Verschachtelungen (Descendants) durchsuchen, musst du dies in den Optionen aktivieren:
contentChildren(PanelComponent, { descendants: true })
Klassische Dekorator-Queries (Legacy)
In älteren Codebases begegnen dir die Dekoratoren @ViewChild, @ViewChildren, @ContentChild und @ContentChildren.
Der größte Unterschied zu Signalen: Du musst auf den Lifecycle-Hook ngAfterViewInit (für View Queries) oder ngAfterContentInit (für Content Queries) warten, bevor die Eigenschaften mit Daten gefüllt sind. Vorher sind sie undefined.
import { Component, ViewChild, ViewChildren, QueryList, AfterViewInit } from '@angular/core';
@Component({
selector: 'app-legacy',
template: `<div #myDiv>Hallo</div> <app-item></app-item>`
})
export class LegacyComponent implements AfterViewInit {
@ViewChild('myDiv') myDivEl!: ElementRef<HTMLDivElement>;
// QueryList statt Array bei Children-Queries
@ViewChildren(ItemComponent) items!: QueryList<ItemComponent>;
ngAfterViewInit() {
// ERST HIER sind die Queries garantiert verfügbar
console.log(this.myDivEl.nativeElement.innerHTML);
console.log('Anzahl Items:', this.items.length);
// Auf Änderungen lauschen (Reaktivität)
this.items.changes.subscribe(() => {
console.log('Items haben sich geändert');
});
}
}Häufige Stolperfallen
static: true friert das Ergebnis ein
Bei Dekorator-Queries macht static: true das Ergebnis zwar schon in ngOnInit verfügbar – aktualisiert es danach aber nie wieder. Sobald das Ziel in @if/@for liegt oder dynamisch erscheint, brauchst du zwingend static: false (Default).
Signal-Queries lösen das Timing-Problem
viewChild() und contentChild() (seit v17.2) sind reaktive Signals und brauchen kein ngAfterViewInit mehr. Sie liefern undefined bis das Element existiert und aktualisieren sich von selbst – ideal für computed() und effect().
contentChildren findet nur direkte Kinder
Standardmäßig durchsuchen Content-Queries nicht den ganzen projizierten Baum. Für tiefere Verschachtelung musst du { descendants: true } setzen – sonst bleiben Listen unerwartet leer, sobald jemand einen Wrapper drumherum baut.
required: true statt ?.-Ketten
Wenn du sicher weißt, dass das Ziel immer im DOM ist, nimm viewChild.required(…). Der Rückgabetyp verliert das | undefined, und Angular wirft beim Fehlen sofort einen klaren Runtime-Error – besser als stille ?.-Kaskaden.
read-Option für anderes Token
Mit { read: ElementRef } oder { read: TemplateRef } bekommst du nicht die Komponenten-Instanz, sondern ein anderes DI-Token desselben Knotens. Klassischer Fall: eine Komponente abfragen, aber nur ihr nacktes ElementRef für DOM-Messungen ziehen.