Reputation: 7549
Assume an email inbox. The 50 most recent emails are loaded on initial page load.
From that point on, any new incoming emails should appear at the top of the list.
The complicating factor is that the user can paginate by scrolling down to the bottom of the page. This will load the next page of data to the bottom of the list.
I was wondering how this could be achieved using Angular and RxJS. Initially I had wanted to have a Subject<Email>
to which I could call next(email)
to push it to the stream, however this doesn't really work since we need to both append and prepend.
Another complicating factor is that new emails should be animated (i.e. fade in).
I was planning to render it as follows:
<email *ngFor="let email of (emails$ | async)">...</email>
Instead, I'm thinking of changing it to the following:
<div class="new-emails" *ngFor="let email of (newEmails$ | async)">...</div>
<div class="initial-emails" *ngFor="let email of (initialEmails$ | async)">...</div>
<div class="older-emails" *ngFor="let email of (oldEmails$ | async)"></div>
Is this a standard solution to the issue or is there some RxJS I might be missing to simplify this?
Upvotes: 1
Views: 697
Reputation: 3588
Hello for that case i would probably use a recursive strategy like thie CodeSandbox, the demo is using Subject.
Here is a snipet with the structure of the recursive item, followed by the mock request-service for data feed.
Overall I'm just calling the same item whenever the Subject
in my service is updated with .next()
, the tricky part is that you must use pipe(first())
on your subject subscription, because otherwise the subscription wont be unsubscribed, even if you put the unsubscribe logic in the onDestroy
(on destroy isn't call, because in reality you are just diving more and more in the recursion without detaching any component) and that will break your visualization logic.
// Typescript
import { Component, Input } from "@angular/core";
import { InfinityScrollFeedService } from "./../services/infinity-scroll-feed.service";
import { first } from "rxjs/operators";
@Component({
selector: "app-recursive",
templateUrl: "./recursive.component.html",
styleUrls: ["./recursive.component.css"]
})
export class RecursiveComponent {
@Input() initialData;
@Input() page;
nextLayer = [];
constructor(private feed: InfinityScrollFeedService) {}
ngOnInit() {
this.feed.httpCallMocker.pipe(first()).subscribe(data => {
this.nextLayer = data;
});
}
loadMore() {
this.feed.retrivePackage(Number(this.page) + 1);
}
}
// Template
<div class="row" *ngFor="let row of initialData">
<div>Name: {{row.name}}</div>
<div>Age: {{row.age}}</div>
</div>
<div *ngIf="nextLayer.length > 0">
<app-recursive [initialData]="nextLayer" [page]="(page+1)">
</app-recursive>
</div>
<button *ngIf="nextLayer.length <= 0" (click)="loadMore()">Show more</button>
// Css
.row {
display: flex;
justify-content: space-between;
border: 1px solid red;
}
import { Injectable } from "@angular/core";
import { Subject } from "rxjs";
@Injectable()
export class InfinityScrollFeedService {
dataPackages = [];
someMokeNames = ["Hari", "Xapu", "Hristiyan", "Pesho", "Gosho", "Ivan"];
httpCallMocker = <[]>new Subject();
constructor() {}
private getRandomNumber = max => {
return Math.floor(Math.random() * Math.floor(max));
};
private addDataPackage = () => {
let newData = [];
for (let i = 0; i < 10; i++) {
newData.push({
name: this.someMokeNames[
this.getRandomNumber(this.someMokeNames.length - 1)
],
age: this.getRandomNumber(100)
});
}
this.dataPackages.push(newData);
};
retrivePackage(page) {
if (this.dataPackages.length <= page) {
this.addDataPackage();
}
this.httpCallMocker.next(this.dataPackages[page]);
}
}
Upvotes: 1