Angular HttpInterceptor - любая внедренная служба возвращает неопределенную ошибку типа

Проблема:

Я не могу внедрить ЛЮБУЮ службу в конструктор моих HttpInterceptors. Любая служба, которую я пытаюсь внедрить в службу, выдает следующую ошибку:

TypeError: Cannot set property 'authenticationService' of undefined

Это касается даже фиктивной foo службы с единственной функцией bar и без дополнительных зависимостей, введенных в нее.

КОД

interceptor.ts

import { Injectable, Injector } from '@angular/core';
import {
    HttpRequest,
    HttpHandler,
    HttpEvent,
    HttpInterceptor
} from '@angular/common/http';
import { AuthenticationService } from '../authentication/authentication.service';
import { Observable } from 'rxjs';

@Injectable({
    providedIn: 'root'
})
export class Interceptor implements HttpInterceptor {

    constructor(private authenticationService: AuthenticationService) { }

    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        if (request.url.includes(location.hostname) && this.authenticationService.getToken()) {
            console.log('Headers added to the HTTP Request');
            request = request.clone({
                setHeaders: {
                    Authorization: `Bearer ${this.authenticationService.getToken()}`
                }
            });
        }
        return next.handle(request);
    }
}

authentication.service.ts

import { OnInit, Injectable } from '@angular/core';
import { AuthServiceConfig, AuthService as SocialAuthService, FacebookLoginProvider, GoogleLoginProvider, SocialUser} from 'angularx-social-login';
import { HttpClient, HttpRequest } from  '@angular/common/http';
import { BehaviorSubject, Observable } from 'rxjs';
import { environment } from '../../../environments/environment';
import { JwtHelperService } from '@auth0/angular-jwt';

@Injectable( { providedIn: "root" } )

export class AuthenticationService implements OnInit{

  jwtHelper: JwtHelperService = new JwtHelperService();
  socialLoginConfig: AuthServiceConfig;
  loggedIn: BehaviorSubject<Boolean> = new BehaviorSubject<Boolean>(false);
  loggedIn$: Observable<Boolean> = this.loggedIn.asObservable();
  user: BehaviorSubject<SocialUser> = new BehaviorSubject(null);
  user$: Observable<SocialUser> = this.user.asObservable();

  cachedRequests: Array<HttpRequest<any>> = [];

  constructor(private socialAuthService: SocialAuthService, private http: HttpClient) { }

  ngOnInit() {
    this.socialAuthService.authState.subscribe((user) => {
      this.user.next(user);
      this.loggedIn.next((user != null));
    });
  }

  provideConfig() {
    return this.socialLoginConfig;
  }

  getToken(): string {
    return localStorage.getItem('token');
  }

  refreshToken(): void {
    // Need to add a call to refresh the token (JWT) to the backend.
  }

  isAuthenticated(): boolean {
    const token = this.getToken();
    return token != null && !this.jwtHelper.isTokenExpired(token);
  }

  collectFailedRequest(request): void {
    this.cachedRequests.push(request);
  }

  retryFailedRequests(): void {
    // Once the token has been refreshed, we can send previous failed requests from the cahcedRequests array...
  }

  signInWithGoogle(cb): void {
    this.socialAuthService.signIn(GoogleLoginProvider.PROVIDER_ID).then(
      (userData) => { //on success
        console.log('google', userData);
        this.user.next(userData);
        this.sendToRestApiMethod(userData.idToken, 'google', cb);
      }
    ).catch(err => {
      console.log('Error logging into Google Services:', err);
    });
  }

  signInWithFB(cb): void {
    this.socialAuthService.signIn(FacebookLoginProvider.PROVIDER_ID).then(
      (userData) => { //on success
        console.log('facebook', userData);
        this.user.next(userData);
        this.sendToRestApiMethod(userData.authToken, 'facebook', cb);
      }
    ).catch(err => {
      console.log('Error logging into Facebook Services:', err);
    });
  }

  signOut(): void {
    this.socialAuthService.signOut();
    this.user.next(null);
  }

  sendToRestApiMethod(token: string, provider: string, cb) : void {
    this.http.post(environment.apiBaseUrl +'oauth2/authorization/' + provider, { token: token } )
      .subscribe(jwt => {
       console.log('login was successful', jwt);
       localStorage.setItem('token', jwt['jwt']);
       cb();
     }, onFail => {
        console.log('login was unsuccessful', onFail);
        //show an error message
     }
   );
 }
}

app.module.ts

import { environment } from '../environments/environment';
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

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

import { LoginComponent } from './modules/public/pages/login/login.component';
import { SocialLoginModule, AuthServiceConfig } from "angularx-social-login";
import { GoogleLoginProvider, FacebookLoginProvider } from "angularx-social-login";

import { NavbarComponent } from './shared/components/navbar/navbar.component';
import { FooterComponent } from './shared/components/footer/footer.component';
import { HomeComponent } from './modules/public/pages/home/home.component';
import { UserComponent } from './modules/secure/pages/user/user.component';

import { HttpClientModule, HTTP_INTERCEPTORS} from '@angular/common/http';
import { Interceptor } from './core/interceptors/interceptor';
import { DashboardComponent } from './modules/secure/pages/dashboard/dashboard.component';

const socialLoginConfig = new AuthServiceConfig([
  { id: GoogleLoginProvider.PROVIDER_ID, 
    provider: new GoogleLoginProvider(environment.google.clientid) },
  { id: FacebookLoginProvider.PROVIDER_ID, 
    provider: new FacebookLoginProvider(environment.facebook.appid) }
]);

export function provideConfig() {
  return socialLoginConfig;
}
@NgModule({
  declarations: [
    AppComponent,
    HomeComponent,
    LoginComponent,
    UserComponent,
    NavbarComponent,
    FooterComponent,
    DashboardComponent
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    AppRoutingModule,
    SocialLoginModule,
    HttpClientModule
  ],
  providers: [
    { provide: AuthServiceConfig, useFactory: provideConfig },
    { provide: HTTP_INTERCEPTORS, useFactory: Interceptor, multi: true }
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

Для людей, сомневающихся в версии Angular ...

Angular CLI: 7.2.3
Node: 11.7.0
OS: darwin x64
Angular: 7.2.2
... animations, cdk, common, compiler, compiler-cli, core, forms
... language-service, platform-browser, platform-browser-dynamic
... router

Package                           Version
-----------------------------------------------------------
@angular-devkit/architect         0.12.3
@angular-devkit/build-angular     0.12.3
@angular-devkit/build-optimizer   0.12.3
@angular-devkit/build-webpack     0.12.3
@angular-devkit/core              7.2.3
@angular-devkit/schematics        7.2.3
@angular/cli                      7.2.3
@ngtools/webpack                  7.2.3
@schematics/angular               7.2.3
@schematics/update                0.12.3
rxjs                              6.3.3
typescript                        3.2.4
webpack                           4.28.4

Проведено исследование / устранение неполадок

Я нашел различные сообщения SO и другие сообщения на форуме, описывающие проблемы при внедрении службы, содержащей Ссылка HttpClient вызовет циклическую ошибку зависимости, приводящую к неопределенному, а authenticationService ДЕЙСТВИТЕЛЬНО имеет ссылку на HTTPClient. Однако, согласно сообщениям, это было исправлено в патче Angular 5. Как видите, в настоящее время я использую Angular 7 для этого проекта.

Я попытался следовать этим инструкциям и вместо того, чтобы вводить authenticationService в конструктор, вводил инжектор, а затем выполнял:

this.authenticationService = this.injector.get(AuthenticationService);

Это приводит к ошибке:

TypeError: Cannot set property 'injector' of undefined

Я также попытался изменить поставщика app.module.ts для HttpInterceptor на следующее:

{ provide: HTTP_INTERCEPTORS, useFactory: Interceptor, multi: true, deps: [AuthenticationService] }

Это снова привело к той же неопределенной ошибке.

Вывод:

Приношу свои извинения за роман, но я хотел убедиться, что у всех есть вся возможная информация, которая поможет решить эту проблему, которая вызвала у меня головные боли на 3 дня. Заранее всем спасибо!


person Ryan C    schedule 08.02.2019    source источник
comment
Есть ли шанс, что сервисы, которые вы используете в Interceptor, сами требуют HttpClient? И разве это не должно быть useClass: Interceptor?   -  person jonrsharpe    schedule 08.02.2019
comment
useClass: Interceptor и удалите { providedIn: "root" } на обоих   -  person William Lohan    schedule 08.02.2019
comment
@jonrsharpe Боже мой, как я этого не заметил ... ты прав, это должен быть useClass, а не useFactory ... это (также удалил предоставленныйIn: root - спасибо Уильям) исправил проблемы ... Спасибо тебе так нравится. Если вы хотите быстро ответить, я бы хотел отметить его как правильный, чтобы никому больше не приходилось сталкиваться с глупой ошибкой, которую я только что совершил.   -  person Ryan C    schedule 08.02.2019
comment
Извините, что перепутали AuthServiceConfig с AuthenticationService , просто удалите { providedIn: "root" } из Interceptor   -  person William Lohan    schedule 08.02.2019
comment
@WilliamLohan Ага, я тоже это сделал, но простой переход на useClass вместо useFactory устранил мою проблему.   -  person Ryan C    schedule 08.02.2019
comment
Правильно, это будет работать, но вы предоставляете Interceptor как себя в корне и как HTTP_INTERCEPTORS, что теперь, когда я думаю об этом, неплохо, просто ненужно, если вам не нужно вводить Interceptor где-то еще (также, если это было так, вы можете нужно использовать useExisting, чтобы он оставался одноэлементным)   -  person William Lohan    schedule 08.02.2019
comment
@WilliamLohan Хороший звонок, точно не хочу прототип реализации перехватчика :)   -  person Ryan C    schedule 08.02.2019
comment
@AaronUllal Что ты имеешь в виду? Корень providedIn просто означает, что вам не нужно импортировать его в зависимости модулей вашего приложения, и делает его доступным глобально. Не уверен, о чем ваш вопрос.   -  person Ryan C    schedule 08.05.2019
comment
@RyanC, да ладно, я забыл декоратор @Injectable () ...   -  person Aaron Ullal    schedule 08.05.2019


Ответы (1)


У меня была такая же проблема, и я решил, переключившись с useFactory на useClass в объявлении модуля:

Это был рабочий код Angular 7.1

{
    provide: HTTP_INTERCEPTORS,
    useFactory(router: Router, authenticationStorageService: AuthenticationStorageService) {
        // a lot of stuff here

        return new AuthInterceptor(router, authenticationStorageService);
    },
    multi: true,
    deps: [Router, AuthenticationStorageService]
},

который не работает в Angular 8. Выброс фабрики решает проблему:

{
    provide: HTTP_INTERCEPTORS,
    useClass: AuthInterceptor,
    multi: true,
    deps: [Router, AuthenticationStorageService]
},

Однако мне пришлось сместить фабричную логику в конструкторе класса. Заводской подход был более гибким, так как вы могли время от времени изменять возвращаемый экземпляр, но, вероятно, в стандартном случае это было бы излишним.

person Yennefer    schedule 09.02.2020