使用 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:
- 300 毫秒延迟
- 来自远程服务的客户(在此演示中模拟)
然后还有用于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));
在我的客户详细信息组件中,我有以下代码可以实现我所追求的目标,但不是我认为可能的 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:
- 300 毫秒延迟
- 来自远程服务的客户(在此演示中模拟)
然后还有用于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));