Как показать счетчик загрузки при ожидании наблюдаемого получения данных от сервиса в Angular - PullRequest
0 голосов
/ 13 февраля 2020

Я настроил наблюдаемую службу, которая помогает мне сохранять данные, но мне нужен способ показать загрузчик, пока данные ожидают получения от наблюдаемой.

Мой сервис:

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

Вот мой компонент:

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

Вот мой макет (использует Uikit):

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

Очевидно, что функция getData, хотя и объявлена ​​как asyn c, на самом деле не asyn c и возвращается немедленно. Таким образом, this.loading является «ложным» OnInit, и, следовательно, он не отображается.

Существует множество ответов, показывающих, как это может работать, когда подписка происходит в компоненте, но потому, что это общие данные. в службе я не могу найти способ ожидания данных, прежде чем установить для this.loading значение false.

Большое спасибо.

Ответы [ 5 ]

1 голос
/ 13 февраля 2020

Для этого вы можете создать загрузочную трубу , преимущество в том, что вы можете использовать ее повсюду в вашем приложении

import { Pipe, PipeTransform } from '@angular/core';
import { isObservable, of } from 'rxjs';
import { map, startWith, catchError } from 'rxjs/operators';

@Pipe({
  name: 'withLoading',
})
export class WithLoadingPipe implements PipeTransform {
 transform(val) {
   return isObservable(val)
      ? val.pipe(
         map((value: any) => ({ loading: false, value })),
         startWith({ loading: true }),
         catchError(error => of({ loading: false, error }))
      )
     : val;
 }
}

вот вам http вызов

httpCallObservable$ = this.httpClient.get(url);

и используйте это в своем шаблоне

 <div *ngIf="httpCallObservable$ | withLoading | async as data">
  <div [ngIf]="data.value">Value: {{ data.value }}</div>
  <ng-template [ngIf]="data.error">Error {{ data.error }}</ng-template>
  <ng-template [ngIf]="data.loading">Loading...</ng-template>
 </div>
0 голосов
/ 14 февраля 2020

Дополни мой комментарий, используя оператор nils . Мне нравится это решение, потому что оно «прозрачное», вижу, что вызов просто, например,

return this.httpClient.get('.....').pipe(
      indicate(this.loading$))

Более того, Вы можете использовать любой наблюдаемый, а не только вызов http. Использование операторов rx js, например, для выполнения нескольких http-вызовов с использованием forkJoin, или использование switchMap для двух вызовов, в которых есть только канал, не перегружайте вызовы и не вызывайте щелчок

Оператор как

export const prepare = <T>(callback: () => void) => {
    return (source: Observable<T>): Observable<T> =>
        defer(() => {
            callback();
            return source;
        });
};

export const indicate = <T>(indicator: Subject<boolean>) => {
    let alive = true;
    return (source: Observable<T>): Observable<T> =>
        source.pipe(
            prepare(() =>
                timer(500)
                    .pipe(
                        takeWhile(() => alive),
                        take(1)
                    )
                    .subscribe(() => {
                        indicator.next(true);
                    })
            ),
            finalize(() => {
                alive = false;
                indicator.next(false);
            })
        );
};

export const toClass = <T>(ClassType: { new(): T }) => (
    source: Observable<T>
) => source.pipe(map(val => Object.assign(new ClassType(), val)));

Я поставил таймер, потому что я не хочу, чтобы отображалась «загрузка», если вызов не тратит более 500 миллисекунд

Сервис, подобный

export class DataService {

  loading$ = new Subject<boolean>()


  getData():Observable<any>
  {
    //one call, I simulate a simple response
    //in general will be like this.httpClient.get(....)
    const observable=of('New Data in'+new Date()).pipe(delay(1000))

    return observable.pipe(
      indicate(this.loading$))
  }
}

У меня есть, например, в нашем app.component

<button (click)="loadData()">load</button>
{{data}}
<div *ngIf="(dataService.loading$ | async)">
    loading...
</div>

И функция loadData

export class AppComponent  {
  data:any
  //don't forget declare as public
  constructor(public dataService:DataService){}
  loadData()
  {
    this.dataService.getData().subscribe(res=>{
      this.data=res
    })
  }
}

Обновление Что ж, я делаю, что $ loading принадлежит сервис, но вы можете сделать так, чтобы $ loading принадлежал компоненту, и использовать канал в компоненте

  loading$ = new Subject<boolean>()
  loadData2()
  {
    this.data="";
    this.dataService.getData2().pipe(
      indicate(this.loading$))
      .subscribe(res=>{
      this.data=res
    })
  }

<div *ngIf="(loading$ | async)">
    loading...
</div>

. Вы можете увидеть в этот стек

0 голосов
/ 14 февраля 2020

Спасибо всем за ваши ответы, но ни одно из этих предложений не сработало для целей, изложенных в вопросе, но я поработал над решением и в итоге получил одно работающее. Здесь мы go:

В сервисе я создал новый Observable с именем loading$ и настроил его как new BehaviorSubject<boolean>(true);

Затем я создал геттер:

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

Затем я завернул HTTP-вызов с помощью true, затем установил загрузку на false в 3-м аргументе подписки (функция onCompleted ).

Таким образом, сервис выглядит следующим образом:

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$;
  }

Обратите внимание, что в методе подписки при вызове HTTP я добавляю пустой второй метод (это для onError), а затем в 3-й метод, Я добавил функцию для установки загрузки на false: this.loading$.next(false);. Это означает, что загрузка будет ложной после завершения подписки.

Тогда в моем компоненте я получу подписку loading$:

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

И, на мой взгляд:

  <div>
    <div uk-spinner *ngIf="loading$ | async"></div>
    <ul class="uk-list">
      <li *ngFor="let organisation of organisations$ | async">
        {{ organisation.name }}
      </li>
    </ul>
  </div>
0 голосов
/ 13 февраля 2020

измените нижнюю строку

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

на

async getData() {
   this.organisations$ = this.organisationsService.getList();
   this.organisations$.subscribe((data) => 
     this.loading = false;
   );
  }

это должно работать, потому что это наблюдаемая, которую вы ждете завершения, а не метод getData

0 голосов
/ 13 февраля 2020

Вы можете больше изменений в OrganisationlistComponent

 export class OrganisationlistComponent implements OnInit {

      public loading = false; // Default false 

      public organisations$: Observable<Organisation[]>;

      constructor(public organisationsService: OrganisationsService) {}

      ngOnInit() {

        this.getData().then((data) => {
        this.organisations$ = data; 
        this.loading = false; //  false one response
        });
      }

     async getData() {
        this.loading = true //  true whene loading data;
        return  this.organisationsService.getList();  // return data
      }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...