Reputation: 8484
I'm facing this issue frequently. I have an element as shown
<div class="element-1" *ngIf="isShown"></div>
by default, isShown = false;
and by clicking an element, I'm making isShown = true;
Now, in the same click callback function If I try to get element-1
as
$('.element-1')
, I am not getting that element because it might not in the DOM immediately when the isShown = true
.
I am able to get the same using ngAfterContentChecked
. But ngAfterContentChecked
called many times.
So, how can I get the element by not using ngAfterContentChecked
?
This is my element
<app-card-kpi-inline-overlay #kpisOverlay class="child-component all-kpis-overlay-wrap {{selectedView}}" [style.left.px]="kpiLeft" *ngIf="data['openKpiDetails']==true" [cardData]="data"></app-card-kpi-inline-overlay>
This is my ts method code
@ViewChild('kpisOverlay') kpisOverlay: ElementRef;
showKpiSection(i, event, card) {
card['openKpiDetails'] = !card['openKpiDetails'];
event.stopPropagation();
if (card['openKpiDetails']) {
setTimeout(() => {
const el: HTMLElement = this.kpisOverlay.nativeElement;
console.log(el); // always showing undefined
}, 0);
}
}
I am trying to toggle the flag. But the console always printing undefined
.
Below is my toggle element
<div (click)="showKpiSection(i, $event, data)">Get element</div>
Upvotes: 8
Views: 11896
Reputation: 6449
You can take advantage of @ViewChild
and @ViewChildren
behaviour:
Property decorator that configures a view query. The change detector looks for the first element or the directive matching the selector in the view DOM. If the view DOM changes, and a new child matches the selector, the property is updated.
Or, as a general recommendation, when possible, prefer the 3rd approach over the others. As it is way more natural to use a custom EventEmitter
, Angular is intended to be used like that.
setter
The important part is If the view DOM changes which means that in this case this'll only be triggered when the element is created or destroyed.
First declare a variable name for the element, for the sample i used #element1
<div #element1 class="element-1" *ngIf="isShown"></div>
Then add a @ViewChild
reference inside your component:
@ViewChild('element1') set element1(element) {
if (element) {
// here you get access only when element is rendered (or destroyed)
}
}
Another solution is to subscribe to
@ViewChildren
change observable, instead of using @ViewChild
put it like this:
@ViewChildren('element1')
private element1: QueryList<any>;
And then subscribe to it change observable:
element1.changes.subscribe((d: QueryList<any>) => {
if (d.length) {
// here you get access only when element is rendered
}
});
I've preferred the ViewChildren
way because to me it was easier to handle observables than validations inside setter's
, also this approach is closer to the "Event" concept.
Add an event triggered inside the ngAfterViewInit
method of your child component (when possible, prefer this approach over the others).
Child component app-card-kpi-inline-overlay
:
// declare the event
@Output()
public created= new EventEmitter<any>();
// later on, trigger the event every time the component is rendered
ngAfterViewInit() {
this.created.next(this);
}
Parent component:
<app-card-kpi-inline-overlay #kpisOverlay (created)="onComponentCreated($event)"></app-card-kpi-inline-overlay>
onComponentCreated(elem: CardKpiInlineOverlayComponent) {
// here you get access only when element is rendered
}
Upvotes: 7
Reputation: 698
You should define your element like this:
<div #element-1 class="element-1" *ngIf="isShown"></div>
And then access it with:
@ViewChild('element-1') element1;
in your Component. The example on this page looks close to what you are trying to do: https://angular.io/api/core/ViewChild
Upvotes: 0
Reputation: 1
For me, it works when I remove property static
from viewChild
decorator
@ViewChild('mainTextBox', {read: ElementRef, static: true}) public parentTextBoxElement: ElementRef<HTMLElement>;
to
@ViewChild('mainTextBox', {read: ElementRef}) public parentTextBoxElement: ElementRef<HTMLElement>;
Upvotes: -1
Reputation:
I will never say it enough :
Using JQuery with Angular is an anti-pattern. You should not touch the DOM yourself, you should let the framework do it for you.
Now, in a full ANgular way :
<div #firstElement *ngIf="isShown"></div>
In your TS :
@ViewChild('firstElement') firstElement: ElementRef;
If you want the element, in your function, once the value of isShown
is set to true, use this
const el: HTMLElement = this.firstElement.nativeElement;
If it doesn't work, trigger a change detection, because it means Angular hasn't finished detecting the changes yet.
EDIT Since your component is in a loop, you should use @ViewChildren
instead.
I made a working StackBlitz for you, look at the code, it's basically the same principle, only that the element becomes an array of elements.
Upvotes: 7