Level Up Your Angular Development: Effective Use of @ViewChild with static
@ViewChild
is a decorator used in Angular components to gain access to a DOM element or a component instance within the component's template.- It injects a reference to the element or component into the class property, allowing you to interact with it programmatically.
The static
Option Introduced in Angular 8
- Prior to Angular 8,
@ViewChild
queries would resolve asynchronously, sometimes in thengOnInit
lifecycle hook and sometimes inngAfterViewInit
, depending on the template structure and the presence of structural directives like*ngIf
or*ngFor
. - This inconsistency could lead to unexpected behavior in your code.
- To address this, Angular 8 introduced the
static
option for@ViewChild
. It specifies whether the query should be resolved synchronously or asynchronously.
When to Use static: true
- Use
static: true
when you need to access the@ViewChild
reference in thengOnInit
lifecycle hook. This is because static queries are resolved before change detection runs (beforengOnInit
). - Common scenarios for
static: true
include:- Creating embedded views dynamically using a
TemplateRef
obtained from@ViewChild
. Static queries are necessary here because creating a new view after change detection has run would cause an error. - Needing the
@ViewChild
reference early in the component's lifecycle for tasks that don't rely on dynamic template changes.
- Creating embedded views dynamically using a
When to Use static: false
(Default in Angular 9+)
- Use
static: false
(the default behavior in Angular 9 and later) in most cases. This ensures the query resolves after the view is initialized and any structural directives have been processed. - This is ideal when you need the
@ViewChild
reference to reflect changes in the template, such as elements conditionally rendered using*ngIf
.
Example:
import { Component, ViewChild, ElementRef, AfterViewInit, OnInit } from '@angular/core';
@Component({
selector: 'app-my-component',
template: `
<div #myDiv>This is my div.</div>
<button (click)="changeContent()">Change Content</button>
`
})
export class MyComponent implements AfterViewInit, OnInit {
@ViewChild('myDiv', { static: false }) myDiv: ElementRef; // Use static: false for dynamic elements
content = 'Initial content';
ngAfterViewInit() {
console.log('ViewChild available after view is initialized:', this.myDiv);
}
ngOnInit() {
// May or may not have access to ViewChild here depending on template structure
console.log('ViewChild potentially available in ngOnInit:', this.myDiv);
}
changeContent() {
this.content = 'Updated content';
}
}
Key Points:
- Choose
static: true
for early access inngOnInit
or for dynamic view creation. - Choose
static: false
(default) for access after view initialization and to reflect dynamic template changes. - Consider using
ngAfterViewInit
if you need the@ViewChild
reference after the view is fully initialized, regardless of thestatic
option.
import { Component, ViewChild, ElementRef, OnInit } from '@angular/core';
@Component({
selector: 'app-static-viewchild',
template: `
<div #myDiv>This is my div.</div>
`
})
export class StaticViewChildComponent implements OnInit {
@ViewChild('myDiv', { static: true }) myDiv: ElementRef;
ngOnInit() {
console.log('Accessing @ViewChild in ngOnInit:', this.myDiv.nativeElement.textContent); // Guaranteed access here
}
}
In this example, static: true
ensures the myDiv
reference is available in ngOnInit
, allowing you to manipulate the element's content directly.
Scenario 2: Dynamic View Creation with TemplateRef
(using static: true
):
import { Component, ViewChild, TemplateRef, ViewContainerRef } from '@angular/core';
@Component({
selector: 'app-dynamic-view',
template: `
<button (click)="showDynamicView()">Show Dynamic View</button>
<template #myTemplate>This is dynamic content.</template>
`
})
export class DynamicViewComponent {
@ViewChild('myTemplate', { static: true }) myTemplate: TemplateRef<any>;
isVisible = false;
constructor(private viewContainer: ViewContainerRef) {}
showDynamicView() {
this.isVisible = true;
const view = this.viewContainer.createEmbeddedView(this.myTemplate);
}
}
Here, static: true
is necessary because creating the embedded view after change detection would cause an error. The myTemplate
reference is used to dynamically create a view from the template defined in the component.
Scenario 3: Accessing @ViewChild
after View Initialization (using static: false
- default):
import { Component, ViewChild, ElementRef, AfterViewInit } from '@angular/core';
@Component({
selector: 'app-default-viewchild',
template: `
<div *ngIf="showDiv" #myDiv>This is my div.</div>
<button (click)="toggleDiv()">Toggle Div</button>
`
})
export class DefaultViewChildComponent implements AfterViewInit {
@ViewChild('myDiv', { static: false }) myDiv: ElementRef;
showDiv = true;
ngAfterViewInit() {
console.log('ViewChild available after view is initialized:', this.myDiv); // Guaranteed access here (after *ngIf processing)
}
toggleDiv() {
this.showDiv = !this.showDiv;
}
}
In this case, static: false
(the default in Angular 9+) is used because we need the @ViewChild
reference to reflect the dynamic presence of the element due to *ngIf
. The ngAfterViewInit
lifecycle hook is a good place to ensure the view is fully initialized before accessing @ViewChild
in scenarios like this.
- Template references provide a way to store references to specific parts of your template.
- They're declared using the
#
symbol followed by a name within the template element. - While not directly accessing the element, you can use these references to manipulate elements within event handlers or component logic.
<button #myButton (click)="handleClick(myButton)">Click me</button>
handleClick(buttonRef: any) {
buttonRef.nativeElement.style.backgroundColor = 'red';
}
Renderer2 (for DOM manipulation):
- In scenarios where you only need to perform basic DOM manipulations without requiring a full component reference, you can use the
Renderer2
service. - It provides methods for manipulating elements directly, such as adding/removing classes, setting styles, or creating/appending elements.
- However,
Renderer2
doesn't offer the same level of reusability or component encapsulation as@ViewChild
.
import { Component, Renderer2, ElementRef } from '@angular/core';
@Component({
selector: 'app-renderer-example',
template: `
<button (click)="changeColor()">Change Color</button>
`
})
export class RendererExampleComponent {
constructor(private renderer: Renderer2, private el: ElementRef) {}
changeColor() {
this.renderer.setStyle(this.el.nativeElement.querySelector('button'), 'background-color', 'red');
}
}
ngModel (for two-way data binding):
ngModel
is a directive used for two-way data binding between form controls and component properties.- While not directly accessing the DOM element, it offers a convenient way to manage the value of an input element and keep it synchronized with a property in your component.
<input type="text" [(ngModel)]="name">
name: string = '';
Choosing the Right Method:
- Use
@ViewChild
for most cases where you need direct access to a DOM element or component instance within your component's logic. - Consider template references for simpler scenarios where you only need to manipulate elements within event handlers.
- Use
Renderer2
cautiously for low-level DOM manipulation when@ViewChild
isn't suitable. - Use
ngModel
for two-way data binding with form controls.
angular angular8 viewchild