简体   繁体   English

Angular 使用 forRoot 将具有依赖关系的服务传递给模块

[英]Angular pass service with dependencies to module using forRoot

I have a service for authentication based on JWT.我有一个基于 JWT 的身份验证服务。 To reuse this service in all my projects i created a library which should be shipped with npm.为了在我的所有项目中重用这个服务,我创建了一个库,它应该随 npm 一起提供。

For this service to work i need some API-Calls.为了使这项服务正常工作,我需要一些 API 调用。 In every project the API could look completely different so i don't want to provide this functionality inside my library instead inject another service which handles my API-Calls.在每个项目中,API 看起来可能完全不同,所以我不想在我的库中提供这个功能,而是注入另一个服务来处理我的 API 调用。

My idea was to create a module which contains my service and provide an interface to describe the service for API-Calls and inject it forRoot.我的想法是创建一个包含我的服务的模块,并提供一个接口来描述 API-Calls 的服务并将其注入 forRoot。 The Problem is that my api service has some dependencies like HttpClient and i cannot simple instantiate it in my app.module.问题是我的 api 服务有一些依赖项,比如 HttpClient,我不能在我的 app.module 中简单地实例化它。

My library looks like:我的图书馆看起来像:

auth.module.ts auth.module.ts

import { NgModule, ModuleWithProviders, InjectionToken } from '@angular/core';
import { AuthService } from '../services/auth.service';
import { AuthAPI } from '../models/authAPI';
import { AuthapiConfigService } from '../services/authapi-config.service';


@NgModule()
export class AuthModule {

  static forRoot(apiService: AuthAPI): ModuleWithProviders {
    return {
      ngModule: AuthModule,
      providers: [
        AuthService,
        {
          provide: AuthapiConfigService,
          useValue: apiService
        }
      ]
    };
  }
}

auth-api.interface.ts auth-api.interface.ts

import { Observable } from 'rxjs';

export interface AuthAPI {
  reqLogin(): Observable<{ access_token: string; }>;
  reqRegister(): Observable<{ access_token: string; }>;
}

auth-api-config.service.ts auth-api-config.service.ts

import { InjectionToken } from '@angular/core';
import { AuthAPI } from '../models/authAPI';
/**
 * This is not a real service, but it looks like it from the outside.
 * It's just an InjectionTToken used to import the config object, provided from the outside
 */
export const AuthapiConfigService = new InjectionToken<AuthAPI>('API-Service');

auth.service.ts auth.service.ts

 constructor(@Inject(AuthapiConfigService) private apiService) {}

How i am trying to implement it:我如何尝试实现它:

auth-rest-service.ts auth-rest-service.ts

import { Injectable } from '@angular/core';
import { AuthAPI } from 'projects/library-project/src/lib/auth/models/authAPI';
import { Observable } from 'rxjs';
import { HttpClient } from '@angular/common/http';

@Injectable({
  providedIn: 'root'
})
export class AuthRestService implements AuthAPI  {

  constructor(private http: HttpClient) {}

  reqLogin(): Observable<{ access_token: string; }> {
    return this.http.post<{access_token: string}>(`/login`, 'test');
  }

  reqRegister(): Observable<{ access_token: string; }> {
    return this.http.post<{access_token: string}>(`/login`, 'test');
  }

}

app.module.ts app.module.ts

import { AuthRestService } from './components/auth-service/auth-rest.service';


@NgModule({
  declarations: [
   ...
  ],
  imports: [
    ...
    AuthModule.forRoot(AuthRestService),
    ...
  ],
  providers: [AuthModule],
  bootstrap: [AppComponent]
})
export class AppModule { }

I can't create an instance of AuthRestService because of the dependencies this service has (HttpClient).由于此服务具有的依赖项(HttpClient),我无法创建 AuthRestService 的实例。 Is there any method to tell angular to provide me this service.有什么方法可以告诉 angular 为我提供这项服务。

This is possible with usage of angular's Injector .这可以通过使用 angular 的Injector来实现。

import { Injector, ModuleWithProviders, NgModule, Optional, Provider, SkipSelf } from '@angular/core';
import { isFunction } from 'lodash';

export function resolveService(cfg: SharedConfig, inj: Injector): IncompleteService {
  const provider = cfg?.service;
  // if service is an angular provider, use Injector, otherwise return service instance as simple value
  const service = isFunction(service) ? inj.get(provider) : provider;
  return service;
}

/**
 * Service to be implemented from outside the module.
 */
@Injectable()
export abstract class IncompleteService {
  abstract strategyMethod();
}

// Optional: A config object is optional of course, but usually it fits the needs.
export interface SharedConfig {
  service: IncompleteService | Type<IncompleteService> | InjectionToken<IncompleteService>;
  // other config properties...
}

/*
 * Optional: If a Config interface is used, one might resolve the config itself 
 * using other dependencies (e.g. load JSON via HTTPClient). Hence an InjectionToken 
 * is necessary.
 */
export const SHARED_CONFIG = new InjectionToken<SharedConfig>('shared-config');

// Optional: If SharedConfig is resolved with dependencies, it must be provided itself.  
export type ModuleConfigProvider = ValueProvider | ClassProvider | ExistingProvider | FactoryProvider;

/**
 * One can provide the config as is, i.e. "{ service: MyService }" or resolved by 
 * injection, i.e.
 * { provide: SHARED_CONFIG: useFactory: myConfigFactory, deps: [DependentService1, DependentService2] }
 */
@NgModule({
  declarations: [],
  imports: []
})
export class SharedModule {
  static forRoot(config: SharedConfig | ModuleConfigProvider): ModuleWithProviders<SharedModule> {
    // dynamic (config is Provider) or simple (config is SharedConfig)
    return {
      ngModule: SharedModule,
      providers: [
        (config as ModuleConfigProvider).provide ? (config as Provider) : { provide: SHARED_CONFIG, useValue: config },
        { provide: IncompleteService, useFactory: resolveService, deps: [SHARED_CONFIG, Injector] },
        // ... provide additional things
      ],
    };
}


/**
 * In general not really useful, because usually an instance of IncompleteService
 * need other dependencies itself. Hence you cannot provide this instance without
 * creating it properly. But for the sake of completeness, it should work as well.
 */
@NgModule({
  declarations: [],
  imports: []
})
export class MostSimpleSharedModule {
  static forRoot(service: IncompleteService): ModuleWithProviders<SharedModule> {
    // dynamic (config is Provider) or simple (config is SharedConfig)
    return {
      ngModule: SharedModule,
      providers: [
        { provide: IncompleteService, useValue: service },
        // ... provide additional things
      ],
    };
}

EDIT编辑

If you really need an interface iso.如果你真的需要一个接口iso。 an (injectable) abstract class IncompleteService , you just need to define another InjectionToken<IncompleteServiceInterface> and provide this token explicitly.一个(可注入的)抽象 class IncompleteService ,您只需要定义另一个InjectionToken<IncompleteServiceInterface>并明确提供此令牌。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM