Ram_T
Ram_T

Reputation: 8484

How to get element after displaying it using *ngIf?

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?

Edit

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

Answers (4)

luiscla27
luiscla27

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.


1. ViewChild approach using a 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)
  }
}

2. ViewChildren approach using Observables

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.


3. ngAfterViewInit() as a component event

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

Vincent Gagnon
Vincent Gagnon

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

Marat Martirosyan
Marat Martirosyan

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

user4676340
user4676340

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

Related Questions