我有一个像这样实现的全局加载器:
CoreModule:
router.events.pipe(
filter(x => x instanceof NavigationStart)
).subscribe(() => loaderService.show());
router.events.pipe(
filter(x => x instanceof NavigationEnd || x instanceof NavigationCancel || x instanceof NavigationError)
).subscribe(() => loaderService.hide());
LoaderService:
@Injectable({
providedIn: 'root'
})
export class LoaderService {
overlayRef: OverlayRef;
componentFactory: ComponentFactory<LoaderComponent>;
componentPortal: ComponentPortal<LoaderComponent>;
componentRef: ComponentRef<LoaderComponent>;
constructor(
private overlay: Overlay,
private componentFactoryResolver: ComponentFactoryResolver
) {
this.overlayRef = this.overlay.create(
{
hasBackdrop: true,
positionStrategy: this.overlay.position().global().centerHorizontally().centerVertically()
}
);
this.componentFactory = this.componentFactoryResolver.resolveComponentFactory(LoaderComponent);
this.componentPortal = new ComponentPortal(this.componentFactory.componentType);
}
show(message?: string) {
this.componentRef = this.overlayRef.attach<LoaderComponent>(this.componentPortal);
this.componentRef.instance.message = message;
}
hide() {
this.overlayRef.detach();
}
}
使用Angular 7.0.2运行时,行为(我想要的)是:
我已经更新到Angular 7.2,现在的行为是:
LoaderComponent
的叠加层我在NavigationStart
和NavigationEnd
事件上添加了一些日志,发现NavigationEnd
之后NavigationStart
立即被触发(这是正常现象),而覆盖消失约0.5秒后。
我已经阅读了CHANGELOG.md
,但没有发现任何可以解释此问题的信息。任何想法都欢迎。
编辑:
经过进一步研究,我通过设置package.json
来恢复以前的行为:
"@angular/cdk": "~7.0.0",
"@angular/material": "~7.0.0",
代替此:
"@angular/cdk": "~7.2.0",
"@angular/material": "~7.2.0",
我已经确定错误的提交(已在7.1.0版中发布),并将问题发布在相关的GitHub issue上。它修复了Overlay
的淡出动画。
获得所需行为的符合v7.1 +的方式是什么?
根据我的看法,最好的方法是:仅在必要时显示加载程序,但是NavigationStart
不包含所需的信息。
我想避免最终出现一些反跳行为。
答案 0 :(得分:0)
在意识到debounceTime
是UX方面的一个很好的解决方案之后,这就是我的结论。因为它允许加载器仅在加载时间值得显示加载器时显示。
counter = 0;
router.events.pipe(
filter(x => x instanceof NavigationStart),
debounceTime(200),
).subscribe(() => {
/*
If this condition is true, then the event corresponding to the end of this NavigationStart
has not passed yet so we show the loader
*/
if (this.counter === 0) {
loaderService.show();
}
this.counter++;
});
router.events.pipe(
filter(x => x instanceof NavigationEnd || x instanceof NavigationCancel || x instanceof NavigationError)
).subscribe(() => {
this.counter--;
loaderService.hide();
});
答案 1 :(得分:0)
我们在带有异常列表的系统中实现加载程序的方式:
export class LoaderInterceptor implements HttpInterceptor {
requestCount = 0;
constructor(private loaderService: LoaderService) {
}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (!(REQUEST_LOADER_EXCEPTIONS.find(r => request.url.includes(r)))) {
this.loaderService.setLoading(true);
this.requestCount++;
}
return next.handle(request).pipe(
tap(res => {
if (res instanceof HttpResponse) {
if (!(REQUEST_LOADER_EXCEPTIONS.find(r => request.url.includes(r)))) {
this.requestCount--;
}
if (this.requestCount <= 0) {
this.loaderService.setLoading(false);
}
}
}),
catchError(err => {
this.loaderService.setLoading(false);
this.requestCount = 0;
throw err;
})
);
}
}
而加载程序服务只是(300毫秒的延迟可防止加载程序在响应速度很快时仅在屏幕上闪烁):
export class LoaderService {
loadingRequest = new BehaviorSubject(false);
private timeout: any;
setLoading(val: boolean): void {
if (!val) {
this.timeout = setTimeout(() => {
this.loadingRequest.next(val);
}, 300);
} else {
clearTimeout(this.timeout);
this.loadingRequest.next(val);
}
}
}
答案 2 :(得分:0)
尝试改进@Guerric P 的想法,我想如果您定义了一个可观察对象:
loading$ = this.router.events.pipe(
filter(
x =>
x instanceof NavigationStart ||
x instanceof NavigationEnd ||
x instanceof NavigationCancel ||
x instanceof NavigationError
),
map(x => (x instanceof NavigationStart ? true : false)),
debounceTime(200),
tap(x => console.log(x))
);
你有
<div *ngIf="loading$|async">Loadding....</div>
您应该看到正在加载...当开始导航时没有看到则所有内容都已加载。
这可以在一个组件中,这个组件在 main-app.component 中,它不需要服务或工厂 在 this stackblitz 中,如果您删除去抖动,您将在控制台中看到 true、false 注意:您可以删除“tap”运算符,它仅用于检查
更新,在我们从服务加载数据时使用相同的加载器。
假设我们有一个服务,其属性“正在加载”是一个主题
@Injectable({
providedIn: 'root',
})
export class DataService {
loading:Subject<boolean>=new Subject<boolean>();
...
}
我们可以将before observable和this合并,这样我们在app.component中的loading$就变成了
loading$ = merge(this.dataService.loading.pipe(startWith(false)),
this.router.events.pipe(
filter(
x =>
x instanceof NavigationStart ||
x instanceof NavigationEnd ||
x instanceof NavigationCancel ||
x instanceof NavigationError
),
map(x => (x instanceof NavigationStart ? true : false))
)).pipe(
debounceTime(200),
tap(x => console.log(x))
);
所以,我们可以做,例如
this.dataService.loading.next(true)
this.dataService.getData(time).subscribe(res=>{
this.dataService.loading.next(false)
this.response=res;
})
注意:您可以检查stackblitz,单组件没有延迟,所以没有显示“加载”,双组件,延迟是因为CanActivate Guard和因为在ngOnInit中调用服务
注意2:在示例中,我们手动调用“this.dataService.loading.next(true|false)”。我们可以改进它创建一个运算符
答案 3 :(得分:0)
我评论的最后更新总是等待 200 毫秒。当然,我们不想在 Navigation 或 observable 结束时等待。所以我们可以通过一些像 debounceTime(200)
来替换 debounce(x => x ? timer(200) : EMPTY)
,但这使得我们在一个组件的 ngOnInit 中有延迟搜索,“加载器”轻弹。
所以我决定在路由器中使用“数据”来指示哪些组件具有 ngOnInit
想象一些像
const routes: Routes = [
{ path: 'one-component', component: OneComponent },
{ path: 'two-component', component: TwoComponent,
canActivate:[FoolGuard],data:{initLoader:true}},
{ path: 'three-component', component: ThreeComponent,
canActivate:[FoolGuard],data:{initLoader:true} },
];
我创建了一个服务,当路由器有数据“initLoader”并包含 nils' operator
/*Nils' operator: https://nils-mehlhorn.de/posts/indicating-loading-the-right-way-in-angular
*/
export function prepare<T>(callback: () => void): (source: Observable<T>) => Observable<T> {
return (source: Observable<T>): Observable<T> => defer(() => {
callback();
return source;
});
}
export function indicate<T>(indicator: Subject<boolean>): (source: Observable<T>) => Observable<T> {
return (source: Observable<T>): Observable<T> => source.pipe(
prepare(() => indicator.next(true)),
finalize(() => indicator.next(false))
)
}
Nils 操作符的工作原理是,如果你有一个 Subject 和一个 observable
myObservable.pipe(indicate(mySubject)).subscribe(res=>..)
在通话开始时向主题发送真,在通话结束时向主题发送假
好吧,我可以提供类似的服务
/*Service*/
@Injectable({
providedIn: "root"
})
export class DataService {
loading: Subject<boolean> = new Subject<boolean>();
constructor(private router: Router){}
//I use the Nils' operator
getData(time: number) {
return of(new Date()).pipe(delay(time),indicate(this.loading));
}
getLoading(): Observable<any> {
let wait=true;
return merge(
this.loading.pipe(map(x=>{
wait=x
return x
})),
this.router.events
.pipe(
filter(
x =>
x instanceof NavigationStart ||
x instanceof ActivationEnd ||
x instanceof NavigationCancel ||
x instanceof NavigationError ||
x instanceof NavigationEnd
),
map(x => {
if (x instanceof ActivationEnd) {
wait=x.snapshot.data.wait|| false;
return true;
}
return x instanceof NavigationStart ? true : false;
})
))
.pipe(
debounce(x=>wait || x?timer(200):EMPTY),
)
}
这使得延迟发生在 StartNavigation 或“路径”中有数据“wait:true”时。在 stackblitz 中看到“component-three”在路径 data:{wait:true}
中有(可能我们忘记删除它)但没有 ngOnInit。这使得我们有延迟
注意:另一个服务可以使用加载器,只需注入“dataService”并使用 nils 操作符
this.anotherService.getData().pipe(
indicate(this.dataService.loading)
).subscribe(res=>{....})