Angular 5中注入服务时出现Http拦截器错误

23

在使用Angular 5+中自定义HttpInterceptors时,我遇到了以下奇怪的依赖注入行为。

下面的简化代码可以正常工作:

    export class AuthInterceptor implements HttpInterceptor {
        constructor(private auth: AuthService) {}

        intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
            const token = this.auth.getToken();
            return next.handle(req);
        }
    }
    export class AuthService {
        token: string;
        constructor() {
          console.log('AuthService.constructor');
        }
    }

然而...

AuthService 有一个或多个自身依赖时,例如:

   export class AuthService {
      token: string;
      constructor(private api: APIService) {
         console.log('AuthService.constructor');
      }
   }

Angular尝试反复创建AuthService的新实例,直到我收到以下错误:

日志显示AuthService.constructor消息约400次

Cannot instantiate cyclic dependency! HTTP_INTERCEPTORS ("[ERROR ->]"): in NgModule AppModule

app.component.html:44 ERROR RangeError: Maximum call stack size exceeded

然后我尝试使用Injector类注入服务 -

 export class AuthService {
      token: string;
      api: APIService;
      constructor(private injector: Injector) {
         this.api = this.injector.get(APIService);
         console.log('AuthService.constructor');
      }
   }

但是出现了相同的错误(最大调用栈大小)。

APIService 是一个简单的服务,只在它的构造函数中注入 HttpClient

@Injectable()
export class APIService {
    constructor(private http: HttpClient) {}
}
最后,当我使用InjectorAuthService注入拦截器时,错误消失了,但AuthService被实例化了200多次:
export class AuthInterceptor implements HttpInterceptor {
    auth: AuthService;
    constructor(private injector: Injector) {}
    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
           this.auth = this.auth || this.injector.get(AuthService);
           const token = this.auth.getToken();
           return next.handle(req);
        }
    }

通过查看官方文档和其他示例,似乎在 Http 拦截器中注入服务是技术上可能的。是否存在任何限制或可能缺失的其他设置?


@SurenSrapyan 更新了问题。 - dev7
你能用 StackBlitz 演示重现它吗? - Max Koretskyi
我遇到了一个类似的问题,使用了与拦截器耦合的相同认证服务设计。在我的情况下,我发现原因是我试图在认证服务的构造函数中启动一个HTTP请求。 - Edward Liang
对于其他遇到类似问题的人,尝试在你的拦截器中添加这个修饰符 @Injectable({ providedIn: 'root' }) export class HttpErrorInterceptor implements HttpInterceptor {,这对我有效。 - Diosney
8个回答

12

2018年1月底更新

Angular团队在2018年1月31日发布的Angular 5.2.3版本中解决了这个问题。更新Angular版本后,您将能够像以前一样在构造函数中注入使用HTTPClient的服务。

错误修复

common: 允许HttpInterceptors注入HttpClient (#19809) (ed2b717),关闭#18224

来自Angular更改日志


8
所以事实证明,如果您注入到Http拦截器中的服务具有对HttpClient的依赖,这将导致循环依赖。由于我的AuthService是各种逻辑的混合体(登录/退出、用户路由、保存/加载令牌、进行API调用),因此我将所需的部分分离成自己的服务(只包括用户凭据和令牌),现在成功地将其注入到拦截器中。
export class AuthInterceptor implements HttpInterceptor {
    constructor(private credentials: CredentialsService) {}
    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        const token = this.credentials.getToken();
        const api_key = this.credentials.getApiKey();
    }
}

export class CredentialsService {
    token: string;
    user: IUser;
    constructor(private http: HttpClient) {
        this.loadCredentialsFromStorage();
    }
}

这似乎完美运作。希望能够帮助到某些人。


1
你的代码与第一行相矛盾,在你的代码中注入了一个依赖于HttpClient的服务,那么这如何解决问题呢? - sarfrazanwar

4

您需要将身份验证服务添加为拦截器的依赖项。

providers: [ 
{
  provide: HTTP_INTERCEPTORS,
  useClass: HttpConfigInterceptor,
  multi: true,
  deps: [AuthService]
}

我尝试了这种方法,但不幸的是,结果还是一样。 - Filip Kováč

3

你需要在构造函数中添加 Injector 并通过它注入 AuthService

export class AuthInterceptor implements HttpInterceptor {
            constructor(private inj: Injector) {}

            intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
                const auth = this.inj.get(AuthService);
                const token = this.auth.getToken();
                return next.handle(req);
            }
        }

不要忘记导入

import {Injector} from '@angular/core';

谢谢,但正如我在问题中提到的那样,我已经尝试过了,仍然出现错误。 - dev7
注入器将为auth服务创建多个实例。因此,注入器将无法正常工作。 - Prosenjit Manna

2

我遇到了一个类似的问题,涉及到认证服务和拦截器的相同设计。

@Injectable() AuthInterceptorService {
    constructor (authApi: AuthApiService) {}
    handle () {...do the job}
}

@Injectable() AuthApiService {
   constructor () {
       // ...do some setup with a request, such as to get current session
       // that leads to an indirect circular between 2 constructors. 
   }

在我的情况下,我发现问题的原因是我尝试在auth服务的构造函数中启动一个http请求。此时,注入器似乎还没有完成对auth服务实例的注册,而http客户端捕获了新请求并尝试再次实例化拦截器,因为先前的拦截器实例也被卡在其构造函数中的调用堆栈上!

这两个构造函数的递归调用打破了注入器的单例模式,并导致调用堆栈溢出。


你是怎么解决你所描述的问题的?我也刚遇到了完全相同的问题。 - poprogrammer
2
不要在拦截器的构造函数及其依赖项中请求http资源。您可以将其推迟到Webhooks。 - Edward Liang
2
感谢您回复,Edward。Webhooks并不能解决我的问题。我的类(实际上是服务)需要从http请求中获取信息进行初始化。最终我将代码从构造函数中移出到一个单独的方法中。然后我从APP_INITIALIZER调用该方法,以确保服务完全初始化,以供应用程序的其余部分使用。这对我很有效,但可能不适用于所有人,因为它会使http请求在应用程序启动之前同步运行。感谢您发布此答案,它帮助我解决了我的应用程序中的这个问题。 - poprogrammer
@poprogrammer,我查看了关于APP_INITIALIZER的文档,我相信你已经设计好了初始化代码的方式。感谢更新。 - Edward Liang

2

对我来说,@Benjineer的回答帮助我在app.module.ts中提供了所需的服务依赖项。

有趣的是,你在app.module.ts中添加依赖项的顺序,决定了你在服务构造函数中的顺序。

例如,在应用程序模块中,您按照AuthService,ErroMsgService的顺序提供。

deps: [AuthService, ErrorMsgService]

在HttpInterceptor构造函数中,您必须按照相同的顺序创建它们。
 constructor(
    private authService: AuthService,
    private errorService: ErrorMsgService
    ) {}

1
对于这个问题,请确保你在模块中将要注入的服务与HTTP_INTERCEPTORS一起添加到providers中。
providers: [ AuthService,
    {
      provide: HTTP_INTERCEPTORS,
      useClass: HttpConfigInterceptor,
      multi: true
    }
]

0
如果服务类在注入器类的同一文件中声明,则应先声明和定义服务类,然后在注入器类中引用它作为依赖项。
按照上述结构进行操作可以解决我的问题。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接