【问题标题】:Angular 6 service with interface带有接口的Angular 6服务
【发布时间】:2018-07-04 13:27:27
【问题描述】:

我正在使用 Angular (6.0.7) 构建一个应用程序,并且我正在尝试使用新创建一个服务:

@Injectable({
  providedIn: 'root'
})

但是如何使用接口键入注入?


问题

我有 2 个服务,Authentication.service 和 SessionStorage.service。我想将会话存储注入身份验证服务。这可以通过以下方式完成:

constructor(private sessionStorage: SessionStorage) {
}

那里没问题。但出于面向对象的目的,我希望在此服务上方有一个interface(这样我就可以将两个本地存储服务实现为会话存储服务)。因此,我想用接口键入注入的类是合乎逻辑的,但这不能以 Angular 5 and lower does it 相同的方式完成。

那么如何使用我的界面将注入输入到这个全局服务中呢?


我试过了

Angular 服务类型描述了 InjectableProvider,但这与 InjectableProvider 的兄弟姐妹的任何参数都不匹配,因此这会产生编译器(和 tslint)错误。

@Injectable({
  providedIn: 'root'
}, {provide: IStorageService, useClass: SessionStorage})

【问题讨论】:

  • 我不明白这样做的目的。你想达到什么目的?
  • 依赖注入应该是这样,类使用接口并且不关心代码的实现。当然 Angular 的工作方式有点不同,需要在任何地方使用“useValue”键入注入,但它的目的是相同的。
  • 那你为什么不用export class MyService implements MyInterface {...}的接口呢?
  • 顺便说一句,useValue 需要一个值(类的实际实例),useClass 允许您使用类。所以试试useClass,也许useInterface(但这个我不确定)
  • @trichetriche,不幸的是,useClass 也不起作用,因为它与 InjectableProvider 的参数不匹配(更新答案)。关于'你为什么不使用(..)实现(..)',我不想这样做,因为那样我仍然必须在构造函数中设置确切的类型,如果可能的话我想避免这种情况

标签: angular typescript service dependency-injection angular6


【解决方案1】:

这可以使用InjectionToken 来完成,它是过时的OpaqueToken 的替代品

export const AuthenticationProvider = new InjectionToken(
  "AuthenticationProvider",
  { providedIn: "root", factory: () => new CognitoAuthenticationProvider() }
);

...

@Injectable()
export class CognitoAuthenticationProvider implements IAuthenticationProvider {

...

@Injectable({
  providedIn: "root"
})
export class AuthenticationService {
  constructor(
    @Inject(AuthenticationProvider)
    private authenticationProvider: IAuthenticationProvider,
    private http: HttpClient
  ) {}

【讨论】:

  • 我认为这是最好的选择,不幸的是它还需要一个其他变量,并且需要在令牌上定义 provideIn,我希望在服务上定义它。跨度>
  • @Mr.wiseguy providedIn 是 ng 6 中的新内容,建议用于一些 tree-shaking 优化目的。您仍然可以在模块本身中注册提供程序。
  • 如果我们想在 'CognitoAuthenticationProvider' 构造函数中注入一些东西呢?
  • 我关注界面IAuthenticationProvider名字。 Angular 风格指南说:Consider naming an interface without an I prefix.
  • @tsiro 还要记住 factory 参数是可选的,并且只为注入令牌提供默认值。您可能会遇到无法提供默认值的情况,在这种情况下,您根本不应该使用 factory 参数。如果您没有将注入器配置为在其他地方使用该令牌注入任何服务,这将使角度失败并出现错误。您可以在将服务添加到模块时设置提供程序以使用providers: [{ provide: YOUR_TOKEN, useClass: YourConcreteClass }]
【解决方案2】:

我使用类似下面的方法来解决这个问题

app.module.ts

providers: [
  { provide: AlmostInterface, useClass: environment.concrete }
  ...
]

AlmostInterface.ts

export abstract class AlmostInterface {
   abstract myMethod();
}

MyConcrete.ts

export class MyConcrete implements AlmostInterface {
   myMethod() { ... }; // implementation
}

export class MyConcreteAlternative implements AlmostInterface {
   myMethod() { ... }; // implementation
}

environment.ts

export const environment = {
  production: false,
  concrete: MyConcreteAlternative
};

environment.prod.ts

export const environment = {
  production: true,
  concrete: MyConcrete
};

【讨论】:

  • 这行得通,但问题是它使用旧的 Angular 5 语法来声明服务。我要求使用新的 Angular 6 语法的解决方案,因为此 (provideIn: 'root') 不是通过模块声明的,而是仅注入使用它的组件中(更适合摇树)。
【解决方案3】:

我认为您不能使用 typescript 接口进行依赖注入,因为 typescript 接口在运行时不存在(仅用于编译时的类型安全)。
我建议为它使用一个抽象类。

编辑: 看来您可以在@Injectable 的 first 参数中使用useClass,而不是像您的示例那样作为第二个参数。将其与@k0zakinio 的答案相结合,得到:

@Injectable({
  providedIn: 'root',
  useClass: environment.concrete,
  deps: []
})
export abstract class SessionStorage { }

您似乎还需要通过deps 或inject 声明您的依赖关系,查看此github issue。希望这次我的回答能对您有所帮助。

【讨论】:

  • 好吧,Angular(和 Typescript)确实提供了一种依赖注入的方法,请参阅@k0zakinio 的回答。所以这是可能的。但我想知道 Angular 6 服务方式是否也可以。
  • 对不起,我完全误解了你的问题。
【解决方案4】:

你可以使用类似 -

[{ provide: InterfaceName, useClass: ClassName}]

【讨论】:

  • 这与InjectableProvider定义的参数不匹配,所以很遗憾这不起作用
猜你喜欢
  • 2017-02-25
  • 1970-01-01
  • 2019-06-26
  • 2019-03-25
  • 2013-12-07
  • 1970-01-01
  • 2020-11-15
  • 2023-03-27
  • 2019-03-02
相关资源
最近更新 更多