Trigger event when element becomes visible with ngIf
Asked Answered
C

4

16

I'm having some divs with ngIf, I just want to have a way to know if the particular div is the one which is visible/active right now like an event trigger like focus (it doesn't work) or something, and with this event, I will set a variable or something.

<div *ngIf="test === true" (focus)="myVariable = true">
</div>
Cervantes answered 31/5, 2018 at 6:38 Comment(6)
please explain more details and try to paste some codes !!!Pickens
There's enough details and code there for me.Wendellwendi
@Pickens There's no need adding more code and making it complex, I just need an event trigger when an element becomes visible with ngIfCervantes
As far as you are using html elements you can't have control of their visibility actions , you are making them visible or !visible. you can use you'r custom component instead of div element, then do whatever you want on onInit() of you'r custom elements.Pickens
You can use behaviorsubject and subscribe for its changes, so in the html you will use ngIf*="yourSubject | async" , and in your .ts file you can put your display logic, or trigger some functions.Biogen
I have some complex ngIf which is also dependent on in-template variablesCervantes
W
6

Your div will be rendered and visible once the change detection is triggered. When a change is detected, the whole lifecycle is ran again.

If you want to run something, you should hook on one of the events of the lifecycle. I suggest AfterViewInit.

Now that you know how, let's see what you should do.

In your case, you should create div with template references. This will allow you to have a reference to the element and make you able to check which div is shown or hidden.

Here is a stackblitz that shows you how it works, and here is the code :

import { Component, ViewChildren, QueryList, ElementRef } from '@angular/core';

@Component({
  selector: 'my-app',
  template: `
  <div *ngFor="let item of [0, 1, 2, 3, 4, 5]; let i = index">
    <span *ngIf="i === show" #shownDiv [id]="'div-' + i">{{ item }}</span>
  </div>
  `
})
export class AppComponent {
  name = 'Angular 6';
  show = 0;

  @ViewChildren('shownDiv') divs: QueryList<ElementRef>;

  ngOnInit() {
    setInterval(() => {
      this.show++;
      if (this.show > 5) {
        this.show = 0;
      }
    }, 1000);
  }

  ngAfterViewChecked() {
    let shown = this.divs.find(div => !!div);
    console.log('DIV shown is ' + (shown.nativeElement as HTMLSpanElement).id);
    // Now that you know which div is shown, you can run whatever piece of code you want
  }
}
Wendellwendi answered 31/5, 2018 at 6:53 Comment(4)
Thanks for the elaborate answer, your solution seems to work for me, but I get ngAfterCheck error like this one: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked As I'm doing the change in the afterViewChecked lifecycle!Cervantes
That's another issue, you can either use other hooks, or use a timeoutWendellwendi
This worked for me well. The important point is to use ngAfterViewChecked() to implement additional checks on the existence of the child elements as they are being toggled on and off by ngIf.Twowheeler
Implementing changes to values in ngAfterViewChecked() in response to changes in visibility of elements caused me to get ExpressionChangedAfterItHasBeenCheckedError errors. To prevent that - if you modify your content in ngAfterViewChecked(), make sure to trigger change detection again using a ChangeDetectorRef.Twowheeler
F
3

This can be a possible work around. It might not be the best one but will work.

In html file,

<div *ngIf="show()"> </div>

In component TS file,

show(){
  if(test){ //condition for showing the div
    myVariable = true; 
    return true;
  }
  else
    return false;
}
Fragonard answered 31/5, 2018 at 7:5 Comment(1)
Yea, I can surely set my variable with this, but the problem here is show() gets called innumerable times based on all the conditions, that defeats the purpose since it only considers the last condition and that's not the active conditionCervantes
P
3

A solution would be to use @ViewChildren and to subscribe to the changes Observable of QueryList in ngAfterViewInit(), also to avoid ExpressionChangedAfterItHasBeenCheckedError ( this happens if for example you want to change a property that is used in the template when the div is visible) you can use detectChanges() of ChangeDetectorRef like this:

@Component({
  selector: "my-app",
  template: `
    <div *ngIf="num % 10 === 0" #doSomethingWhenVisibleDIV>
      Show some content
    </div>
    <div *ngIf="showOtherDiv">Show different content here</div>
  `,
  styleUrls: ["./app.component.css"]
})
export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
  num: number = 0;
  showOtherDiv: boolean = false;

  private subscription: Subscription;

  @ViewChildren("doSomethingWhenVisibleDIV") divs: QueryList<ElementRef>;

  constructor(private changeDetectorRef: ChangeDetectorRef) {}

  ngOnInit() {
    setInterval(() => this.num++, 1000);
  }

  ngAfterViewInit() {
    this.subscription = this.divs.changes.subscribe({
      next: () => {
        if (this.divs.length > 0) {
          this.showOtherDiv = !this.showOtherDiv;
          this.changeDetectorRef.detectChanges();
        }
      }
    });
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}

Stackblitz example

Persian answered 4/4, 2021 at 10:30 Comment(0)
W
2

I would like to build on Rachit's answer.

<div *ngIf="test"><ng-container *ngIf="runShow && show()"></ng-container></div>

and in the component

this.runShow = true;

//show always returns true.
show() {
  //Return if not running. Shouldn't be needed as runShow proceeds show in the template.
  if(!this.runShow) {
    return true;
  }
  //Do modifications...

  this.runShow = false;
  return true;

show() will only run if test is truthy, and will turn itself off after a single iteration (of course, you can change this.runShow to be based off something). An important gotcha is that until this.runShow == false, this will run every time the component detects a change, no more and no less. We put the show() inside its own ng-container so that it doesn't impact the DOM and is run after the test is rendered.

Windtight answered 3/7, 2019 at 20:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.