Javascript 如何在Angular中实现全局加载程序
我有一个全局加载器,其实现方式如下:Javascript 如何在Angular中实现全局加载程序,javascript,angular,rxjs,angular-cdk,Javascript,Angular,Rxjs,Angular Cdk,我有一个全局加载器,其实现方式如下: "@angular/cdk": "~7.0.0", "@angular/material": "~7.0.0", 核心模块: router.events.pipe( filter(x => x instanceof NavigationStart) ).subscribe(() => loaderService.show()); router.events.pipe( filter(x => x instanceof Navig
"@angular/cdk": "~7.0.0",
"@angular/material": "~7.0.0",
核心模块:
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());
装载机服务:
@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/cdk": "~7.2.0",
"@angular/material": "~7.2.0",
我已经确定了7.1.0版中发布的错误提交,并将我的问题发布在相关的网站上。它修复了覆盖
的淡出动画
什么是v7.1+兼容的方式来获得所需的行为?
我认为,最好的办法是:仅在必要时显示加载程序,但NavigationStart
不包含所需信息。
我希望避免以一些去抖动行为结束。在意识到延迟是UX方面的一个很好的解决方案后,这里是我的结束,因为它允许加载程序仅在加载时间值得显示加载程序时才显示 我不喜欢这个解决方案,因为它意味着在两个可观测对象之间共享一个状态,而可观测对象是纯管道,而不是副作用和共享状态
计数器=0;
路由器.事件.管道(
过滤器(x=>x个NavigationStart实例),
延迟(200),
).订阅(()=>{
/*
如果此条件为true,则对应于此NavigationStart结束的事件
尚未通过,因此我们显示加载程序
*/
if(this.counter==0){
loaderService.show();
}
这个.counter++;
});
路由器.事件.管道(
过滤器(x=>x导航实例结束| | x导航实例取消| | x导航实例错误)
).订阅(()=>{
这个柜台;
loaderService.hide();
});
我们在系统中实现加载器的方式以及例外列表:
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;
})
);
}
}
试图改进@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>
所以,我们可以做,例如
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.load.next(true | false)”。我们可以通过创建一个操作符来改进它我的评论的最后一次更新总是等待200毫秒。当然,我们不希望在导航结束时或在可观测的时间结束时等待。因此,我们可以通过一些类似于
debounce(x=>x?timer(200):EMPTY)
的方法重新使用debounceTime(200)
,但这使得我们在ngOnInit中有一个延迟搜索的组件,“loader”flick
因此,我决定在路由器中使用“数据”来指示哪些组件具有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”中包含
在通话开始时向主题发送true,在通话结束时向主题发送false
好吧,我可以做这样的服务
/*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),
)
}
是否有可能在没有触发器的情况下执行
loaderService.hide()
?您是否询问它是否从其他地方调用?这可能是我从未考虑过的选项,但我的意思是,它可以在没有任何触发器的情况下执行,并且您使用的符号只是解释为要执行的代码,而不是带有函数的OOP结构。@David抱歉,我真的不明白您的意思。我已经在Angular CDK中识别了错误的pull请求:它只适用于http请求,不适用于懒惰模块我认为最好创建一个独特的可观察对象。在html管道中使用异步是没有必要的。我们的示例应该显示一个使用Angular的覆盖的加载程序,因为最初的问题是覆盖在不需要时短暂显示。Guerric,我用一个带*ngIf的简单div,您可以使用任何组件或带覆盖的div。老实说,我不喜欢使用“计数器”或订阅两个观察值,但这是个人观点。使用一个独特的可观测值和一个去盎司,避免它,但当然,你的代码也是一个很好的方法。我也不喜欢它,这就是为什么我要求另一个答案与我的赏金。带有*ngIf的div的行为与覆盖不同,因为覆盖从v7开始逐渐消失,在以前的版本中,我们总是可以在很短的时间间隔内显示/隐藏它,它不可见,但这是因为v7YourdebounceTime
应用于true
和false
事件,这可能会导致跳过事件needed@GuerricP,我更新了stackblitz。我在“第二个组件”中使用AuthGuard。您可以在更改第二个组件之前看到“加载”。我认为在延迟加载的情况下应该可以工作。我希望这个例子能帮助我更好地解释。
this.dataService.loading.next(true)
this.dataService.getData(time).subscribe(res=>{
this.dataService.loading.next(false)
this.response=res;
})
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} },
];
/*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))
)
}
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),
)
}
this.anotherService.getData().pipe(
indicate(this.dataService.loading)
).subscribe(res=>{....})