APP_INITIALIZER 在与正在重定向的自定义 Http 提供程序一起使用时引发 "Cannot instantiate cyclic dependency! ApplicationRef_"
APP_INITIALIZER raises "Cannot instantiate cyclic dependency! ApplicationRef_" when used with a custom Http provider that is redirecting
我正在使用自定义 Http 提供程序来处理 API 身份验证错误。在我的 CustomHttp 中,当 API 发出 401 状态错误时,我需要将用户重定向到登录页面。效果不错!
app.module.ts
export function loadCustomHttp(backend: XHRBackend, defaultOptions: AppRequestOptions,
router: Router, dataHelper: DataHelperService) {
return new CustomHttp(backend, defaultOptions, router, dataHelper);
}
@NgModule({
// some declarations, imports, ...
providers: [
// some services ...
{
provide: Http,
useFactory: loadCustomHttp,
deps: [XHRBackend, RequestOptions, Router, DataHelperService]
}
});
自定义-http.ts
import { Injectable } from '@angular/core';
import { Http, RequestOptions, RequestOptionsArgs, ConnectionBackend, Request, Response } from '@angular/http';
import { Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { DataHelperService } from '../helpers/data-helper.service';
import { AuthStorage } from '../services/auth/auth-storage';
import 'rxjs/add/observable/throw';
import 'rxjs/add/observable/empty';
@Injectable()
export class CustomHttp extends Http {
constructor(backend: ConnectionBackend, defaultOptions: RequestOptions,
private router: Router, private dataHelper: DataHelperService) {
super(backend, defaultOptions);
}
request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
return this.intercept(super.request(url, options));
}
get(url: string, options?: RequestOptionsArgs): Observable<Response> {
return this.intercept(super.get(url, options));
}
post(url: string, body: string, options?: RequestOptionsArgs): Observable<Response> {
return this.intercept(super.post(url, body, options));
}
put(url: string, body: string, options?: RequestOptionsArgs): Observable<Response> {
return this.intercept(super.put(url, body, options));
}
delete(url: string, options?: RequestOptionsArgs): Observable<Response> {
return this.intercept(super.delete(url, options));
}
intercept(observable: Observable<Response>): Observable<Response> {
return observable.catch((err, source) => {
let token = AuthStorage.getToken();
if (err.status === 401 && token && AuthStorage.isTokenExpired()) {
// token has expired -> redirecting user to login
AuthStorage.clearAll();
this.router.navigate(['auth/login']);
}
return Observable.throw(err);
});
}
}
然后,我尝试使用 APP_INITIALIZER
不透明令牌来获取初始化我的应用程序所需的设置。
app.module.ts
@NgModule({
// some declarations, imports, ...
providers: [
// some services ...
ConfigService,
{
provide: APP_INITIALIZER,
useFactory: (config: ConfigService) => () => config.load(),
deps:[ConfigService, Http],
multi: true
}
});
config.service.ts
import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { AppSettings } from '../../environments/app-settings';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/toPromise';
@Injectable()
export class ConfigService {
public settings:AppSettings;
constructor(private http:Http) { }
load() : Promise<AppSettings> {
let url = '/settings/';
var observable= this.http.get(url)
.map(res => res.json());
observable.subscribe(config => this.settings = config);
return observable.toPromise();
}
}
这会产生一个错误:
Uncaught Error: Provider parse errors:
Cannot instantiate cyclic dependency! ApplicationRef_: in NgModule AppModuleNgModuleProviderAnalyzer.parse @ provider_analyzer.js:291NgModuleCompiler.compile @ ng_module_compiler.js:54RuntimeCompiler._compileModule @ runtime_compiler.js:102RuntimeCompiler._compileModuleAndComponents @ runtime_compiler.js:65RuntimeCompiler.compileModuleAsync @ runtime_compiler.js:55PlatformRef_._bootstrapModuleWithZone @ application_ref.js:303PlatformRef_.bootstrapModule @ application_ref.js:285(anonymous function) @ main.ts:18__webpack_require__ @ bootstrap 0e2b412…:52(anonymous function) @ main.bundle.js:86665__webpack_require__ @ bootstrap 0e2b412…:52webpackJsonpCallback @ bootstrap 0e2b412…:23(anonymous function) @ main.bundle.js:1
如果我注释掉自定义 Http 提供程序,则不会显示错误并且 APP_INITIALIZER
按预期工作。
如果我从 Http provider deps 声明中删除 Router
,我就不再有错误,但是我的 ConfigService.load()
函数被调用了两次。
有谁知道为什么这种路由器依赖性会导致这种循环依赖性错误?
如何防止我的 ConfigService.load()
函数被调用两次?
如果需要,我创建了一个 public 重现错误的存储库:https://github.com/haia212/AngularErrorTestProject
问题是 Router
可以异步加载一些路由。这就是为什么它需要 Http
。您的 Http
取决于 Router
,而 Router
取决于 Http
。 Angular 注入器无法创建任何这些服务。
我遇到了类似的问题,解决方案之一是注入 Injector
而不是服务,然后再获取服务。
代码:
@Injectable()
export class CustomHttp extends Http {
constructor(backend: ConnectionBackend, defaultOptions: RequestOptions,
private injector: Injector, private dataHelper: DataHelperService) {
super(backend, defaultOptions);
}
public get router(): Router { //this creates router property on your service.
return this.injector.get(Router);
}
...
所以,基本上,您不需要 Router
来获取 Http
服务的实例。当您访问 router
属性 时注入完成 - 只有当您想要重定向用户时。 router
属性 对代码的其他部分是透明的。
如果它不能解决问题,你可以对其余注入的服务做同样的事情(除了这些调用 super
)。
也许这有帮助;我解决这个问题的方法是改变 CustomHttp
class 的策略,改为使用合成。
我的 CustomHttp
看起来像这样:
@Injectable()
export class CustomHttp {
constructor(private http: Http) {}
现在,我不需要在我的自定义 Http 服务中注入路由器或任何其他服务。
在配置加载器 (config.service.ts
) 中,我做了以下更改:
import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { AppSettings } from '../../environments/app-settings';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/toPromise';
@Injectable()
export class ConfigService {
public settings:AppSettings;
constructor() { }
load(http: Http) : Promise<AppSettings> {
let url = '/settings/';
var observable= http.get(url)
.map(res => res.json());
observable.subscribe(config => this.settings = config);
return observable.toPromise();
}
}
删除了注入 Http
服务依赖项的需要,而是将其添加到 load(http: Http)
方法中。
在我的 app.module.ts
我有以下内容:
providers: [
{
provide: Http,
useFactory: (backend, options) => new CustomHttp(new Http(backend, options)),
deps: [XHRBackend, RequestOptions]
},
ConfigService,
{
provide: APP_INITIALIZER,
useFactory: (config, http) => () => config.load(http),
deps: [ConfigService, Http],
multi: true
},
这是我目前在我的应用程序上使用的。不确定这种方法是否适合您,但希望对您有所帮助。
我只是通过从 deps
声明中删除 Router 来解决它:
{
provide: Http,
useFactory: loadCustomHttp,
deps: [XHRBackend, RequestOptions, DataHelperService]
}
其他一切都保持不变。
感觉有点像魔术,但确实有效。
我正在使用自定义 Http 提供程序来处理 API 身份验证错误。在我的 CustomHttp 中,当 API 发出 401 状态错误时,我需要将用户重定向到登录页面。效果不错!
app.module.ts
export function loadCustomHttp(backend: XHRBackend, defaultOptions: AppRequestOptions,
router: Router, dataHelper: DataHelperService) {
return new CustomHttp(backend, defaultOptions, router, dataHelper);
}
@NgModule({
// some declarations, imports, ...
providers: [
// some services ...
{
provide: Http,
useFactory: loadCustomHttp,
deps: [XHRBackend, RequestOptions, Router, DataHelperService]
}
});
自定义-http.ts
import { Injectable } from '@angular/core';
import { Http, RequestOptions, RequestOptionsArgs, ConnectionBackend, Request, Response } from '@angular/http';
import { Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { DataHelperService } from '../helpers/data-helper.service';
import { AuthStorage } from '../services/auth/auth-storage';
import 'rxjs/add/observable/throw';
import 'rxjs/add/observable/empty';
@Injectable()
export class CustomHttp extends Http {
constructor(backend: ConnectionBackend, defaultOptions: RequestOptions,
private router: Router, private dataHelper: DataHelperService) {
super(backend, defaultOptions);
}
request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
return this.intercept(super.request(url, options));
}
get(url: string, options?: RequestOptionsArgs): Observable<Response> {
return this.intercept(super.get(url, options));
}
post(url: string, body: string, options?: RequestOptionsArgs): Observable<Response> {
return this.intercept(super.post(url, body, options));
}
put(url: string, body: string, options?: RequestOptionsArgs): Observable<Response> {
return this.intercept(super.put(url, body, options));
}
delete(url: string, options?: RequestOptionsArgs): Observable<Response> {
return this.intercept(super.delete(url, options));
}
intercept(observable: Observable<Response>): Observable<Response> {
return observable.catch((err, source) => {
let token = AuthStorage.getToken();
if (err.status === 401 && token && AuthStorage.isTokenExpired()) {
// token has expired -> redirecting user to login
AuthStorage.clearAll();
this.router.navigate(['auth/login']);
}
return Observable.throw(err);
});
}
}
然后,我尝试使用 APP_INITIALIZER
不透明令牌来获取初始化我的应用程序所需的设置。
app.module.ts
@NgModule({
// some declarations, imports, ...
providers: [
// some services ...
ConfigService,
{
provide: APP_INITIALIZER,
useFactory: (config: ConfigService) => () => config.load(),
deps:[ConfigService, Http],
multi: true
}
});
config.service.ts
import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { AppSettings } from '../../environments/app-settings';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/toPromise';
@Injectable()
export class ConfigService {
public settings:AppSettings;
constructor(private http:Http) { }
load() : Promise<AppSettings> {
let url = '/settings/';
var observable= this.http.get(url)
.map(res => res.json());
observable.subscribe(config => this.settings = config);
return observable.toPromise();
}
}
这会产生一个错误:
Uncaught Error: Provider parse errors:
Cannot instantiate cyclic dependency! ApplicationRef_: in NgModule AppModuleNgModuleProviderAnalyzer.parse @ provider_analyzer.js:291NgModuleCompiler.compile @ ng_module_compiler.js:54RuntimeCompiler._compileModule @ runtime_compiler.js:102RuntimeCompiler._compileModuleAndComponents @ runtime_compiler.js:65RuntimeCompiler.compileModuleAsync @ runtime_compiler.js:55PlatformRef_._bootstrapModuleWithZone @ application_ref.js:303PlatformRef_.bootstrapModule @ application_ref.js:285(anonymous function) @ main.ts:18__webpack_require__ @ bootstrap 0e2b412…:52(anonymous function) @ main.bundle.js:86665__webpack_require__ @ bootstrap 0e2b412…:52webpackJsonpCallback @ bootstrap 0e2b412…:23(anonymous function) @ main.bundle.js:1
如果我注释掉自定义 Http 提供程序,则不会显示错误并且 APP_INITIALIZER
按预期工作。
如果我从 Http provider deps 声明中删除 Router
,我就不再有错误,但是我的 ConfigService.load()
函数被调用了两次。
有谁知道为什么这种路由器依赖性会导致这种循环依赖性错误?
如何防止我的 ConfigService.load()
函数被调用两次?
如果需要,我创建了一个 public 重现错误的存储库:https://github.com/haia212/AngularErrorTestProject
问题是 Router
可以异步加载一些路由。这就是为什么它需要 Http
。您的 Http
取决于 Router
,而 Router
取决于 Http
。 Angular 注入器无法创建任何这些服务。
我遇到了类似的问题,解决方案之一是注入 Injector
而不是服务,然后再获取服务。
代码:
@Injectable()
export class CustomHttp extends Http {
constructor(backend: ConnectionBackend, defaultOptions: RequestOptions,
private injector: Injector, private dataHelper: DataHelperService) {
super(backend, defaultOptions);
}
public get router(): Router { //this creates router property on your service.
return this.injector.get(Router);
}
...
所以,基本上,您不需要 Router
来获取 Http
服务的实例。当您访问 router
属性 时注入完成 - 只有当您想要重定向用户时。 router
属性 对代码的其他部分是透明的。
如果它不能解决问题,你可以对其余注入的服务做同样的事情(除了这些调用 super
)。
也许这有帮助;我解决这个问题的方法是改变 CustomHttp
class 的策略,改为使用合成。
我的 CustomHttp
看起来像这样:
@Injectable()
export class CustomHttp {
constructor(private http: Http) {}
现在,我不需要在我的自定义 Http 服务中注入路由器或任何其他服务。
在配置加载器 (config.service.ts
) 中,我做了以下更改:
import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { AppSettings } from '../../environments/app-settings';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/toPromise';
@Injectable()
export class ConfigService {
public settings:AppSettings;
constructor() { }
load(http: Http) : Promise<AppSettings> {
let url = '/settings/';
var observable= http.get(url)
.map(res => res.json());
observable.subscribe(config => this.settings = config);
return observable.toPromise();
}
}
删除了注入 Http
服务依赖项的需要,而是将其添加到 load(http: Http)
方法中。
在我的 app.module.ts
我有以下内容:
providers: [
{
provide: Http,
useFactory: (backend, options) => new CustomHttp(new Http(backend, options)),
deps: [XHRBackend, RequestOptions]
},
ConfigService,
{
provide: APP_INITIALIZER,
useFactory: (config, http) => () => config.load(http),
deps: [ConfigService, Http],
multi: true
},
这是我目前在我的应用程序上使用的。不确定这种方法是否适合您,但希望对您有所帮助。
我只是通过从 deps
声明中删除 Router 来解决它:
{
provide: Http,
useFactory: loadCustomHttp,
deps: [XHRBackend, RequestOptions, DataHelperService]
}
其他一切都保持不变。 感觉有点像魔术,但确实有效。