Infinite Scroll made easier with Angular Defer

Robert Isaac
2 min readDec 11, 2023

--

Infinite Scroll made easier with Angular Defer

while there are many solutions for Infinite Scroll, there is one very simple solution after having the defer in Angular v17

I will make it short and go to the point directly, but before I briefly explain what’s defer view and what’s the option on viewport for it

the new @defer Angular added in v17 will make this part of the view not get rendered until a condition is being met, in this article we are interested in only one of them the on viewport according to the angular docs “viewport would trigger the deferred block when the specified content enters the viewport”, this is the magic behind my simple approach

you can see the initial implementation from this GitHub link

but it’s just very simple for loop

@for (question of questions(); track question.id) {
<app-question-item [question]="question"/>
}

now what I’d do is to wrap the component with @defer to be

@for (question of questions(); track question.id) {
@defer (on viewport) {
<app-question-item [question]="question" (loaded)="loaded($event)"/>
} @placeholder {
<div>loading...</div>
}
}

here is important thing, inside placeholder it must be a block element not inline element, since inline elements will make all of the loading elements be in the viewport together and all of them will be rendered together, so if it’s an image for example, wrap it with a div or change it’s display through css to be block

and inside the question item component we will simply just add output to emit when the ngOnInit is triggered (and that’s only happen when it’s rendered), here is how the component looks now

export class QuestionItemComponent implements OnInit {
@Input({required: true}) question!: Question;
@Output() loaded = new EventEmitter<number>();

ngOnInit() {
this.loaded.emit(this.question.id);
}
}

now that’s basically it, you can see check when an element is reached and start fetching the new data from the API and add them to the list

here is for example the logic for fetching the items when the 2nd last item in the list is reached

export class QuestionListComponent {
protected readonly questions = signal<Question[]>([]);
private total = 0;

constructor(private readonly questionService: QuestionService) {
this.loadQuestions(0);
}

private loadQuestions(page: number) {
this.questionService.getQuestions(page).subscribe(questions => {
this.total = questions.total;

this.questions.update(oldQuestions => {
return [...oldQuestions, ...questions.data];
});
});
}

loaded(id: number) {
if (this.questions().length < this.total && this.questions().at(-2)?.id === id) {
this.loadQuestions(this.questions().length / 5);
}
}
}

I’m using signals here, but you can do it with a normal array as well

you can find the changes here https://github.com/robertIsaac/infinite-scroll/commit/7826686915b82a99238a13ae89c39e2db61017ae

or the full solution here https://github.com/robertIsaac/infinite-scroll

--

--

Robert Isaac
Robert Isaac

Written by Robert Isaac

I am a Senior Front-End Developer who fall in love with Angular and TypeScript.

No responses yet