typescript Angular 2 - 在 (observableData | async) 尚未解析时显示加载信息

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/38262083/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-21 03:38:15  来源:igfitidea点击:

Angular 2 - Show loading-information when (observableData | async) is not yet resolved

typescriptangularrxjsobservable

提问by Alexander Ciesielski

just as the title says, I want to embrace the power of rxjs Observables.

正如标题所说,我想拥抱 rxjs Observables 的强大功能。

What I do now:

我现在应该做什么:

// dataview.html
<div *ngIf="isLoading">Loading data...div>
<ul *ngIf="!isLoading">
    <li *ngFor="let d of data">{{ d.value }}</li>
</ul>


// dataview.ts

data: any[] = [];
isLoading: boolean = false;

getData() {

this.isLoading = true;
this._api.getData().subscribe(
        data => {
            this.data = data;
            this.isLoading = false;
        },
        error => {
            this.error = error;
            this.isLoading = false;
        });
}

What I want to do:

我想做的事:

1. Use asyncpipe in my template

1.async在我的模板中使用管道

  1. Make dataan Observable array

  2. Still display loading information for the user

  1. 制作data一个 Observable 数组

  2. 仍然为用户显示加载信息

I'm a big fan of clean code, so how can this be done nicely using rxjs and Angular 2?

我是干净代码的忠实粉丝,那么如何使用 rxjs 和 Angular 2 很好地做到这一点呢?

采纳答案by Kliment

This is how I do it. Also i use $at the and of the variable name to remind me that it is a stream.

我就是这样做的。我还使用$变量名的 和 来提醒我它是一个流。

// dataview.html
<div *ngIf="isLoading$ | async">Loading data...</div>
<ul *ngIf="!(isLoading$ | async)">
    <li *ngFor="let d of data">{{ d.value }}</li>
</ul>


// dataview.ts

data: any[] = [];
isLoading$: BehaviorSubject<boolean> = new BehaviorSubject(false);

getData() {

this.isLoading$.next(true);

this._api.getData().subscribe(
        data => {
            this.data = data;
        },
        error => {
            this.error = error;
        },
        complete => {
            this.isLoading$.next(false);
        });
}

回答by maxime1992

I Came up with the following:

我想出了以下内容:

export enum ObsStatus {
  SUCCESS = 'Success',
  ERROR = 'Error',
  LOADING = 'Loading',
}

export interface WrapObsWithStatus<T> {
  status: ObsStatus;
  value: T;
  error: Error;
}

export function wrapObsWithStatus<T>(obs: Observable<T>): Observable<WrapObsWithStatus<T>> {
  return obs.pipe(
    map(x => ({ status: ObsStatus.SUCCESS, value: x, error: null })),
    startWith({ status: ObsStatus.LOADING, value: null, error: null }),
    catchError((err: Error) => {
      return of({ status: ObsStatus.ERROR, value: null, error: err });
    })
  );
}

And then in your component:

然后在您的组件中:

TS

TS

public ObsStatus: typeof ObsStatus = ObsStatus;

public obs$: Observable<WrapObsWithStatus<YOUR_TYPE_HERE>> = wrapObsWithStatus(this.myService.getObs());

HTML

HTML

<div *ngIf="obs$ | async as obs" [ngSwitch]="obs.status">
  <div *ngSwitchCase="ObsStatus.SUCCESS">
    Success! {{ obs.value }}
  </div>

  <div *ngSwitchCase="ObsStatus.ERROR">
    Error! {{ obs.error }}
  </div>

  <div *ngSwitchCase="ObsStatus.LOADING">
    Loading!
  </div>
</div>

回答by trungk18

I did it by using the async pipe. But this approach still required you to catch it manually to handle the error. See herefor more detail.

我是通过使用异步管道做到的。但是这种方法仍然需要您手动捕获它以处理错误。请参阅此处了解更多详情。

app.component.html

应用程序组件.html

<div class="wrapper">
    <div class="form-group" *ngIf="pickupLocations$ | async as pickupLocations; else loading">    
        <ul class="dropdown-menu" *ngIf="pickupLocations.length">
            <li *ngFor="let location of pickupLocations">
                <strong>{{location.Key}}</strong>
            </li>
        </ul>
        <span *ngIf="!pickupLocations.length">There are no locations to display</span>
    </div>

    <ng-template #loading>
        <i class="fa fa-circle-o-notch fa-spin fa-3x fa-fw"></i>
        <span class="sr-only">Loading...</span>
    </ng-template>
</div>

app.component.ts

app.component.ts

this.pickupLocations$ = this.apiService.getPickupLocations(storeId);

回答by Simon_Weaver

This is my current best attempt for displaying search results.

