You are on page 1of 14

1/7/2021 angular - @ViewChild in *ngIf - Stack Overflow

@ViewChild in *ngIf
Asked 4 years, 4 months ago Active 24 days ago Viewed 110k times

Question
259 What is the most elegant way to get @ViewChild after corresponding element in template was shown?

Below is an example. Also Plunker available.

Component.template.html:
65

<div id="layout" *ngIf="display">


<div #contentPlaceholder></div>
</div>

Component.component.ts:

export class AppComponent {

display = false;
@ViewChild('contentPlaceholder', { read: ViewContainerRef }) viewContainerRef;

show() {
this.display = true;
console.log(this.viewContainerRef); // undefined
setTimeout(() => {
console.log(this.viewContainerRef); // OK
}, 1);
}
}

I have a component with its contents hidden by default. When someone calls show() method it becomes visible. However, before Angular 2
change detection completes, I can not reference to viewContainerRef . I usually wrap all required actions into setTimeout(()=>{},1) as shown
above. Is there a more correct way?

https://stackoverflow.com/questions/39366981/viewchild-in-ngif 1/14
1/7/2021 angular - @ViewChild in *ngIf - Stack Overflow

I know there is an option with ngAfterViewChecked , but it causes too much useless calls.

ANSWER (Plunker)

angular angular2-changedetection viewchild

edited Sep 5 '20 at 3:03 asked Sep 7 '16 at 10:08


ng-hobby sinedsem
1,615 2 9 20 3,872 5 22 40

3 did you try using [hidden] attribute instead of *ngIf? It worked for me for a similar situation. – Shardul Jan 27 '17 at 21:39

15 Answers Active Oldest Votes

Use a setter for the ViewChild:

393 private contentPlaceholder: ElementRef;

@ViewChild('contentPlaceholder') set content(content: ElementRef) {


if(content) { // initially setter gets called with undefined
this.contentPlaceholder = content;
}
}

The setter is called with an element reference once *ngIf becomes true .

Note, for Angular 8 you have to make sure to set { static: false } , which is a default setting in other Angular versions:

@ViewChild('contentPlaceholder', { static: false })

Note: if contentPlaceholder is a component you can change ElementRef to your component Class:

https://stackoverflow.com/questions/39366981/viewchild-in-ngif 2/14
1/7/2021 angular - @ViewChild in *ngIf - Stack Overflow

private contentPlaceholder: MyCustomComponent;

@ViewChild('contentPlaceholder') set content(content: MyCustomComponent) {


if(content) { // initially setter gets called with undefined
this.contentPlaceholder = content;
}
}

edited Dec 14 '20 at 10:07 answered Dec 12 '16 at 7:04


ssuperczynski parliament
2,325 2 35 56 16.9k 32 129 218

27 note that this setter is called initially with undefined content, so check for null if doing something in the setter – Recep Jun 30 '17 at 9:10

1 Good answer, but contentPlaceholder is ElementRef not ViewContainerRef . – developer033 Sep 9 '17 at 17:49

7 How do you call the setter? – Leandro Cusack Nov 6 '18 at 19:12

2 @LeandroCusack it gets called automatically when Angular finds <div #contentPlaceholder></div> . Technically you can call it manually like any other setter
this.content = someElementRef but I don't see why you would want to do that. – parliament Nov 14 '18 at 15:05

3 Just a helpful note for anyone who comes across this now - you need to have @ViewChild('myComponent', {static: false}) where the key bit is the static: false,
which allows it to take different inputs. – nospamthanks Jan 27 '20 at 15:23

An alternative to overcome this is running the change detector manually.

123 You first inject the ChangeDetectorRef :

constructor(private changeDetector : ChangeDetectorRef) {}

Then you call it after updating the variable that controls the *ngIf

show() {
this.display = true;
this.changeDetector.detectChanges();
}

https://stackoverflow.com/questions/39366981/viewchild-in-ngif 3/14
1/7/2021 angular - @ViewChild in *ngIf - Stack Overflow

answered Sep 4 '17 at 20:46


Jefferson Lima
3,402 2 19 24

2 Thanks! I was using the accepted answer but it was still causing an error because the children were still undefined when I tried to use them sometime after
onInit() , so I added the detectChanges before calling any child function and it fixed it. (I used both the accepted answer and this answer) – kebab-case Oct
25 '19 at 14:52

Super helpful! Thanks! – AppDreamer Mar 17 '20 at 0:53

I had to run the CDR as well, the ViewChild was not updated soon enough when I needed it. This may happen if you rely on the child in the same function as
you update the *ngIf property. In that case, the changes may not have been detected yet and the ViewChild property may still be undefined. – andreas Oct 2
'20 at 20:25

