In angular, how do I detect if a certain element is in view?
For example, I have the following:
<div class="test">Test</div>
Is there a way to detect when this div is in view?
Thanks.
In angular, how do I detect if a certain element is in view?
For example, I have the following:
<div class="test">Test</div>
Is there a way to detect when this div is in view?
Thanks.
 
    
    Based off this answer, adapted to Angular:
Template:
<div #testDiv class="test">Test</div>
Component:
  @ViewChild('testDiv', {static: false}) private testDiv: ElementRef<HTMLDivElement>;
  isTestDivScrolledIntoView: boolean;
  @HostListener('window:scroll', ['$event'])
  isScrolledIntoView(){
    if (this.testDiv){
      const rect = this.testDiv.nativeElement.getBoundingClientRect();
      const topShown = rect.top >= 0;
      const bottomShown = rect.bottom <= window.innerHeight;
      this.isTestDivScrolledIntoView = topShown && bottomShown;
    }
  }
Example with scroll event binding
Another nice feature is to determine how much of that <div> is to be considered as "within view". Here's a reference to such implementation.
 
    
    Here is a directive that you can use. It uses the shiny IntersectionObserver API
import {AfterViewInit, Directive, TemplateRef, ViewContainerRef} from '@angular/core'
@Directive({
  selector: '[isVisible]',
})
/**
 * IS VISIBLE DIRECTIVE
 * --------------------
 * Mounts a component whenever it is visible to the user
 * Usage: <div *isVisible>I'm on screen!</div>
 */
export class IsVisible implements AfterViewInit {
  constructor(private vcRef: ViewContainerRef, private tplRef: TemplateRef<any>) {
  }
  ngAfterViewInit() {
    const observedElement = this.vcRef.element.nativeElement.parentElement
    const observer = new IntersectionObserver(([entry]) => {
      this.renderContents(entry.isIntersecting)
    })
    observer.observe(observedElement)
  }
  renderContents(isIntersecting: boolean) {
    this.vcRef.clear()
    if (isIntersecting) {
      this.vcRef.createEmbeddedView(this.tplRef)
    }
  }
}
<div *isVisible>I'm on screen!</div>
 
    
    I had the same task in one of the latest projects I've worked on and ended up using a npm package, which provides a working directive out of the box. So if someone doesn't feel like writing and testing directives, like me, check out this npm package ng-in-view. It works like a charm in Angular 14. 
I hope that this post will help someone to save some time writing directives.
 
    
    I ended up creating a slightly vanilla directive (though it uses the @HostListener decorator and stuff). If the height of the directive-bound element is fully within the viewport, it emits a boolean value ($event) of true, otherwise it returns false. It does this through JS getBoundingClientRect() method; where it can then take the rect element's position and do some quick/simple math with the viewport's height against the top/bottom position of the element.
inside-viewport.directive.ts:
import { Directive, ElementRef, EventEmitter, HostListener, Output } from '@angular/core';
@Directive({
  selector: '[insideViewport]'
})
export class InsideViewportDirective {
  @Output() insideViewport = new EventEmitter();
  constructor(
    private elementRef: ElementRef;
  ) { }
  @HostListener('body:scroll', ['$event'])
  public onScrollBy(): any {
    const windowHeight = window.innerHeight;
    const boundedRect = this.elementRef.nativeElement.getBoundingClientRect();
    if (boundedRect.top >= 0 && boundedRect.bottom <= windowHeight) {
      this.insideViewport.emit(true);
    } else {
      this.insideViewport.emit(false);
    }
  }
}
Then, in the template app.component.html, I added an additional second argument (string), to follow the first $event arg (which emits the boolean value) so that the component could identify which element was scrolled into view, given that you can apply this directive to multiple elements:
<div (insideViewport)="onElementView($event, 'blockOne')">
  <span *ngIf="showsText"> I'm in view! </span>
</div>
And in the component (aka the "do stuff" part) app.component.ts, now we can receive the boolean value to drive the behavior of the conditional component property; while also qualifying the element as the first block (aka blockOne) in a view that might have multiple "blocks":
// ... other code
export class AppComponent implements OnInit {
  showsText!: boolean;
  //... other code
  onElementView(value: any, targetString: string): void {
    if (value === true && targetString === 'blockOne') {
      this.showsText = true;
    } else { this.showsText = false; }
  }
}
Hope this helps as I've been trying to figure the most vanilla-y way to do this (with core angular stuff) for a while lol -- but I am not nearly versed enough on best-case APIs sadface
