Angular2:解决Http拦截工厂和auth服务之间的循环依赖

Angular2: Resolving a cyclic dependency between Http intercepting factory and auth service

我有以下授权服务:

@Injectable()
export class AuthService {

  //...

  constructor(private http: Http, private router: Router) {
    //...
  }

  public login(username: string, password: string): Observable<boolean> {
    // perform login
  }

  public logout() {
    // perform cleanup
    this.router.navigateByUrl('/login');
  }
}

以及以下 Http 拦截器工厂:

@Injectable()
class MyHttpInterceptor extends Http {

  constructor(backend: ConnectionBackend, defaultOptions: RequestOptions, private authService: AuthService) {
    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, this.getRequestOptionArgs(options)));
  }

  put(url: string, body: string, options?: RequestOptionsArgs): Observable<Response> {
    return this.intercept(super.put(url, body, this.getRequestOptionArgs(options)));
  }

  delete(url: string, options?: RequestOptionsArgs): Observable<Response> {
    return this.intercept(super.delete(url, options));
  }

  getRequestOptionArgs(options?: RequestOptionsArgs): RequestOptionsArgs {
    if (options == null) {
      options = new RequestOptions();
    }
    if (options.headers == null) {
      options.headers = new Headers();
    }
    // add headers required by our backend
    return options;
  }

  intercept(observable: Observable<Response>): Observable<Response> {
    return observable.catch((err, source) => {
      if (err.status == 401) {
        this.authService.logout();
        return Observable.empty();
      } else {
        return Observable.throw(err);
      }
    });

  }
}

export function myHttpInterceptorFactory(backend: ConnectionBackend, options: RequestOptions, authService: AuthService): MyHttpInterceptor {
  return new MyHttpInterceptor(backend, options, authService);
}

基本上这里的要求是,如果从后端收到状态为 401 的任何响应,则应该开始注销过程。

App模块中设置如下:

@NgModule({
  imports: [
    HttpModule,
    //...
  ],
  declarations: [
    AppComponent,
    //...
  ],
  providers: [
    {
      provide: Http,
      useFactory: myHttpInterceptorFactory,
      deps: [XHRBackend, RequestOptions, AuthService]
    },
    AuthService,
    //...
  ],
  bootstrap: [AppComponent]
})
export class AppModule {
}

这会造成循环依赖错误,其中 Http 拦截器需要 AuthService,但 AuthService 需要 Http.

Error: Provider parse errors:
Cannot instantiate cyclic dependency! Http: in NgModule AppModule in ./AppModule

我尝试使用 forwardRefAuthService 中注入 Http,但这并没有改变任何东西。

任何关于如何重组的帮助都会很棒。

Basically the requirement here is that if any response is ever received from the backend with status 401, the logout procedure should start.

如果目标是以特定方式处理 HTTP 错误,这就是我要做的:我不会扩展 HTTP 服务,而是为我的服务创建一个基础 class 来扩展处理重复的 HTTP 功能,例如提取数据或处理错误。它看起来像这样:

import { Response } from '@angular/http';
import { Observable } from 'rxjs/Observable';

export class HttpServiceBase {
    constructor(private authSvc: AuthService ) { }

    extractData(res: Response) {
        const body = res.json();
        return body || {};
    }

    handleError(error: Response | any) {
        switch (error.status) {
            .... //other cases

            case 401:
               this.authSvc.logout();

            default:
                //default handling
        }

    }
}

然后像这样使用它:

@Injectable()
export class SomeService extends HttpServiceBase {

    constructor(
        authSvc: AuthService,
        private http: AuthHttp
    )
    {
        super(authSvc);
    }


    sampleCall() {
        return this.http.get(...)
            .map(this.extractData)
            .catch(this.handleError);
    }

}

这解决了循环依赖。

希望对您有所帮助。