Any ideas why I might be getting this error when trying to call detectChanges(): ERROR TypeError: Cannot read property 'detectChanges' of undefined – J.D.
Oct 5 '20 at 15:06

Angular 8+

64 You should add { static: false } as a second option for @ViewChild . This causes the query results to be resolved after change detection runs,
allowing your @ViewChild to be updated after the value changes.

Example:

export class AppComponent {


@ViewChild('contentPlaceholder', { static: false }) contentPlaceholder: ElementRef;

display = false;

constructor(private changeDetectorRef: ChangeDetectorRef) {


}

show() {
this.display = true;
this.changeDetectorRef.detectChanges(); // not required
console.log(this.contentPlaceholder);
}
}

https://stackoverflow.com/questions/39366981/viewchild-in-ngif 4/14
1/7/2021 angular - @ViewChild in *ngIf - Stack Overflow

Stackblitz example: https://stackblitz.com/edit/angular-d8ezsn

edited Feb 11 '20 at 3:04 answered Aug 15 '19 at 10:45


Michael Ziluck Sviatoslav Oleksiv
450 4 14 1,615 11 11

3 Thank you Sviatoslav. Tried everything above but only your solution worked. – Peter Drinnan Aug 22 '19 at 1:23

This also worked for me (as did the viewchildren trick). This one is more intuitive and easier for angular 8. – Alex Aug 28 '19 at 21:06

2 This should be the accepted answer for the latest version. – Krishna Prashatt Sep 30 '19 at 11:51

1 The text of the answer is missing the fact that you have to call detectChanges which does not seem like something you should do, I would much rather have a
setter and not have to inject extra cruft into my component. Not to mention the two comments above saying it doesn't work... so I don't agree that this should be
the accepted answer, it's an alternative. – Juan Mendes Jan 17 '20 at 11:33

1 Probably the best solution for Angular 8+, but this.changeDetectorRef.detectChanges(); is indeed required – mirushaki Mar 29 '20 at 11:19

The answers above did not work for me because in my project, the ngIf is on an input element. I needed access to the nativeElement attribute
in order to focus on the input when ngIf is true. There seems to be no nativeElement attribute on ViewContainerRef. Here is what I did (following
21 @ViewChild documentation):

<button (click)='showAsset()'>Add Asset</button>


<div *ngIf='showAssetInput'>
<input #assetInput />
</div>

...

private assetInputElRef:ElementRef;
@ViewChild('assetInput') set assetInput(elRef: ElementRef) {
this.assetInputElRef = elRef;
}

...

showAsset() {
this.showAssetInput = true;
setTimeout(() => { this.assetInputElRef.nativeElement.focus(); });
}
https://stackoverflow.com/questions/39366981/viewchild-in-ngif 5/14
1/7/2021 angular - @ViewChild in *ngIf - Stack Overflow

I used setTimeout before focusing because the ViewChild takes a sec to be assigned. Otherwise it would be undefined.

answered Apr 25 '17 at 22:53


zebraco
263 2 9

2 A setTimeout() of 0 worked for me. My element hidden by my ngIf was correctly bound after a setTimeout, without the need for the set assetInput() function in
the middle. – Will Shaver Sep 6 '17 at 21:41

You can detectChanges in showAsset() and not have to use the timeout. – WrksOnMyMachine Jun 12 '19 at 21:36

How's this an answer? The OP already mentioned using a setTimeout ? I usually wrap all required actions into setTimeout(()=>{},1) as shown
above. Is there a more correct way? – Juan Mendes Jan 17 '20 at 11:35

As was mention by others, the fastest and quickest solution is to use [hidden] instead of *ngIf. Taking this approach the component will be
created but not visible, therefore you have access to it. This might not be the most efficient way.
13
edited Oct 13 '20 at 6:36 answered Jan 29 '17 at 9:55
Neoheurist user3728728
2,036 3 24 44 695 2 10 13

1 you have to note that using "[hidden]" may not work if the element is not of "display: block". better use [style.display]="condition ? '' : 'none'" – Félix Brunet May 8
'19 at 19:29

This could work but I don't know if it's convenient for your case:

12 @ViewChildren('contentPlaceholder', {read: ViewContainerRef}) viewContainerRefs:


QueryList;

ngAfterViewInit() {
this.viewContainerRefs.changes.subscribe(item => {
if(this.viewContainerRefs.toArray().length) {
// shown
https://stackoverflow.com/questions/39366981/viewchild-in-ngif 6/14
1/7/2021 angular - @ViewChild in *ngIf - Stack Overflow
}
})
}

edited Dec 12 '16 at 7:06 answered Sep 7 '16 at 10:21


Günter Zöchbauer
463k 140 1644
1357

1 Can you please try ngAfterViewInit() instead of ngOnInit() . I assumed that viewContainerRefs is already initialized but doesn't yet contain items.
Seems I remembered this wrong. – Günter Zöchbauer Sep 7 '16 at 10:37

