Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
409 views
in Technique[技术] by (71.8m points)

How to show a loading spinner while waiting on an observable getting data from a service in Angular

I have set up an observable service that helps me persist data, but I need a way to show a loading spinner while the data is waiting to come from the observable.

My service:

imports [...]

import { Observable, BehaviorSubject } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class OrganisationsService {
  private organisations$ = new BehaviorSubject<Organisation[]>([
    {
      [...]
    }
  ]);

  constructor(private http: HttpClient) {
    this.http
      .get(
        environment.apicall
      )
      .subscribe((data) => this.organisations$.next(data['organisations']));
  }

  getList(): Observable<Organisation[]> {
    return this.organisations$.asObservable();
  }

Here's my component:

    imports ...

    import UIkit from 'uikit';
    import { Observable } from 'rxjs';

    @Component({
      selector: 'app-organisationlist',
      templateUrl: './organisationlist.component.html',
      styleUrls: ['./organisationlist.component.css']
    })
    export class OrganisationlistComponent implements OnInit {

      public loading = true;

      public organisations$: Observable<Organisation[]>;

      constructor(public organisationsService: OrganisationsService) {}

      ngOnInit() {
        this.getData().then(() => {
          this.loading = false;
        });
      }

      async getData() {
        this.organisations$ = this.organisationsService.getList();
      }
}

Here's my layout (uses Uikit):

  <div *ngIf="loading" uk-spinner></div>
  <div>
    <ul class="uk-list">
      <li *ngFor="let organisation of organisations$ | async">
        {{ organisation.name }}
      </li>
    </ul>
  </div>

Obviously, the getData function, although declared async, is not actually async and returns immediately. So this.loading is 'false' OnInit, and therefore it doesn't show.

There are plenty of answers around that show how this might work when the subscription happens in the component, but because this is shared data in a service, I can't find a way to wait on the data before setting this.loading to false.

Many thanks.

Question&Answers:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Thank you all for your responses, but none of these suggestions worked for the purposes outlined in the question, but I did work on a solution and eventually got one working. Here we go:

In the service I created a new Observable called loading$ and set it up as a new BehaviorSubject<boolean>(true);

Then I created a getter:

getLoading(): Observable<boolean> { return this.loading$; }

Then I wrapped the HTTP call with a true then set loading to false in the 3rd argument on the subscription (the onCompleted function).

So the service looks like this:

import { Observable, BehaviorSubject, of } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class OrganisationsService {
  private organisations$ = new BehaviorSubject<Organisation[]>([
    {
      [...]
    }
  ]);

  //initiate the new Loading variable via BehaviorSubject and set it to "true" from the beginning.
  public loading$ = new BehaviorSubject<boolean>(true);

  constructor(private http: HttpClient) {

    //set the loading$ to true again just before we start the HTTP call
    this.loading$.next(true);


    this.http
      .get(environment.apicall)
      .subscribe(

        //onNext method, where we get the data
        (data) => this.organisations$.next(data['organisations']),

        //onError method (Blank for now)
        () => {},

        //onComplated method, now that we have the data we can set the loading to false
        () => {
          this.loading$.next(false);
        }
      );
  }

  getLoading(): Observable<boolean> {
    return this.loading$;
  }

  getList(): Observable<Organisation[]> {
    return this.organisations$;
  }

Notice in the subscribe method on the HTTP call, I'm adding a blank second method (This is for onError) and then in the 3rd method, I've added a function to set the loading to false: this.loading$.next(false); . This means that Loading will now be false when the subscription is complete.

Then in my component, I get the loading$ subscription:

      public loading$: Observable<boolean>;
      public organisations$: Observable<Organisation[]>;

      constructor(public organisationsService: OrganisationsService) {}

      ngOnInit() {
        this.getData();
      }

      async getData() {

        //initially loading$ will be true because the service makes it so
        this.loading$ = this.organisationsService.getLoading();

        //when this completes, loading$ will be set to false via the service
        this.organisations$ = this.organisationsService.getList();
      }

And in my view:

  <div>
    <div uk-spinner *ngIf="loading$ | async"></div>
    <ul class="uk-list">
      <li *ngFor="let organisation of organisations$ | async">
        {{ organisation.name }}
      </li>
    </ul>
  </div>

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...