1. ViewChild:
- Used to access elements/components in the template of the current component
- Available after the view is initialized (
ngAfterViewInit) - Can reference elements by template reference variable or component type (Ex: #divElement)
- The
staticoption determines when the query is resolved.
// counter.component.ts
@Component({
selector: 'app-counter',
template: `<p>Count: {{ count }}</p>`
})
export class CounterComponent {
count = 0;
increment() {
this.count++;
}
}
// parent.component.ts
import { Component, ViewChild, ElementRef, AfterViewInit } from '@angular/core';
@Component({
selector: 'app-parent',
template: `
<!-- 1. Basic element reference -->
<div #greeting>Hello World</div>
<!-- 2. Component reference -->
<app-counter #counter></app-counter>
<!-- 3. Static reference (available in ngOnInit) -->
<input #staticInput type="text" value="Static Content">
<!-- 4. Dynamic reference (only available in ngAfterViewInit) -->
<div *ngIf="showDynamic">
<p #dynamicText>Dynamic Content</p>
</div>
<button (click)="showContent()">Show Content</button>
<button (click)="incrementCounter()">Increment Counter</button>
`
})
export class ParentComponent implements AfterViewInit {
showDynamic = false;
// 1. Basic element reference
@ViewChild('greeting')
greetingDiv!: ElementRef;
// 2. Component reference
@ViewChild('counter')
counterComponent!: CounterComponent;
// 3. Static reference available in ngOnInit
@ViewChild('staticInput', { static: true })
staticInput!: ElementRef;
// 4. Dynamic reference
@ViewChild('dynamicText')
dynamicText!: ElementRef;
ngOnInit() {
// Static content is available here
console.log('Static input value:', this.staticInput.nativeElement.value);
}
ngAfterViewInit() {
// 1. Accessing element reference
console.log('Greeting text:', this.greetingDiv.nativeElement.textContent);
// 2. Accessing component methods
this.counterComponent.increment();
// 3. Static content is still available
console.log('Static input in AfterViewInit:', this.staticInput.nativeElement.value);
// 4. Dynamic content might be undefined if not shown
if (this.dynamicText) {
console.log('Dynamic text:', this.dynamicText.nativeElement.textContent);
}
}
showContent() {
this.showDynamic = true;
}
incrementCounter() {
this.counterComponent.increment();
}
}
2. ContentChild:
- Used to access content projected into the component
- Available after content is initialised (ngAfterContentInit)
- Accesses the first matching element in projectedContent
- Useful for accessing elements passed via ng-content
3. ContentChildren:
- Similar to ContentChild but return multiple elements as QueryList
- Can query all instances of a component type or template refernce
- Provides real-time updates when content changes
- Includes methods like forEach, Map, filter on the QueryList
Example:
// form-field.component.ts
@Component({
selector: 'app-form-field',
template: `
<div class="form-group">
<label>{{ label }}</label>
<input [type]="type"
[formControlName]="fieldName"
class="form-control">
<span *ngIf="error" class="error">{{ error }}</span>
</div>
`
})
export class FormFieldComponent {
@Input() label: string = '';
@Input() type: string = 'text';
@Input() fieldName: string = '';
@Input() error: string = '';
}
// form-container.component.ts
@Component({
selector: 'app-form-container',
template: `
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<!-- Primary form field (ContentChild) -->
<ng-content select="[primary]"></ng-content>
<!-- Additional form fields (ContentChildren) -->
<ng-content></ng-content>
<div class="form-actions">
<button type="submit" [disabled]="!form.valid">Submit</button>
<button type="button" (click)="resetForm()">Reset</button>
</div>
</form>
`
})
export class FormContainerComponent implements AfterContentInit {
@Input() formName: string = '';
// Access primary form field using ContentChild
@ContentChild('primaryField')
primaryField!: FormFieldComponent;
// Access all form fields using ContentChildren
@ContentChildren(FormFieldComponent)
formFields!: QueryList<FormFieldComponent>;
form: FormGroup;
constructor(private fb: FormBuilder) {
this.form = this.fb.group({});
}
ngAfterContentInit() {
// Access primary field
console.log('Primary field:', this.primaryField);
// Initialize form controls based on projected fields
this.formFields.forEach(field => {
this.form.addControl(field.fieldName, new FormControl('', Validators.required));
});
// Listen for changes in form fields
this.formFields.changes.subscribe(fields => {
console.log('Form fields updated:', fields);
});
}
onSubmit() {
if (this.form.valid) {
console.log('Form values:', this.form.value);
}
}
resetForm() {
this.form.reset();
}
// Method to validate specific field
validateField(fieldName: string) {
const control = this.form.get(fieldName);
if (control && control.errors) {
const field = this.formFields.find(f => f.fieldName === fieldName);
if (field) {
field.error = 'This field is required';
}
}
}
}
// app.component.ts
@Component({
selector: 'app-root',
template: `
<app-form-container formName="userForm">
<!-- Primary field accessed via ContentChild -->
<app-form-field
#primaryField
primary
label="Email"
type="email"
fieldName="email">
</app-form-field>
<!-- Additional fields accessed via ContentChildren -->
<app-form-field
label="First Name"
type="text"
fieldName="firstName">
</app-form-field>
<app-form-field
label="Last Name"
type="text"
fieldName="lastName">
</app-form-field>
<app-form-field
label="Phone"
type="tel"
fieldName="phone">
</app-form-field>
</app-form-container>
`
})
export class AppComponent {}
Key differences:
- ViewChild queries elements in the component’s own template
- ContentChild queries elements projected into the component
- ViewChild is resolved after view initialization
- ContentChild is resolved after content initialization
Common use cases:
- ViewChild:
- Accessing DOM elements directly
- Calling methods on child components
- Manipulating view elements programmatically
- ContentChild/ContentChildren:
- Building reusable components that wrap projected content
- Creating container components
- Implementing advanced component communication