简体   繁体   中英

Angular RxJS - set valueChanges pipe value to null until observable resolved

I'm trying to pipe valueChanges from a select to fire off the appropriate API request and show a spinner while the response hasn't been received. On top of that I'm trying to use publish() and refCount() so that I can use that observable with the async pipe in multiple places in my template.

my-component.ts

ngOnInit() {
  this.results$ = this.selectControl.valueChanges.pipe(
    startWith('hard'),
    switchMap((evalStrategy: 'hard' | 'soft') => this.apiService.getResults(evalStrategy)),
    publish(),
    refCount()
  );
}

my-component.html

<mat-progress-spinner *ngIf="!(results$ | async)"></mat-progress-spinner>

<div *ngIf="results$ | async">
   <p>Name {{(results$ | async).name}}</p>
   <small>{{(results$ | async).description}}
</div>

Ideally, I'd expect this code to work and only fire off just 1 request to the server.

But I end up getting Cannot read property 'name' of null :(

If I make the following change to my-component.html

<mat-progress-spinner *ngIf="!(results$ | async)"></mat-progress-spinner>

<div *ngIf="(results$ | async) as results">
   <p>Name {{(results).name}}</p>
   <small>{{(results).description}}
</div>

Then it works fine and the only fire's off 1 request to the server. But when you change the select option, results$ | async results$ | async no longer evaluates to null until the new value is available. Instead, it sticks with the old value and then all of a sudden replaces it with the updated one.

Ideally you want to separate out the variables - so that context is clearer - status of loading and final results are two different things. Making an assumption that results are null equates to status is loading can be dangerous, as they are fundamentally different. For example, what happens if there is error? That would mean results is null and yet the status is no longer loading. If following your implementation your spinner will be forever loading when an error is thrown. And also it could run into the scenario you described - You lose the state of the loading , cos now its no longer null and your spinner will never loads after the first initial request.

It is also kinda redundant (pointless) to reset your results to null every time you there is a change in your form. Technically your results are not null . It has results. Just that the results are not the latest.

Instead, use a subject to emit values:

private isLoading = new BehaviourSubject<boolean>(false);

and in your component, simply use .tap() to change the status of the Subject :

this.results$ = this.selectControl.valueChanges.pipe(
    startWith('hard'),
    tap(() => this.isLoading.next(true)), //sets loading to true, we are firing our api request!
    switchMap((evalStrategy: 'hard' | 'soft') => this.apiService.getResults(evalStrategy)),
    tap(() => this.isLoading.next(false)), //api finished fetching, stop the spinner!
    catchError(() => this.isLoading.next(false)), //this is up to you on how you want to do error handling.
    publish(),
    refCount(),
);

and get your html to bind to it:

<mat-progress-spinner *ngIf="isLoading"></mat-progress-spinner>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM