How can I select dynamic elements rendered by *ngIf
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.
@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.
Related videos on Youtube
Comments
-
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.
- 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
.- @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 over 7 yearssorry for unclear sample code, in this example I tried to use ViewChild tag #button not ID button1
-
Joe over 7 yearsEven the expression is satisfied and the dom was rendered, I still can't get the element. Is there any way? Thanks.
-
Joe over 7 yearsconstructor( 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 over 7 yearsA 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 over 7 yearsHow 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 over 7 yearsYou can do this with other events as well.
-
Matthias247 over 7 yearsExactly. 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 over 7 yearsFor example, the AJAX callback give me a boolean 'expr'. And different template will be changed based on the value of expr.
-
Joe over 7 yearsI understood and will have a try. Thanks. @Matthias247
-
Joe over 7 yearsThanks. I will try this. But still want to have a look about other possible way mentioned in your answer.
-
Matthias247 over 7 years@Joe: I added more info regarding the
ViewChildren
approach