RRGT19
RRGT19

Reputation: 1677

Loading spinner is not showing while doing a HTTP request in Angular

I'm doing a web application in Angular 8 and I want to show a loading spinner while doing a HTTP request.

The loading spinner is not showing with my implementation and I could not find the reason.

Service

@Injectable()
export class UserService {

  // Caching
  private bsResource: BehaviorSubject<string[]> = new BehaviorSubject([]);
  private readonly resources$: Observable<string[]> = this.bsResource.asObservable();

  constructor(
    private http: HttpClient
  ) {
  }

  // Calling this method the loading spinner is not showing.
  getResources(): Observable<string[]> {
    if (this.bsResource.getValue().length === 0) {
      this.fetchResourceList().toPromise().then(res => this.bsResource.next(res));
    }

    return this.resources$;
  }

  // The loading spinner appears if I use this method directly and make this method public.
  private fetchResourceList(): Observable<string[]> {
    return this.http.get<string[]>('MY_URL');
  }
}

Component:

@Component({
  templateUrl: './create-new.component.html',
  styleUrls: ['./create-new.component.scss']
})
export class CreateNewComponent {
  resourceList$: Observable<string[]> = this.service.getResources();

  constructor(
    private service: UserService
  ) { }
}

Template

<div class="form-group">
  <label for="resourceSelect">* Resources</label>
  <div *ngIf="resourceList$ | async as resourceList; else loading">
     <ng-container *ngIf="resourceList.length; else noResults" >
        <div *ngFor="let r of resourceList; index as i">
          <!-- Show the result using checkboxes -->
        </div>
     </ng-container>
  </div>
</div>

<ng-template #loading>
  <br/>
  <div class="spinner-border spinner-border-sm text-muted" role="status">
    <span class="sr-only">Loading...</span>
  </div>
</ng-template>

<ng-template #noResults>
  <div class="text-muted">No results.</div>
</ng-template>

I don't understand why by using this.service.getResources() the loading spinner is not showing and if I use this.service.fetchResourceList() the loading spinner appears correctly.

My goal is to show the loading spinner correctly using the example that I have provided and keeping the method that I'm calling in my component.

Upvotes: 1

Views: 933

Answers (1)

Random
Random

Reputation: 3236

I spent time to reproduce it in a sandbox, but finally found that the problem comes from the initialization of your BehaviorSubject. The first value of bsResource is []. So in your *ngIf, when the pipe async subscribes to it, it receives the value [], which is truthy, so the *ngIf is instantly true, and never triggers the else (loading) block.

All you have to do is to initialize your BehaviorSubject with a null value (and then fix the code using it, to be null-safe):

private bsResource: BehaviorSubject<string[]> = new BehaviorSubject(null);

And when you use it:

const cachedValue = this.bsResource.getValue();
if (!cachedValue || cachedValue.length === 0) {
  this.fetchResourceList().subscribe(res => this.bsResource.next(res));
}

I'm saying it again, you should not use Promises, use .subscribe, which works the same as toPromise().then. You should NEVER use Promise. .toPromise is only usefull when you are upgrading Angular, where most of the code uses Promises, and don't want to upgrade the whole code at once.

Upvotes: 1

Related Questions