Angular 17独立组件加载器拦截器失效及API调用报错排查
Angular 17 独立组件模式下加载器失效及「Cannot read properties of undefined (reading 'apply')」错误修复
问题根源
- 服务实例隔离:
LoadingComponent的组件元数据中添加了providers: [LoaderService],导致组件创建了独立的服务实例,和全局root级别的LoaderService状态无法同步。 - RxJS 操作符版本不兼容:
ProgressInterceptor使用了旧版RxJS的finally操作符,Angular 17配套的RxJS已改用finalize操作符,且导入方式错误。 - AppComponent代码不完整:缺少
Router注入、构造函数语法错误、未导入LoaderService。 - 路由逻辑冗余:每次路由事件都触发
startLoading,导致重复加载状态。
分步修复
1. 统一LoaderService实例
移除LoadingComponent中providers数组里的LoaderService,确保全局共享同一个服务实例:
// loading.component.ts @Component({ selector: 'loading', templateUrl: './loading.component.html', styleUrls: ['./loading.component.css'], standalone: true, imports: [CommonModule], // 移除该行:providers:[LoaderService] })
2. 替换RxJS操作符
将finally改为新版finalize操作符,并修正导入路径:
// progress.interceptor.ts import { Injectable } from '@angular/core'; import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http'; import { Observable } from 'rxjs'; // 替换旧导入,使用rxjs官方操作符 import { finalize } from 'rxjs/operators'; import { LoaderService } from '../service'; @Injectable() export class ProgressInterceptor implements HttpInterceptor { constructor(private loaderSvc: LoaderService) {} intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { if (request.params.has('ignoreLoadingBar')) { return next.handle(request.clone({ headers: request.headers.delete('ignoreLoadingBar') })); } this.loaderSvc.startLoading(); // 用finalize替代finally,通过pipe包裹 return next.handle(request).pipe( finalize(() => this.loaderSvc.stopLoading()) ); } }
3. 修复AppComponent代码缺陷
补充依赖注入,修正语法错误,优化路由事件逻辑:
// app.component.ts import { Component, OnInit } from '@angular/core'; import { Router, NavigationEnd, RouterModule } from '@angular/router'; import { LoadingComponent } from './shared/components'; import { LoaderService } from './shared/service'; // 补充WINDOW令牌的导入路径 import { WINDOW } from './path-to-window-token'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], standalone: true, imports: [RouterModule, NgIdleKeepaliveModule, LoadingComponent], providers: [{ provide: WINDOW, useValue: window }] // 修正useValue为实际window对象 }) export class AppComponent implements OnInit { constructor( private router: Router, public loaderSVC: LoaderService ) {} ngOnInit() { this.router.events.subscribe((evt) => { // 仅在非NavigationEnd事件时启动加载,导航完成后停止 if (evt instanceof NavigationEnd) { setTimeout(() => this.loaderSVC.stopLoading()); } else { this.loaderSVC.startLoading(); } }); } }
4. 验证拦截器配置
确认appConfig中HTTP_INTERCEPTORS配置正确,当前配置已满足要求,无需修改:
// app.config.server.ts export const appConfig: ApplicationConfig = { providers: [ provideRouter(routes), provideClientHydration(withHttpTransferCacheOptions({ includePostRequests: true })), provideNgxMask(), { provide: HTTP_INTERCEPTORS, useClass: ProgressInterceptor, multi: true }, { provide: HTTP_INTERCEPTORS, useClass: BasicAuthInterceptor, multi: true }, provideAnimations(), CookieService, StorageService, provideToastr({ positionClass: 'toast-top-right' }), importProvidersFrom(ReactiveFormsModule, HttpClientModule), ] };
额外优化:避免内存泄漏
使用takeUntil操作符替代手动取消订阅,简化代码同时防止内存泄漏:
// loading.component.ts import { Component, OnInit, OnDestroy } from '@angular/core'; import { LoaderService } from '../../service'; import { Subject, takeUntil } from 'rxjs'; import { LoaderState } from '../../models/loader.model' import { CommonModule } from '@angular/common'; @Component({ selector: 'loading', templateUrl: './loading.component.html', styleUrls: ['./loading.component.css'], standalone: true, imports: [CommonModule] }) export class LoadingComponent implements OnInit, OnDestroy { show = false; private destroy$ = new Subject<void>(); constructor(private loaderSvc: LoaderService) { } ngOnInit() { this.loaderSvc.loaderState .pipe(takeUntil(this.destroy$)) .subscribe((state: LoaderState) => { this.show = state.show; }); } ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); } }
内容的提问来源于stack exchange,提问作者Palak Gandhi
相关产品推荐
相关产品推荐