这是我目前显示搜索结果的最佳尝试。

I thought about extending Observable somehow to include an isLoading property - or returning a tuple but in the end a helper function (in my service) that returns a pair of observables seems to be the cleanest way. Like you I was looking for some 'magic' but I can't see any better way to do it than this.

我想以某种方式扩展 Observable 以包含一个 isLoading 属性 - 或者返回一个元组,但最终返回一对 observable 的辅助函数(在我的服务中)似乎是最干净的方法。像你一样,我一直在寻找一些“魔法”,但我找不到比这更好的方法了。



So in this example I have a FormGroup(a standard reactive form) which contains search criteria:

所以在这个例子中,我有一个FormGroup(标准的反应形式),其中包含搜索条件:

{ email: string, name: string } 

I get the search criteria from the form's valueChangesobservable when it changes.

valueChanges当表单更改时,我从表单的observable 中获取搜索条件。

Component Constructor

组件构造函数

Note: The search isn't actually run until the criteria change, which is why this is in the constructor.

注意:在条件更改之前,搜索实际上不会运行,这就是构造函数中的原因。

// get debounced data from search UI
var customerSearchCriteria = this.searchForm.valueChanges.debounceTime(1000);

// create a pair of observables using a service (data + loading state)
this.customers = this.customersService.searchCustomers(customerSearchCriteria);

// this.customers.data => an observable containing the search results array
// this.customers.isLoading => an observable for whether the search is running or not

Search Service

搜索服务

public searchCustomers(searchCriteria: Observable<CustomersSearch>):
                       { data: Observable<CustomerSearchResult[]>, 
                         isLoading: Observable<boolean> }
{
    // Observable to track loading state
    var isLoading$ = new BehaviorSubject(false);

    // Every time the search criteria changes run the search
    var results$ = searchCriteria
                    .distinctUntilChanged()
                    .switchMap(criteria =>
                    {
                        // update isLoading = true
                        isLoading$.next(true);

                        // run search
                        var search$ = this.client.search(new CustomersSearch(criteria)).shareReplay();

                        // when search complete set isLoading = false
                        search$.subscribe({ complete: () => isLoading$.next(false) });

                        return search$;
                    })
                    .shareReplay();

    return { data: results$, isLoading: isLoading$ };
}

Need to find some way to make this generic, but that's pretty easy. Also note that if you don't care about isLoading you simply do searchCustomers(criteria).dataand then you're just getting to the data.

需要找到某种方法来使其通用,但这很容易。另请注意,如果您不关心 isLoading,您只需这样做searchCustomers(criteria).data,然后您就可以获取数据。

Edit: needed to add an extra ShareReply to prevent search firing twice.

编辑:需要添加额外的 ShareReply 以防止搜索触发两次。

Component HTML

组件 HTML

Use both customers.dataand customers.isLoadingas observables as normal. Remember customersis just an object with two observable properties on it.

像往常一样使用customers.datacustomers.isLoading作为可观察对象。记住customers只是一个具有两个可观察属性的对象。

<div *ngIf="customers.isLoading | async">Loading data...</div>
<ul *ngIf="!(customers.isLoading | async)">
    <li *ngFor="let d of customers.data | async">{{ d.email }}</li>
</ul>

Also note that you need the asyncpipe for both observables. I realize that looks a little clumsy for the isLoading, I believe that it is faster to use an observable than a property anyway. There could be a refinement to this, but I'm not yet an expert but would certainly welcome improvements.

另请注意,您需要async两个 observable的管道。我意识到 isLoading 看起来有点笨拙,我相信无论如何使用 observable 比使用属性更快。可能会对此进行改进,但我还不是专家,但肯定会欢迎改进。

回答by Alex Fuentes

One way to do that without any member property could be evaluating the async observable results in the template: !(yourAsyncData$ | async)or !(yourAsyncData$ | async)?.length.

在没有任何成员属性的情况下执行此操作的一种方法可能是评估模板中的异步可观察结果:!(yourAsyncData$ | async)!(yourAsyncData$ | async)?.length

For instance: <p-dataView #dv [value]="bikes$ | async" [loading]="!(bikes$ | async)"> ... </p-dataview>

例如: <p-dataView #dv [value]="bikes$ | async" [loading]="!(bikes$ | async)"> ... </p-dataview>

回答by Wouter Klene

Perhaps this could work for you. This show the data when the observable exists and there is async data. Otherwise shows a loading template.

也许这对你有用。这显示了当 observable 存在并且有异步数据时的数据。否则显示加载模板。

<ul *ngIf="data$ && (data$ | async);else loading">
    <li *ngFor="let d of data$ | async">{{ d.value }}</li>
</ul>
<ng-template #loading>Loading...</ng-template>