Home > Enterprise >  Clear Observable value when calling function
Clear Observable value when calling function

Time:12-08

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.

CodePudding user response:

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.


a cleaner way would be to resubscribe to your scan observable once you wish to reset.

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()

CodePudding user response:

  1. Remove scan from this.awsOwners$ pipe
this.awsOwners$ = this.awsOwners.asObservable()
  1. Make this.selectedPlatform as Subject, and make it call getAwsOwners if changed
this._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);
 ))
}
  1. Inside 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);
    },
  );

CodePudding user response:

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 }
    )
  ))
)
  1. The offset and platform arguments are converted into a subjects.
  2. The main observable is created from the platform subject.
  3. The only operator inside the main observable is switchMap. This causes any existing requests to immediately get cancelled.
  4. Inside the switchMap, offsetChanges is subscribed to. It immediately will emit 0 because of the startWith operator.
  5. Any emissions from offsetChanges will immediately kick off the owners request inside the mergeScan. This will use data from the previous emission as well as emit twice for each request so that the loading state is also managed.

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.

  • Related