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>
Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Manzur Khan
  • 2,366
  • 4
  • 23
  • 44
  • 2
    please explain more details and try to paste some codes !!! – Arash May 31 '18 at 06:41
  • 1
    There's enough details and code there for me. –  May 31 '18 at 06:45
  • @Arash There's no need adding more code and making it complex, I just need an event trigger when an element becomes visible with ngIf – Manzur Khan May 31 '18 at 06:48
  • 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. – Arash May 31 '18 at 06:50
  • 1
    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. – Християн Христов May 31 '18 at 06:52
  • I have some complex ngIf which is also dependent on in-template variables – Manzur Khan May 31 '18 at 08:35

4 Answers4

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
  }
}
  • 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! – Manzur Khan May 31 '18 at 07:22
  • That's another issue, you can either use other hooks, or use a timeout –  May 31 '18 at 07:24
  • 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`. – Guss Jul 20 '21 at 13:56
  • 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`. – Guss Jul 20 '21 at 14:03
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;
}
  • 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 condition – Manzur Khan May 31 '18 at 07:23
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.

1

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

Cozmin
  • 36
  • 2