Sorry, I was wrong. AfterViewInit actually works. I've removed all my comments in order not to confuse people. Here is a working Plunker:
plnkr.co/edit/myu7qXonmpA2hxxU3SLB?p=preview – sinedsem Sep 7 '16 at 11:30

1 This is actually a good answer. It works and I'm using this now. Thanks! – Konstantin Jul 11 '19 at 16:14

1 This worked for me after upgrade from angular 7 to 8. For some reason, the upgrade caused the component to be undefined in afterViewInit even with using
static: false per the new ViewChild syntax when the component was wrapped in an ngIf. Also note that the QueryList requires a type now like this
QueryList<YourComponentType>; – Alex Aug 28 '19 at 21:01

Might be the change related to the const parameter of ViewChild – Günter Zöchbauer Aug 29 '19 at 3:14

Another quick "trick" (easy solution) is just to use [hidden] tag instead of *ngIf, just important to know that in that case Angular build the
object and paint it under class:hidden this is why the ViewChild work without a problem. So it's important to keep in mind that you should
9 not use hidden on heavy or expensive items that can cause performance issue

<div class="addTable" [hidden]="CONDITION">

answered Jan 12 '19 at 17:19


Or Yaacov
2,751 2 12 34

If that hidden is inside in another if then need to change many things – VIKAS KOHLI Dec 19 '19 at 7:15

https://stackoverflow.com/questions/39366981/viewchild-in-ngif 7/14
1/7/2021 angular - @ViewChild in *ngIf - Stack Overflow

My goal was to avoid any hacky methods that assume something (e.g. setTimeout) and I ended up implementing the accepted solution with a
bit of RxJS flavour on top:
6
private ngUnsubscribe = new Subject();
private tabSetInitialized = new Subject();
public tabSet: TabsetComponent;
@ViewChild('tabSet') set setTabSet(tabset: TabsetComponent) {
if (!!tabSet) {
this.tabSet = tabSet;
this.tabSetInitialized.next();
}
}

ngOnInit() {
combineLatest(
this.route.queryParams,
this.tabSetInitialized
).pipe(
takeUntil(this.ngUnsubscribe)
).subscribe(([queryParams, isTabSetInitialized]) => {
let tab = [undefined, 'translate', 'versions'].indexOf(queryParams['view']);
this.tabSet.tabs[tab > -1 ? tab : 0].active = true;
});
}

My scenario: I wanted to fire an action on a @ViewChild element depending on the router queryParams . Due to a wrapping *ngIf being false
until the HTTP request returns the data, the initialization of the @ViewChild element happens with a delay.

How does it work: combineLatest emits a value for the first time only when each of the provided Observables emit the first value since the
moment combineLatest was subscribed to. My Subject tabSetInitialized emits a value when the @ViewChild element is being set. Therewith, I
delay the execution of the code under subscribe until the *ngIf turns positive and the @ViewChild gets initialized.

Of course don't forget to unsubscribe on ngOnDestroy, I do it using the ngUnsubscribe Subject:

ngOnDestroy() {
this.ngUnsubscribe.next();
this.ngUnsubscribe.complete();
}

https://stackoverflow.com/questions/39366981/viewchild-in-ngif 8/14
1/7/2021 angular - @ViewChild in *ngIf - Stack Overflow

edited May 23 '18 at 19:16 answered May 23 '18 at 18:59


Filip Juncu
313 2 9

1 thanks a lot I've had the same issue, with tabSet & ngIf, your method saved me a lot of time and headache. Cheers m8 ;) – Exitl0l Dec 5 '19 at 10:41

A simplified version, I had a similar issue to this when using the Google Maps JS SDK.

3 My solution was to extract the div and ViewChild into it's own child component which when used in the parent component was able to be
hid/displayed using an *ngIf .

Before

HomePageComponent Template

<div *ngIf="showMap">
<div #map id="map" class="map-container"></div>
</div>

HomePageComponent Component

@ViewChild('map') public mapElement: ElementRef;

public ionViewDidLoad() {
this.loadMap();
});

private loadMap() {

const latLng = new google.maps.LatLng(-1234, 4567);


const mapOptions = {
center: latLng,
zoom: 15,
mapTypeId: google.maps.MapTypeId.ROADMAP,
};
this.map = new google.maps.Map(this.mapElement.nativeElement, mapOptions);
}

https://stackoverflow.com/questions/39366981/viewchild-in-ngif 9/14
1/7/2021 angular - @ViewChild in *ngIf - Stack Overflow

public toggleMap() {
this.showMap = !this.showMap;
}

After

MapComponent Template

<div>
<div #map id="map" class="map-container"></div>
</div>

MapComponent Component

