Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save johannesMatevosyan/ac4c051ceb7c018a8bd0bc3452ca0973 to your computer and use it in GitHub Desktop.

Select an option

Save johannesMatevosyan/ac4c051ceb7c018a8bd0bc3452ca0973 to your computer and use it in GitHub Desktop.
Angular IntersectionObserver Directive
import {
Directive,
ElementRef,
EventEmitter,
Input,
OnInit,
OnChanges,
SimpleChanges,
OnDestroy,
Output,
inject,
} from '@angular/core';
@Directive({
selector: '[appIntersectionObserver]',
standalone: true,
})
export class IntersectionObserverDirective implements OnInit, OnChanges, OnDestroy {
private readonly elementRef = inject(ElementRef<HTMLElement>);
@Input() threshold: number | number[] = 0.1;
@Input() rootMargin = '0px';
@Input() once = false;
@Output() visible = new EventEmitter<IntersectionObserverEntry>();
@Output() hidden = new EventEmitter<IntersectionObserverEntry>();
private observer: IntersectionObserver | null = null;
private initialized = false;
ngOnInit(): void {
this.initialized = true;
this.createObserver();
}
ngOnChanges(changes: SimpleChanges): void {
if (!this.initialized) {
return;
}
const thresholdChanged =
changes['threshold'] &&
changes['threshold'].previousValue !== changes['threshold'].currentValue;
const rootMarginChanged =
changes['rootMargin'] &&
changes['rootMargin'].previousValue !== changes['rootMargin'].currentValue;
if (thresholdChanged || rootMarginChanged) {
this.createObserver();
}
}
ngOnDestroy(): void {
this.disconnectObserver();
}
private createObserver(): void {
this.disconnectObserver();
if (typeof IntersectionObserver === 'undefined') {
return;
}
this.observer = new IntersectionObserver(
(entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
this.visible.emit(entry);
if (this.once) {
this.disconnectObserver();
return;
}
} else {
this.hidden.emit(entry);
}
}
},
{
threshold: this.threshold,
rootMargin: this.rootMargin,
}
);
this.observer.observe(this.elementRef.nativeElement);
}
private disconnectObserver(): void {
this.observer?.disconnect();
this.observer = null;
}
}

Basic usage

<div appIntersectionObserver (visible)="onVisible()">
  Watch me
</div>

Lazy load image

<img
  [src]="imageLoaded ? realSrc : placeholder"
  appIntersectionObserver
  [once]="true"
  (visible)="imageLoaded = true"
/>

Use cases

  • Lazy loading images/components
  • Triggering animations on scroll
  • Infinite scroll / pagination
  • Viewport visibility tracking
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment