How to Implement Infinite Scroll in Angular

How to Implement Infinite Scroll in Angular

How to Implement Infinite Scroll in Angular

Infinite scroll is more performant than pagination for large datasets. This guide uses a reusable directive powered by the native Intersection Observer API.

Step 1 — Infinite Scroll Directive

// directives/infinite-scroll.directive.ts
import { Directive, ElementRef, EventEmitter, OnInit, OnDestroy, Output } from '@angular/core';

@Directive({
  selector: '[appInfiniteScroll]',
  standalone: true,
})
export class InfiniteScrollDirective implements OnInit, OnDestroy {
  @Output() scrolled = new EventEmitter<void>();

  private observer!: IntersectionObserver;

  constructor(private el: ElementRef) {}

  ngOnInit(): void {
    this.observer = new IntersectionObserver(
      ([entry]) => { if (entry.isIntersecting) this.scrolled.emit(); },
      { threshold: 0.1 }
    );
    this.observer.observe(this.el.nativeElement);
  }

  ngOnDestroy(): void { this.observer.disconnect(); }
}

Step 2 — Post List Component

@Component({
  standalone: true,
  imports: [InfiniteScrollDirective, AsyncPipe],
  template: `
    @for (post of posts; track post.id) {
      <app-post-card [post]="post" />
    }

    <!-- Sentinel element at the bottom -->
    <div appInfiniteScroll (scrolled)="loadMore()" class="sentinel">
      @if (loading) { <span>Loading...</span> }
    </div>
  `,
})
export class PostListComponent implements OnInit {
  private postService = inject(PostService);

  posts:   Post[]  = [];
  page:    number  = 1;
  loading: boolean = false;
  hasMore: boolean = true;

  ngOnInit(): void { this.loadMore(); }

  loadMore(): void {
    if (this.loading || !this.hasMore) return;
    this.loading = true;

    this.postService.getPosts(this.page, 10).subscribe(newPosts => {
      this.posts   = [...this.posts, ...newPosts];
      this.hasMore = newPosts.length === 10;
      this.page++;
      this.loading = false;
    });
  }
}
All Comments