How can I select dynamic elements rendered by *ngIf

10,054

Solution 1

You can't get the element when the *ngIf="expr" expression is false because then the element doesn't exist.

The value is not yet set in ngOnInit(), only when ngAfterViewInit() is called.

Plunker example

@Component({
  selector: 'my-app',
  template: `
    <div class="test" *ngIf="prop">
      <a #button id="button1">button1</a>
    </div>
    <div class="test" *ngIf="!boolean">
      <a id="button2">button2</a>
    </div>`
 ,
})
export class App {
  @ViewChild('button') button: ElementRef;

  prop:boolean = true;

  ngAfterViewInit() {
    console.log(this.button);
  }
}

Plunker example with ViewChildren

@Component({
  selector: 'my-app',
  template: `
    <button (click)="prop = !prop">toggle</button>
    <div class="test" *ngIf="prop">
      <a #button id="button1">button1</a>
    </div>
    <div class="test" *ngIf="!boolean">
      <a #button id="button2">button2</a>
    </div>`
 ,
})
export class App {
  @ViewChildren('button') button: QueryList<ElementRef>;

  prop:boolean = true;

  ngAfterViewInit() {
    console.log(this.button.toArray());
    this.button.changes.subscribe(val => {
      console.log(this.button.toArray());
    });

  }
}

Solution 2

If you need the element only in the moment that someone interacts with it (e.g. clicks on it) or with a sibling or child element of it you can pass it's reference with the event.

Template:

<div class="test" *ngIf="expr">
  <a #b id="button1" (click)="onButton1(b)">button1</a>
</div>

Code:

onButton1(button: HTMLButtonElement) {
}

If you need the element without interaction with it you might also look at the ViewChildren query instead of ViewChild.

You can set it up with

@ViewChildren('button') buttons: QueryList<ElementRef>;    

You can then subscribe to changes of elements that match a selector through this.buttons.changes.subscribe(...). If the element get's created or deleted you will get notified through the subscription. However my first way involves far less boilerplate code.

Alternativly you can also only access the QueryList synchronously in moments where you are sure that the element exists (through some preconditions). In your example you should be able to retrieve the button with

let button: ElementRef = this.buttons.first;

in these cases.

Solution 3

Is not exactly the best but I deal with a similar situation using [ngClass] instead *ngIf. Just create a class with "display: none" and hide the elements when needed. The elements selected with @ViewChild can be accessed without problem. For now you can use this little "hack" while search for a better solution or more elegant one. ex.

.hide-element{
    display: none;
}

<div class="test" [ngClass]="expr === 'yourTest' ? 'hide-element' : null">
  <a #b id="button1" (click)="onButton1(b)">button1</a>
</div>

if you are handling some async return you can just use async pipe

<div class="test" [ngClass]="(expr | async) === 'yourTest' ? 'hide-element' : null">
      <a #b id="button1" (click)="onButton1(b)">button1</a>
</div>

Hope it helps.

Share:
10,054

Related videos on Youtube

Joe
Author by

Joe

Coding is interesting :)

Updated on July 21, 2022

Comments

  • Joe
    Joe almost 2 years

    As the code provided bellow. I tried to select a dynamic element generated by ngIf but failed.

    I used two ways in total.

    1. ElementRef and querySelector

    component template:

        `<div class="test" *ngIf="expr">
          <a id="button">Value 1</a>
        </div>
        <div class="test" *ngIf="!expr">
          <a id="button">Value 2</a>
        </div>`
    

    component class:

        expr: boolean;
    
        constructor(
          private elementRef: ElementRef,
        ) {
        }
    
        ngOnInit(): void{
        //Call Ajax and set the value of this.expr based on the callback;
        //if expr == true, then show text Value 1; 
        //if expr == false, then show text Value 2;
        }
        ngAfterViewInit(): void{
          console.log(this.elementRef.nativeElement.querySelector('#button'));
        }
    

    The output result is null.

    1. @ViewChild

    component template:

        `<div class="test" *ngIf="expr">
          <a #button>Value 1</a>
        </div>
        <div class="test" *ngIf="!expr">
          <a #button>Value 2</a>
        </div>`
    

    component class:

        @ViewChild('button') button: elementRef;
    
        expr: boolean;
    
        ngOnInit(): void{
        //Call Ajax and set the value of this.expr based on the callback;
        //if expr == true, then show text Value 1; 
        //if expr == false, then show text Value 2;
        }
    
        ngAfterViewInit(): void{
          console.log(this.button);
        }
    

    The out put result is undefined;

    Is there a way to get dynamic dom generated by *ngIf?


    Finally the problem has been solved through @ViewChildren.

    And to log the updated result, it is necessary to use a separate function.

    For example:

    Wrong Code:

        @ViewChildren('button') buttons: ElementRef;
    
        function(): void{
          this.expr = true; // Change expression then the DOM will be changed;
          console.log(this.buttons.toArray()); // This is wrong because you will still get the old result;
        }
    

    Right Code:

        @ViewChildren('button') buttons: ElementRef;
    
        function(): void{
          this.expr = true; // Change expression then the DOM will be changed;
        }
        ngAfterViewInit(): void{
          this.buttons.changes.subscribe( e => console.log(this.buttons.toArray()) ); // This is right and you will get the latest result;
        }
    
  • Joe
    Joe over 7 years
    sorry for unclear sample code, in this example I tried to use ViewChild tag #button not ID button1
  • Joe
    Joe over 7 years
    Even the expression is satisfied and the dom was rendered, I still can't get the element. Is there any way? Thanks.
  • Joe
    Joe over 7 years
    constructor( private elementRef: ElementRef ) {} can only get the init DOM. For example, after the ajaxcall, the DOM will be changed but the elementRef doesn't. So that I'm trying to find a way to declare elementRef after ajax call.
  • Joe
    Joe over 7 years
    A good thinking. But the reason I want to get this dynamic DOM is to create observable includes not only click but also other event. Still thanks.
  • Günter Zöchbauer
    Günter Zöchbauer over 7 years
    How does it change? The ElementRef passed to the constructor is the host element of the component or directive, there is no way to move a component to a different element - at least I never heard of such a thing. How do you modify the DOM after the AJAX call?
  • Günter Zöchbauer
    Günter Zöchbauer over 7 years
    You can do this with other events as well.
  • Matthias247
    Matthias247 over 7 years
    Exactly. This is why I wrote "interacts with it". Or even with another element in the if block. But the example with a click is easiest to understand.
  • Joe
    Joe over 7 years
    For example, the AJAX callback give me a boolean 'expr'. And different template will be changed based on the value of expr.
  • Joe
    Joe over 7 years
    I understood and will have a try. Thanks. @Matthias247
  • Joe
    Joe over 7 years
    Thanks. I will try this. But still want to have a look about other possible way mentioned in your answer.
  • Matthias247
    Matthias247 over 7 years
    @Joe: I added more info regarding the ViewChildren approach