15

I create custom XHRBackend class to catch 401 error globally. In AuthService I have 2 methods which use http - login and refreshToken. So i have dependency chain like that: Http -> customXHRBackend -> AuthService -> Http. How can I fix this?

export class CustomXHRBackend extends XHRBackend {
  constructor(browserXHR: BrowserXhr,
              baseResponseOptions: ResponseOptions,
              xsrfStrategy: XSRFStrategy,
              private router: Router,
              private authService: AuthService) {
    super(browserXHR, baseResponseOptions, xsrfStrategy);
  }

  createConnection(request: Request): XHRConnection {
    let connection: XHRConnection = super.createConnection(request);
    connection.response = connection.response
      .catch(this.handleError.bind(this));

    return connection;
  }

  handleError(error: Response | any) {
    console.log('ERROR',error['status']);
    if(error['status'] === 401) {
      this.authService.logout();
      this.router.navigate(['/']);
    }

    return Observable.throw(error);
  }
}

AuthService.ts

@Injectable()
export class AuthService {
  private loggedIn: boolean = false;

  constructor(private http: Http) {
    this.loggedIn = !!localStorage.getItem('authToken');
  }

  login(email: string, password: string): Observable<Response> {
    let headers: Headers = new Headers();
    headers.set('Content-Type', 'application/json');

    return this.http.post('https://httpbin.org/post',
      {
        email: email,
        password: password
      },
      {
        headers: headers
      })
      .map((response) => {
        let res = response.json();

        // if (res['success']) {
        if (res) {
          localStorage.setItem('authToken', res['token']);
          localStorage.setItem('refreshToken', res['refreshToken']);
          console.log('logged');
          this.loggedIn = true;
        }

        return response;
      }
    );
  }

  logout(): void {
    localStorage.removeItem('authToken');
    this.loggedIn = false;

    console.log('Logged out');
  }

  isLogged(): boolean {
    return this.loggedIn;
  }

  refreshToken(): Observable<Response> {
    let headers: Headers = new Headers();
    headers.set('token', localStorage.getItem('token'));
    headers.set('refreshToken', localStorage.getItem('refreshToken'));

    return this.http.get('https://httpbin.org/get', {
      headers: headers
    });
  }

}

Include CustomXHRBackend in app.module.ts

{
      provide: XHRBackend,
      useFactory: (browserXHR: BrowserXhr,
                   baseResponseOptions: ResponseOptions,
                   xsrfStrategy: XSRFStrategy,
                   router: Router,
                   authService: AuthService) => {
        return new CustomXHRBackend(browserXHR, baseResponseOptions, xsrfStrategy, router, authService);
      },
      deps: [BrowserXhr, ResponseOptions, XSRFStrategy, Router, AuthService]
    }
Eran Shabi
  • 14,201
  • 7
  • 30
  • 51
baidario
  • 239
  • 3
  • 8
  • Check this http://stackoverflow.com/questions/40525850/circular-dependency-injection-angular-2/40525992#40525992 – yurzui Dec 20 '16 at 13:31
  • I saw that answer, but maybe there is better way than use setTimeout – baidario Dec 20 '16 at 13:34
  • http://stackoverflow.com/questions/40826073/pass-parameter-to-mddialog-in-angular-material-2/40828423#40828423 – yurzui Dec 20 '16 at 13:36
  • I don't think you should use `AuthService` in your `CustomXHRBackend`, as `XHRBackend` is lower level than your `AuthService`. That looks like an anti-pattern, even if it's convenient to handle errors only once, if there is a solution it might be a bit "hacky". – n00dl3 Dec 20 '16 at 13:39
  • @n00dl3 to handle http error globally I found two ways, first i described above and second is extend Http class and add rxjs catch method for request method. Does second way ok or it is bad practise? – baidario Dec 20 '16 at 14:06
  • extending `Http` sounds like a better solution to me. But you will end up with same problem because `AuthService` will need `Http` and vice versa. Maybe you could add a 3rd service like `AuthErrorHandler` that will handle the errors without depending neither on `Http` nor `AuthService`. – n00dl3 Dec 20 '16 at 14:09
  • 1
    So what about the answer in the first comment? (or http://stackoverflow.com/questions/40860202/di-with-cyclic-dependency-with-custom-http-and-configservice/40860233#40860233) – Günter Zöchbauer Dec 20 '16 at 14:17
  • Possible duplicate of [DI with cyclic dependency with custom HTTP and ConfigService](https://stackoverflow.com/questions/40860202/di-with-cyclic-dependency-with-custom-http-and-configservice) – Zze Dec 15 '17 at 22:31

2 Answers2

1

How about HTTP Interceptors... There's a blog post here.
If you Google you'll find more... Here's how you hook one into you App Module you can clone the request in you interceptor and add X-CustomAuthHeader into headers etc.

JGFMK
  • 8,425
  • 4
  • 58
  • 92
0

Please see in your constructor where you inject dependency. You can't inject in a few Services the same dependency.Example: CustomXHRBackend => AuthService, AuthService => CustomXHRBackend

Maxim
  • 21
  • 3