I have a dropdown which gets values from a service.
Dropdown
<mat-select
*ngIf="selectedCloudTypeName === 'AWS'"
class="select--width--130"
[formControl]="awsOwnersControl"
placeholder="AWS Owners"
msInfiniteScroll
(infiniteScroll)="loadMoreAwsOwners()"
[complete]="currentAwsOwnerssDropdownOffset >= awsOwnersTotalCount"
>
<mat-option *ngFor="let owner of awsOwners$ | async" [value]="owner.id">
{{ owner.name }}
</mat-option>
</mat-select>
Service call in TS file
getAwsOwners(offset = 0) {
this.isLoading$.next(true);
this.awsService
.getOwnersListForAws(this.selectedCredentialIdForCloudType, this.selectedPlatform, { offset, limit: 100 })
.subscribe(
(owners: PaginatedResult<CommonEnumValue[]>) => {
this.awsOwners.next(owners.data);
this.awsOwnersTotalCount = owners.totalCount;
this.isLoading$.next(false);
},
(error) => {
this.isLoading$.next(false);
this.alertify.error(error);
},
);
}
In OnInit hook I use an Observable
this.awsOwners$ = this.awsOwners.asObservable().pipe(scan((acc, curr) => [...acc, ...curr], []));
which I declare on as property in the file as
awsOwners = new BehaviorSubject<CommonEnumValue[]>([]);
awsOwners$: Observable<CommonEnumValue[]>;
The problem is when I call the getAwsOnwers function with different 'selectedPlatform' value, the old data persists and the new data gets appended.
I tried to clear the awsOwners Subject like this
this.awsOwners.next([]);
before I call it but it does clear it and new data gets appended to the old one,
Any ways how can I clear it?
I sense that in the scan operator I some how need to clear the '.acc' because it accumulates the values.
scan from this.awsOwners$ pipethis.awsOwners$ = this.awsOwners.asObservable()
this.selectedPlatform as Subject, and make it call getAwsOwners if changedthis._selectedPlatform$ = new Subject()
get selectedPlatform$() {
// if platform is object you should provide comparing callback to distinctUntilChanged operator
return this._selectedPlatform$.asObservable.pipe(distinctUntilChanged(), tap(() =>
//make getAwsOwners() accept platform as an parameter and offset as a optional one.
this.getAwsOwners(platform);
))
}
getAwsOwners subscribe method make some changes .subscribe(
(owners: PaginatedResult<CommonEnumValue[]>) => {
// always if offset === 0 you should reset owners state
this.awsOwners.next(offset ?
[...this.awsOwners.getValue(), ...owners.data] :
owners.data
);
this.awsOwnersTotalCount = owners.totalCount;
this.isLoading$.next(false);
},
(error) => {
this.isLoading$.next(false);
this.alertify.error(error);
},
);
you could reset your scan if your curr value in the scan lambda is empty.
(acc, curr) => curr === [] ? [] : [...acc, ...curr]
but that would kind of feel dirty to me.
you could create a BehaviourSubject and emit every time you wish to reset your value. then you could pipe your subject and switchMap to your scan observable. this way the observable would always resubscribe to your scan observable and thus start over, as switchMap automatically unsubscribes and subscribes if the source emits a value.
could look something like this:
BehaviourSubject resetSubject$ = new BehaviourSubject<void>(); // use behaviour subject to get an initial emit
awsOwners$ = resetSubject$.pipe(
switchMap(() => this.awsOwners.asObservable().pipe(
scan((acc, curr) => [...acc, ...curr], [])
),
);
// call this to reset your scan
resetSubject$.emit()
Instead of having a method that updates a subject, just create a stream that maintains the state of the list data, if it's loading, and the current page offset.
readonly offsetChangeSubject = new Subject<number>();
readonly selectedPlatformSubject = new Subject<string>();
readonly ownersList$ = this.selectedPlatformSubject.pipe(
switchMap(platform) => offsetChangeSubject.pipe(
startWith(0),
mergeScan((acc, offset) =>
this.awsService.getOwnersListForAws(this.credentialId, platform, { offset, limit: 100 }).pipe(
startWith({ ...acc, isLoading: true, offset })
map(page => ({
isLoading: false,
offset,
owners: owners.concat(page.data),
totalCount: page.totalCount
}))
),
{ isLoading: false, owners: [], totalCount: 0, offset }
)
))
)
Notes
If it's not apparent, there will always be a result once platformSubject first emits.
I presumed that once that platform changed, you'd want the offset reset as well, that is one of the reasons why I didn't make offsetSubject a BehaviorSubject and instead used startWith to emit 0. So you'd retrieve the current value of the offset from the ownersList$ observable, not the offsetChangeSubject - that's only for updates.
You can use shareReplay if you want the current list state in more than just the view.