Infinite scroll is more performant than pagination for large datasets. This guide uses a reusable directive powered by the native Intersection Observer API.
// 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(); }
}
@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