使用 Observables,延迟后显示加载指示器,但如果加载及时完成则取消?

Using Observables, show loading indicator after delay but cancel if loading is completed in time?

在我的客户详细信息组件中,我有以下代码可以实现我所追求的目标,但不是我认为可能的 reactive/observable 方式。

不是将 this.isLoading = true; 包装在 if 语句中,有没有一种方法可以使用反应式编程技术来做到这一点?如果先检索到客户,也许可以通过 cancelling/dropping 延迟观察?或者,我是不是用错了方法?

export class CustomerDetailComponent implements OnInit {

  customer: Customer;
  errorMessage: string;
  isLoading: boolean;

  constructor(
    private customerService: CustomerService,
    private route: ActivatedRoute,
    private router: Router,
    private location: Location
  ) { }

  ngOnInit() { 
    let idParam = this.route.params
      .distinctUntilChanged(params => params['id']);

    idParam.subscribe(params => 
    {
      this.errorMessage = ''; 
    });

    idParam.delay(300).subscribe(params => 
    {
      if (!(this.customer && this.customer.id == params['id']))
        this.isLoading = true;
    });

    idParam.switchMap((params: Params) => this.customerService.getCustomer(params['id']))
      .subscribe(customer => 
      { 
        this.customer = customer; 
        this.isLoading = false;
      },
      error => this.errorMessage = error);
  }
}

你可以这样写:

function getCustomer(id) {
    return Observable.of({'name': 'John', id}).delay(500);
}

Observable.of({'id': 42})
    .distinctUntilChanged(params => params['id'])
    .do(() => {
        // this.errorMessage = '';
    })
    .switchMap((params) => {
        return Observable.combineLatest(
            Observable.of(true).delay(300).startWith(null), // delay Observable
            getCustomer(params['id']).startWith(null), // customer Observable
            function(delay, customer) { // selector function
                if (customer) {
                    return customer;
                }

                if (delay && !customer) {
                    console.log('this.isLoading = true;');
                }
                return null;
            })
            .filter(customer => customer)
            .distinctUntilChanged(customer => customer['id']);
    })
    .subscribe(
        customer => {
            console.log('this.isLoading = false;');
            console.log(customer);
            // this.customer = customer;
        },
        error => {
            // this.errorMessage = error;
        }
    );

观看现场演示:https://jsbin.com/nebutup/5/edit?js,console

内部combineLatest()接收两个Observables:

  1. 300 毫秒延迟
  2. 来自远程服务的客户(在此演示中模拟)

然后还有用于select我们想要进一步传播的投影函数。两个 Observables 都使用 .startWith(null) 来确保它们至少发出了一个项目,因此 combineLatest() 将被其中任何一个的变化触发。然后我们可以很容易地知道第一个发出的 Observable 是延迟还是客户。

然后还有 filter() 删除所有 null 值和 distinctUntilChanged() 以确保我们不会两次发出同一个客户(这处理客户首先完成的情况).

然后当我们运行这个演示并且首先触发延迟时,输出如下:

this.isLoading = true;
this.isLoading = false;
{ name: 'John', id: 42 }

这意味着我们先显示加载然后隐藏它。

然后当我们把getCustomer()改成先完成:

function getCustomer(id) {
    return Observable.of({'name': 'John', id}).delay(100);
}

我们将得到以下信息:

this.isLoading = false;
{ name: 'John', id: 42 }

这意味着我们从不显示任何加载。

这是一个带有可重用运算符的 rxjs 6 管道方法:

export function delayIndicator<T>(delay: number, start: () => void, complete: () => void): OperatorFunction<T, T> {
  const loadingShown$ = timer(delay).pipe(
    tap(() => start()),
    mapTo(true),
    startWith(false)
  );

  return (input$) =>
    combineLatest([input$, loadingShown$]).pipe(
      take(1),
      map(([input, delayShown]) => {
        if (delayShown) {
          complete();
        }

        return input;
      })
    );
}

myObservable$.pipe(delayIndicator(300, () => this.loading = true, () => this.loading = false));