@ViewChild('map') public mapElement: ElementRef;

public ngOnInit() {
this.loadMap();
});

private loadMap() {

const latLng = new google.maps.LatLng(-1234, 4567);


const mapOptions = {
center: latLng,
zoom: 15,
mapTypeId: google.maps.MapTypeId.ROADMAP,
};
this.map = new google.maps.Map(this.mapElement.nativeElement, mapOptions);
}

HomePageComponent Template

<map *ngIf="showMap"></map>

HomePageComponent Component

https://stackoverflow.com/questions/39366981/viewchild-in-ngif 10/14
1/7/2021 angular - @ViewChild in *ngIf - Stack Overflow

public toggleMap() {
this.showMap = !this.showMap;
}

answered Nov 18 '18 at 13:11


Eugene
213 2 8

It Work for me if i use ChangeDetectorRef in Angular 9

1 @ViewChild('search', {static: false})


public searchElementRef: ElementRef;

constructor(private changeDetector: ChangeDetectorRef) {}

//then call this when this.display = true;


show() {
this.display = true;
this.changeDetector.detectChanges();
}

answered May 12 '20 at 4:36


Ivan Sim
95 1 9

In my case I needed to load a whole module only when the div existed in the template, meaning the outlet was inside an ngif. This way
everytime angular detected the element #geolocalisationOutlet it created the component inside of it. The module only loads once as well.
1
constructor(
public wlService: WhitelabelService,
public lmService: LeftMenuService,
private loader: NgModuleFactoryLoader,
private injector: Injector
) {
}

https://stackoverflow.com/questions/39366981/viewchild-in-ngif 11/14
1/7/2021 angular - @ViewChild in *ngIf - Stack Overflow

@ViewChild('geolocalisationOutlet', {read: ViewContainerRef}) set geolocalisation(geolocalisationOutlet: ViewContainerRef) {


const path = 'src/app/components/engine/sections/geolocalisation/geolocalisation.module#GeolocalisationModule';
this.loader.load(path).then((moduleFactory: NgModuleFactory<any>) => {
const moduleRef = moduleFactory.create(this.injector);
const compFactory = moduleRef.componentFactoryResolver
.resolveComponentFactory(GeolocalisationComponent);
if (geolocalisationOutlet && geolocalisationOutlet.length === 0) {
geolocalisationOutlet.createComponent(compFactory);
}
});
}

<div *ngIf="section === 'geolocalisation'" id="geolocalisation">


<div #geolocalisationOutlet></div>
</div>

answered Mar 12 '19 at 13:27


Gabb1995
681 5 18

I think using defer from lodash makes a lot of sense especially in my case where my @ViewChild() was inside async pipe

1 answered Jun 12 '19 at 16:10


Timo
81 1 5

for Angular 8 - a mixture of null checking and @ViewChild static: false hackery

0 for a paging control waiting for async data

@ViewChild(MatPaginator, { static: false }) set paginator(paginator: MatPaginator) {


if(!paginator) return;
paginator.page.pipe(untilDestroyed(this)).subscribe(pageEvent => {
const updated: TSearchRequest = {
pageRef: pageEvent.pageIndex,
https://stackoverflow.com/questions/39366981/viewchild-in-ngif 12/14
1/7/2021 angular - @ViewChild in *ngIf - Stack Overflow
pageSize: pageEvent.pageSize
} as any;
this.dataGridStateService.alterSearchRequest(updated);
});
}

answered Dec 20 '19 at 13:55


jenson-button-event
15.4k 7 72 140

Working on Angular 8 No need to import ChangeDector

0 ngIf allows you not to load the element and avoid adding more stress to your application. Here's how I got it running without ChangeDetector

elem: ElementRef;

@ViewChild('elemOnHTML', {static: false}) set elemOnHTML(elemOnHTML: ElementRef) {


if (!!elemOnHTML) {
this.elem = elemOnHTML;
}
}

Then when I change my ngIf value to be truthy I would use setTimeout like this for it to wait only for the next change cycle:

this.showElem = true;
console.log(this.elem); // undefined here
setTimeout(() => {
console.log(this.elem); // back here through ViewChild set
this.elem.do();
});

This also allowed me to avoid using any additional libraries or imports.

answered Sep 6 '19 at 1:54


Manuel BM
739 12 16

https://stackoverflow.com/questions/39366981/viewchild-in-ngif 13/14
1/7/2021 angular - @ViewChild in *ngIf - Stack Overflow

Just make sur that the static option is set to false

-1 @ViewChild('contentPlaceholder', {static: false}) contentPlaceholder: ElementRef;

answered Jul 21 '20 at 11:25


Smaillns
1,137 8 17

https://stackoverflow.com/questions/39366981/viewchild-in-ngif 14/14

You might also like