Angular - @ViewChild in - Ngif - Stack Overflow
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?
Component.template.html:
65
Component.component.ts:
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)
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
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:
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
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
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
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
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:
display = false;
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
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):
...
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.
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:
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
}
})
}
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
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.
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
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
public ionViewDidLoad() {
this.loadMap();
});
private loadMap() {
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
public ngOnInit() {
this.loadMap();
});
private loadMap() {
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;
}
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
I think using defer from lodash makes a lot of sense especially in my case where my @ViewChild() was inside async pipe
for Angular 8 - a mixture of null checking and @ViewChild static: false hackery
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;
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();
});
https://stackoverflow.com/questions/39366981/viewchild-in-ngif 13/14
1/7/2021 angular - @ViewChild in *ngIf - Stack Overflow
https://stackoverflow.com/questions/39366981/viewchild-in-ngif 14/14