I am trying to learn Angular 2.
I would like to access to a child component from a parent component using the @ViewChild Annotation.
Here some lines of code:
In BodyContent.ts I have:
import { ViewChild, Component, Injectable } from 'angular2/core';
import { FilterTiles } from '../Components/FilterTiles/FilterTiles';
@Component({
selector: 'ico-body-content',
templateUrl: 'App/Pages/Filters/BodyContent/BodyContent.html',
directives: [FilterTiles]
})
export class BodyContent {
@ViewChild(FilterTiles) ft: FilterTiles;
public onClickSidebar(clickedElement: string) {
console.log(this.ft);
var startingFilter = {
title: 'cognomi',
values: [ 'griffin', 'simpson' ]
}
this.ft.tiles.push(startingFilter);
}
}
while in FilterTiles.ts:
import { Component } from 'angular2/core';
@Component({
selector: 'ico-filter-tiles',
templateUrl: 'App/Pages/Filters/Components/FilterTiles/FilterTiles.html'
})
export class FilterTiles {
public tiles = [];
public constructor(){};
}
Finally here the templates (as suggested in comments):
BodyContent.html
<div (click)="onClickSidebar()" class="row" style="height:200px; background-color:red;">
<ico-filter-tiles></ico-filter-tiles>
</div>
FilterTiles.html
<h1>Tiles loaded</h1>
<div *ngFor="#tile of tiles" class="col-md-4">
... stuff ...
</div>
FilterTiles.html template is correctly loaded into ico-filter-tiles tag (indeed I am able to see the header).
Note: the BodyContent
class is injected inside another template (Body) using DynamicComponetLoader: dcl.loadAsRoot(BodyContent, '#ico-bodyContent', injector)
:
import { ViewChild, Component, DynamicComponentLoader, Injector } from 'angular2/core';
import { Body } from '../../Layout/Dashboard/Body/Body';
import { BodyContent } from './BodyContent/BodyContent';
@Component({
selector: 'filters',
templateUrl: 'App/Pages/Filters/Filters.html',
directives: [Body, Sidebar, Navbar]
})
export class Filters {
constructor(dcl: DynamicComponentLoader, injector: Injector) {
dcl.loadAsRoot(BodyContent, '#ico-bodyContent', injector);
dcl.loadAsRoot(SidebarContent, '#ico-sidebarContent', injector);
}
}
The problem is that when I try to write ft
into the console log, I get undefined
, and of course I get an exception when I try to push something inside the "tiles" array: 'no property tiles for "undefined"'.
One more thing: FilterTiles
component seems to be correctly loaded, since I'm able to see the html template for it.
Any suggestions?
ft
wouldn't be set in the constructor, but in a click event handler it would be set already.
loadAsRoot
, which has a known issue with change detection. Just to make sure try using loadNextToLocation
or loadIntoLocation
.
loadAsRoot
. Once I replaced with loadIntoLocation
the problem was solved. If you make your comment as answer I can mark it as accepted
I had a similar issue and thought I'd post in case someone else made the same mistake. First, one thing to consider is AfterViewInit
; you need to wait for the view to be initialized before you can access your @ViewChild
. However, my @ViewChild
was still returning null. The problem was my *ngIf
. The *ngIf
directive was killing my controls component so I couldn't reference it.
import { Component, ViewChild, OnInit, AfterViewInit } from 'angular2/core';
import { ControlsComponent } from './controls/controls.component';
import { SlideshowComponent } from './slideshow/slideshow.component';
@Component({
selector: 'app',
template: `
<controls *ngIf="controlsOn"></controls>
<slideshow (mousemove)="onMouseMove()"></slideshow>
`,
directives: [SlideshowComponent, ControlsComponent],
})
export class AppComponent {
@ViewChild(ControlsComponent) controls: ControlsComponent;
controlsOn: boolean = false;
ngOnInit() {
console.log('on init', this.controls);
// this returns undefined
}
ngAfterViewInit() {
console.log('on after view init', this.controls);
// this returns null
}
onMouseMove(event) {
this.controls.show();
// throws an error because controls is null
}
}
Hope that helps.
EDIT
As mentioned by @Ashg below, a solution is to use @ViewChildren
instead of @ViewChild
.
The issue as previously mentioned is the ngIf
which is causing the view to be undefined. The answer is to use ViewChildren
instead of ViewChild
. I had similar issue where I didn't want a grid to be shown until all the reference data had been loaded.
html:
<section class="well" *ngIf="LookupData != null">
<h4 class="ra-well-title">Results</h4>
<kendo-grid #searchGrid> </kendo-grid>
</section>
Component Code
import { Component, ViewChildren, OnInit, AfterViewInit, QueryList } from '@angular/core';
import { GridComponent } from '@progress/kendo-angular-grid';
export class SearchComponent implements OnInit, AfterViewInit
{
//other code emitted for clarity
@ViewChildren("searchGrid")
public Grids: QueryList<GridComponent>
private SearchGrid: GridComponent
public ngAfterViewInit(): void
{
this.Grids.changes.subscribe((comps: QueryList <GridComponent>) =>
{
this.SearchGrid = comps.first;
});
}
}
Here we are using ViewChildren
on which you can listen for changes. In this case any children with the reference #searchGrid
. Hope this helps.
this.SearchGrid
properties you should use syntax like setTimeout(()=>{ ///your code here }, 1);
to avoid Exception: Expression has changed after it was checked
*ngIf
works, and after render we can save a ElementRef from the dinamic components.
You could use a setter for @ViewChild()
@ViewChild(FilterTiles) set ft(tiles: FilterTiles) {
console.log(tiles);
};
If you have an ngIf wrapper, the setter will be called with undefined, and then again with a reference once ngIf allows it to render.
My issue was something else though. I had not included the module containing my "FilterTiles" in my app.modules. The template didn't throw an error but the reference was always undefined.
FilterTiles
. I've encountered that issue for that reason before.
@ViewChild('paginator', {static: false})
What solved my problem was to make sure static
was set to false
.
@ViewChild(ClrForm, {static: false}) clrForm;
With static
turned off, the @ViewChild
reference gets updated by Angular when the *ngIf
directive changes.
{static:false}
is default (as of Angular9 iirc)
This worked for me.
My component named 'my-component', for example, was displayed using *ngIf="showMe" like so:
<my-component [showMe]="showMe" *ngIf="showMe"></my-component>
So, when the component is initialized the component is not yet displayed until "showMe" is true. Thus, my @ViewChild references were all undefined.
This is where I used @ViewChildren and the QueryList that it returns. See angular article on QueryList and a @ViewChildren usage demo.
You can use the QueryList that @ViewChildren returns and subscribe to any changes to the referenced items using rxjs as seen below. @ViewChild does not have this ability.
import { Component, ViewChildren, ElementRef, OnChanges, QueryList, Input } from '@angular/core';
import 'rxjs/Rx';
@Component({
selector: 'my-component',
templateUrl: './my-component.component.html',
styleUrls: ['./my-component.component.css']
})
export class MyComponent implements OnChanges {
@ViewChildren('ref') ref: QueryList<any>; // this reference is just pointing to a template reference variable in the component html file (i.e. <div #ref></div> )
@Input() showMe; // this is passed into my component from the parent as a
ngOnChanges () { // ngOnChanges is a component LifeCycle Hook that should run the following code when there is a change to the components view (like when the child elements appear in the DOM for example)
if(showMe) // this if statement checks to see if the component has appeared becuase ngOnChanges may fire for other reasons
this.ref.changes.subscribe( // subscribe to any changes to the ref which should change from undefined to an actual value once showMe is switched to true (which triggers *ngIf to show the component)
(result) => {
// console.log(result.first['_results'][0].nativeElement);
console.log(result.first.nativeElement);
// Do Stuff with referenced element here...
}
); // end subscribe
} // end if
} // end onChanges
} // end Class
Hope this helps somebody save some time and frustration.
.take(1).subscribe()
, but excellent answer, thank you so much!
My solution to this was to replace *ngIf
with [hidden]
. Downside was all the child components were present in the code DOM. But worked for my requirements.
My workaround was to use [style.display]="getControlsOnStyleDisplay()"
instead of *ngIf="controlsOn"
. The block is there but it is not displayed.
@Component({
selector: 'app',
template: `
<controls [style.display]="getControlsOnStyleDisplay()"></controls>
...
export class AppComponent {
@ViewChild(ControlsComponent) controls:ControlsComponent;
controlsOn:boolean = false;
getControlsOnStyleDisplay() {
if(this.controlsOn) {
return "block";
} else {
return "none";
}
}
....
In my case, I had an input variable setter using the ViewChild
, and the ViewChild
was inside of an *ngIf
directive, so the setter was trying to access it before the *ngIf
rendered (it would work fine without the *ngIf
, but would not work if it was always set to true with *ngIf="true"
).
To solve, I used Rxjs to make sure any reference to the ViewChild
waited until the view was initiated. First, create a Subject that completes when after view init.
export class MyComponent implements AfterViewInit {
private _viewInitWaiter$ = new Subject();
ngAfterViewInit(): void {
this._viewInitWaiter$.complete();
}
}
Then, create a function that takes and executes a lambda after the subject completes.
private _executeAfterViewInit(func: () => any): any {
this._viewInitWaiter$.subscribe(null, null, () => {
return func();
})
}
Finally, make sure references to the ViewChild use this function.
@Input()
set myInput(val: any) {
this._executeAfterViewInit(() => {
const viewChildProperty = this.viewChild.someProperty;
...
});
}
@ViewChild('viewChildRefName', {read: MyViewChildComponent}) viewChild: MyViewChildComponent;
It must work.
But as Günter Zöchbauer said there must be some other problem in template. I have created kinda Relevant-Plunkr-Answer. Pleas do check browser's console.
boot.ts
@Component({
selector: 'my-app'
, template: `<div> <h1> BodyContent </h1></div>
<filter></filter>
<button (click)="onClickSidebar()">Click Me</button>
`
, directives: [FilterTiles]
})
export class BodyContent {
@ViewChild(FilterTiles) ft:FilterTiles;
public onClickSidebar() {
console.log(this.ft);
this.ft.tiles.push("entered");
}
}
filterTiles.ts
@Component({
selector: 'filter',
template: '<div> <h4>Filter tiles </h4></div>'
})
export class FilterTiles {
public tiles = [];
public constructor(){};
}
It works like a charm. Please double check your tags and references.
Thanks...
For me using ngAfterViewInit
instead of ngOnInit
fixed the issue :
export class AppComponent implements OnInit {
@ViewChild('video') video;
ngOnInit(){
// <-- in here video is undefined
}
public ngAfterViewInit()
{
console.log(this.video.nativeElement) // <-- you can access it here
}
}
My solution to this was to move the ngIf from outside of the child component to inside of the child component on a div that wrapped the whole section of html. That way it was still getting hidden when it needed to be, but was able to load the component and I could reference it in the parent.
This works for me, see the example below.
import {Component, ViewChild, ElementRef} from 'angular2/core'; @Component({ selector: 'app', template: ` Toggle
I had a similar issue, where the ViewChild
was inside of a switch
clause that wasn't loading the viewChild element before it was being referenced. I solved it in a semi-hacky way but wrapping the ViewChild
reference in a setTimeout
that executed immediately (i.e. 0ms)
A kind of generic approach:
You can create a method that will wait until ViewChild
will be ready
function waitWhileViewChildIsReady(parent: any, viewChildName: string, refreshRateSec: number = 50, maxWaitTime: number = 3000): Observable<any> {
return interval(refreshRateSec)
.pipe(
takeWhile(() => !isDefined(parent[viewChildName])),
filter(x => x === undefined),
takeUntil(timer(maxWaitTime)),
endWith(parent[viewChildName]),
flatMap(v => {
if (!parent[viewChildName]) throw new Error(`ViewChild "${viewChildName}" is never ready`);
return of(!parent[viewChildName]);
})
);
}
function isDefined<T>(value: T | undefined | null): value is T {
return <T>value !== undefined && <T>value !== null;
}
Usage:
// Now you can do it in any place of your code
waitWhileViewChildIsReady(this, 'yourViewChildName').subscribe(() =>{
// your logic here
})
If an *ngIf="show" prevents a ViewChild from being rendered and you need the ViewChild right after your show
turns true, it helped me to fire ChangeDetectorRef.detectChanges() immediately after I set show
true.
After that the *ngIf creates the component and renders the ViewChild, s.t. you can use it afterwards. Just typed a quick sample code.
@ViewChild(MatSort) sort: MatSort;
constructor(private cdRef: ChangeDetectorRef) {}
ngOnInit() {
this.show = false;
this.someObservable()
.pipe(
tap(() => {
this.show = true;
this.cdRef.detectChanges();
})
)
.subscribe({
next: (data) => {
console.log(sort)
this.useResult(data);
}
});
}
Is this bad, or why has no one proposed it?
Use [hidden] instead of *ngif because *ngif kills your code when the condition is not satisfied.
<div [hidden]="YourVariable">
Show Something
</div>
just adding {static: true} to @View Solves my problem.
@ViewChild(FilterTiles, { static : true }) ft: FilterTiles;
I fix it just adding SetTimeout after set visible the component
My HTML:
<input #txtBus *ngIf[show]>
My Component JS
@Component({
selector: "app-topbar",
templateUrl: "./topbar.component.html",
styleUrls: ["./topbar.component.scss"]
})
export class TopbarComponent implements OnInit {
public show:boolean=false;
@ViewChild("txtBus") private inputBusRef: ElementRef;
constructor() {
}
ngOnInit() {}
ngOnDestroy(): void {
}
showInput() {
this.show = true;
setTimeout(()=>{
this.inputBusRef.nativeElement.focus();
},500);
}
}
In my case, I knew the child component would always be present, but wanted to alter the state prior to the child initializing to save work.
I choose to test for the child until it appeared and make changes immediately, which saved me a change cycle on the child component.
export class GroupResultsReportComponent implements OnInit {
@ViewChild(ChildComponent) childComp: ChildComponent;
ngOnInit(): void {
this.WhenReady(() => this.childComp, () => { this.childComp.showBar = true; });
}
/**
* Executes the work, once the test returns truthy
* @param test a function that will return truthy once the work function is able to execute
* @param work a function that will execute after the test function returns truthy
*/
private WhenReady(test: Function, work: Function) {
if (test()) work();
else setTimeout(this.WhenReady.bind(window, test, work));
}
}
Alertnatively, you could add a max number of attempts or add a few ms delay to the setTimeout
. setTimeout
effectively throws the function to the bottom of the list of pending operations.
For me the problem was I was referencing the ID on the element.
@ViewChild('survey-form') slides:IonSlides;
<div id="survey-form"></div>
Instead of like this:
@ViewChild('surveyForm') slides:IonSlides;
<div #surveyForm></div>
If you're using Ionic you'll need to use the ionViewDidEnter()
lifecycle hook. Ionic runs some additional stuff (mainly animation-related) which typically causes unexpected errors like this, hence the need for something that runs after ngOnInit
, ngAfterContentInit
, and so on.
For Angular: Change *ngIf with display style 'block' or 'none' in HTML.
selector: 'app',
template: `
<controls [style.display]="controlsOn ? 'block' : 'none'"></controls>
<slideshow (mousemove)="onMouseMove()"></slideshow>
`,
directives: [SlideshowComponent, ControlsComponent]
Here's something that worked for me.
@ViewChild('mapSearch', { read: ElementRef }) mapInput: ElementRef;
ngAfterViewInit() {
interval(1000).pipe(
switchMap(() => of(this.mapInput)),
filter(response => response instanceof ElementRef),
take(1))
.subscribe((input: ElementRef) => {
//do stuff
});
}
So I basically set a check every second until the *ngIf
becomes true and then I do my stuff related to the ElementRef
.
I had a similar issue in which a ViewChild
was inside a conditionally (*ngIf
) rendered component. Which would become rendered on the response of an api call. The response came later than when the @ViewChild
decorator was executed and so the desired component reference stayed undefined (null). After using {static: false}
the @ViewChild
decorator wasn't fired again even when the desired component was visible after some (small) amount of time. This was against the 'promise' of Angular 😢 (as stated in other answers in this thread)
The reason for this was ChangeDetectionStrategy
was set to OnPush
😧. When changing this to ChangeDetectionStrategy.Default
all worked as expected.
Conclusion:
✅ Use { static: false } & ✅ ChangeDetectionStrategy.Default
for @ViewChild
components that are conditionally (*ngIf) rendered to get their reference "later on" (when they become rendered)
ChangeDetectionStrategy.OnPush
is very performant. If that was the default strategy used, it must have been thoroughly thought by the authors that wrote code before you. And also {static: false}
is the default option available. If that was set to true
, the logic must have been executing within oninit
and hence that was necessary. This problem could have probably been solved by triggering change detection manually. Helpful article on change detection: mokkapps.de/blog/…
I resolved this issue with the help of change detection along with delayed initialization of view container reference.
HTML setup:
<ng-container *ngIf="renderMode === 'modal'" [ngTemplateOutlet]="renderModal">
</ng-container>
<ng-container *ngIf="renderMode === 'alert'" [ngTemplateOutlet]="renderAlert">
</ng-container>
<ng-template #renderModal>
<div class="modal">
<ng-container appSelector></ng-container>
</div>
</ng-template>
<ng-template #renderAlert>
<div class="alert">
<ng-container appSelector></ng-container>
</div>
</ng-template>
Component:
@ViewChild(SelectorDirective, { static: true }) containerSelector!: SelectorDirective;
constructor(private cdr: ChangeDetectorRef) { }
ngOnInit(): void {
// step: 1
this.renderMode = someService.someMethod();
// step: 2
this.cdr.markForCheck();
// step: 3
const viewContainerRef = this.containerSelector?.viewContainerRef;
if (viewContainerRef) {
// logic...
}
}
Modified the code such that condition on which HTML is dependent on (*ngIf), should update first Once the condition is updated, manually trigger ChangeDetection Get the reference from ViewChild after manual cdr trigger and proceed ahead with logic.
Apart from the other answers you can also use the last life-cycle hook:
ngAfterViewChecked() {}
ngAfterViewChecked
is called even after ngAfterViewInit
Life-cycle hooks: https://angular.io/guide/lifecycle-hooks#lifecycle-event-sequence
The solution which worked for me was to add the directive in declarations in app.module.ts
Success story sharing
ngClass
+hidden
class instead ofngIf
. That worked. Thanks!