3 Effective Strategies to Use @ViewChild with *ngIf in Your Angular Applications
In Angular, @ViewChild
is a decorator that allows you to access a child component or element within the template of a parent component. It injects a reference to the child element or component into a property of the parent component's class. This is useful when you need to interact with the child's properties or methods from the parent.
Challenges with @ViewChild
and *ngIf
- Timing:
*ngIf
conditionally shows or hides elements based on a boolean expression. When*ngIf
isfalse
, the child element might not be created immediately, leading to@ViewChild
returningundefined
if you try to access it before the child is available.
Solutions
Here are common approaches to address this challenge:
-
Using
ngAfterViewInit
Lifecycle Hook:- The
ngAfterViewInit
lifecycle hook is called after the component's view initialization, ensuring the child element exists. - Within
ngAfterViewInit
, you can safely access the@ViewChild
reference.
import { Component, AfterViewInit, ViewChild } from '@angular/core'; @Component({ selector: 'app-parent', template: ` <div *ngIf="showChild"> <app-child #childRef></app-child> </div> ` }) export class ParentComponent implements AfterViewInit { @ViewChild('childRef') childRef: any; showChild = true; ngAfterViewInit() { if (this.childRef) { console.log('Child element is available:', this.childRef); } } }
- The
-
Setting
@ViewChild
tostatic: false
(Optional):- By default,
@ViewChild
performs a static query, meaning it searches for the element during the initial component creation. - Setting
static: false
tells Angular to perform a view query, which re-evaluates whenever the view changes, including when*ngIf
toggles the child element's visibility.
@ViewChild('childRef', { static: false }) childRef: any;
Note: This approach might have a slight performance impact due to more frequent queries. Use it only if
ngAfterViewInit
doesn't meet your needs. - By default,
-
Alternative: Using
[hidden]
or[style.display]
(Consideration):- If you only need to hide/show the child element without completely removing it from the DOM, consider using
[hidden]
or[style.display]
. This way, the element is still created, and@ViewChild
can access it.
<div *ngIf="showChild" [hidden]="!showChild"> </div>
- If you only need to hide/show the child element without completely removing it from the DOM, consider using
Choosing the Right Approach
- The
ngAfterViewInit
approach is generally recommended as it provides a clear hook to access the child when it's ready. - Use
static: false
with caution ifngAfterViewInit
doesn't work for your specific scenario, considering the potential performance trade-off. - The
[hidden]
or[style.display]
option is a viable alternative if you just need to control visibility without removing the element.
import { Component, AfterViewInit, ViewChild } from '@angular/core';
@Component({
selector: 'app-parent',
template: `
<div *ngIf="showChild">
<app-child #childRef></app-child>
</div>
`
})
export class ParentComponent implements AfterViewInit {
@ViewChild('childRef') child: ChildComponent; // Replace `any` with the actual child component type
showChild = true;
ngAfterViewInit() {
if (this.child) {
console.log('Child element is available:', this.child);
// You can now access the child's properties or methods
this.child.doSomething();
}
}
}
@Component({
selector: 'app-child',
template: `
<p>I am the child component!</p>
`
})
export class ChildComponent {
doSomething() {
console.log('Child component doing something!');
}
}
Explanation:
- We define two components:
ParentComponent
andChildComponent
. - In
ParentComponent
, we have a@ViewChild
decorated propertychildRef
that references the child component with the template reference variable#childRef
. - The
showChild
property controls the visibility of the child element using*ngIf
. - Inside
ngAfterViewInit
, we check ifchild
(which becomes a reference to the child component instance) is available. - If available, we can access the child's methods or properties, like calling
doSomething()
in this example.
import { Component, ViewChild } from '@angular/core';
@Component({
selector: 'app-parent',
template: `
<div *ngIf="showChild">
<app-child #childRef></app-child>
</div>
`
})
export class ParentComponent {
@ViewChild('childRef', { static: false }) child: ChildComponent;
showChild = true;
// You can access the child here, but might be undefined before the view is initialized
}
// ... ChildComponent definition (same as Example 1)
- We set
static: false
in the@ViewChild
decorator, instructing Angular to perform a view query. - You can access the child reference (
child
) here, but it might beundefined
initially since the view hasn't fully loaded.
- Use Example 1 (with
ngAfterViewInit
) for most cases as it ensures the child is available before accessing it. - Use Example 2 (with
static: false
) cautiously ifngAfterViewInit
doesn't suit your needs, considering potential performance implications.
-
Using
Template Reference Variable
(Limited Use):- If you only need to access the child element within the template itself (not from the component class), you can use a template reference variable directly within the element's tag.
<app-child #childRef *ngIf="showChild"></app-child> <button (click)="doSomethingWithChild(childRef)">Do Something with Child</button>
- The
#childRef
reference variable stores the child element. - The
(click)
event handler on the button can accesschildRef
directly to perform actions on the child.
Limitation: This approach is limited as you cannot access the child from the component class itself.
-
Using
ContentChildren
for Multiple Elements (For Multiple Child Elements):- If you need to access multiple child elements or components, consider using
@ContentChildren
instead of@ViewChild
.@ContentChildren
provides a query list of all child elements or components matching the selector.
import { Component, AfterContentChecked, ContentChildren } from '@angular/core'; import { MyChildComponent } from './my-child.component'; @Component({ selector: 'app-parent', template: ` <div *ngIf="showChildren"> <app-child #child></app-child> <app-child></app-child> </div> ` }) export class ParentComponent implements AfterContentChecked { @ContentChildren(MyChildComponent) children: QueryList<MyChildComponent>; showChildren = true; ngAfterContentChecked() { if (this.children) { // Access all child components in the QueryList this.children.forEach(child => console.log(child)); } } }
- We import
ContentChildren
andAfterContentChecked
. @ContentChildren(MyChildComponent)
decorates a propertychildren
that holds aQueryList
ofMyChildComponent
child components.ngAfterContentChecked
is called after content initialization, ensuring the children are available.- We can iterate through the
children
QueryList to access each child component.
Use Case: This is useful when you have multiple child components that you need to interact with collectively in the parent component.
- If you need to access multiple child elements or components, consider using
angular angular2-changedetection viewchild