1

I am creating an extended class of Angular4 Http, so that I can manage the authentication in this top layer service by adding auth header and handling the logout on authentication failure. Here is the service,

import { Injectable } from '@angular/core';
import { Http, XHRBackend, RequestOptions, Request, RequestOptionsArgs, Response, Headers } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import { AuthService } from '../../auth/auth.service';

@Injectable()
export class HttpService extends Http {
    constructor(backend: XHRBackend, options: RequestOptions, private authService: AuthService) {
        super(backend, options);
    }

    request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
        let token = localStorage.getItem('accessToken');
        if (typeof url === 'string') { // meaning we have to add the token to the options, not in url
            if (!options) {
                // let's make option object
                options = { headers: new Headers() };
            }
            options.headers.set('Authorization', `Bearer ${token}`);
        } else {
            // we have to add the token to the url object
            url.headers.set('Authorization', `Bearer ${token}`);
        }
        return super.request(url, options).catch(this.catchAuthError(this));
    }

    private catchAuthError(self: HttpService) {
        // we have to pass HttpService's own instance here as `self`
        return (res: Response) => {
            console.log(res);
            if (res.status === 401 || res.status === 403) {
                // if not authenticated
                console.log(res);
            }
            return Observable.throw(res);
        };
    }
}

I am using it in my app.module.ts file as follows.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
// import { HttpModule } from '@angular/http';
import { HttpModule, RequestOptions, XHRBackend } from '@angular/http';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ReactiveFormsModule } from '@angular/forms';
import { MdMenuModule } from '@angular/material';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';


// Services
import { AuthService } from './auth/auth.service';
import { AuthGuard } from './shared/auth-guard.service';

import { SharedModule } from './shared/shared/shared.module';
import { HttpService } from './shared/services/http.service';
export function uFact (backend: XHRBackend, options: RequestOptions) {
    return new HttpService(backend, options);
}
@NgModule({
    declarations: [
        AppComponent
    ],
    imports: [
        BrowserModule,
        HttpModule,
        AppRoutingModule,
        ReactiveFormsModule,
        SharedModule,
        BrowserAnimationsModule,
        MdMenuModule
    ],
    providers: [
        AuthService,
        AuthGuard,
        {
            provide: HttpService,
            useFactory: uFact,
            deps: [XHRBackend, RequestOptions]
        }
    ],
    bootstrap: [AppComponent]
})
export class AppModule { }

I have to add AuthService in the constructor of HttpService like private authService: AuthService . But it throws an error in app.module.ts. I can't instantiate new HttpService(backend, options) in the exported function uFact since it needs one more parameter in its constructor. How can I solve this? I am new to Angular.

shafeequemat
  • 1,002
  • 2
  • 17
  • 22
  • Have you tried using `Injector`? https://stackoverflow.com/questions/46362598/angular-4-httpinterceptor-refresh-token/46362695#46362695 – yurzui Sep 23 '17 at 03:15
  • I can't pass anything to the constructor, not even private injector: Injector. Showing an error, A 'super' call must be the first statement in the constructor when a class contains initialized properties or has parameter properties. – shafeequemat Sep 23 '17 at 03:39
  • Move it up like `constructor(backend: XHRBackend, options: RequestOptions) { super(backend, options); let token = loc....` The error states what you should do – yurzui Sep 23 '17 at 03:41
  • Then how can I pass token and headers to options? – shafeequemat Sep 23 '17 at 03:44
  • You will pass it in `request` method – yurzui Sep 23 '17 at 03:47

2 Answers2

1

You can solve this by adding Authservice to 'deps' because deps will help you to load AuthService dependency to HttpService.

  providers: [
    AuthService,
    AuthGuard,
    {
        provide: HttpService,
        useFactory: uFact,
        deps: [XHRBackend, RequestOptions,AuthService] // here
    }
],

.....

the deps property is an array of provider tokens so that the services you are mentioning here will serve as a token for their own classes and The injector resolves these tokens and injects the corresponding services into the matching factory function parameters.

If there is a problem with your constructor of HttpService then you can declare the AuthService variable as optional.(constructor(backend: XHRBackend, options: RequestOptions, private authService?: AuthService)) . So the compiler won't show any argument missing error. but it will cause error in future.

Vishnu KR
  • 718
  • 1
  • 8
  • 22
0

You can write it as:

constructor(backend: XHRBackend, options: RequestOptions) {
   super(backend, options);
   let token = localStorage.getItem('accessToken'); 
   options.headers.set('Authorization', `Bearer ${token}`);
}

It shouldn't matter that you set the header after super since the options are used when the actual request is made.

yurzui
  • 205,937
  • 32
  • 433
  • 399
sirrocco
  • 7,975
  • 4
  • 59
  • 81