使用 Angular Ivy 抽象 @Injectable 不起作用

Posted

技术标签:

【中文标题】使用 Angular Ivy 抽象 @Injectable 不起作用【英文标题】:Abstracting @Injectable with Angular Ivy does not work 【发布时间】:2020-04-27 12:06:51 【问题描述】:

更新

为这个问题打开了问题https://github.com/angular/angular/issues/34721

简介

在 Angular 中使用装饰器 @Injectable 提供服务。

@Injectable() // -> works
export class MyService 
  constructor() 

抽象@Injectable

在 Ivy 之前,可以为 @Injectable 构建抽象(例如,用于动态配置提供程序、增强服务类)。

以下 sn-p 显示了如何包装 @Injectable 的示例。

function InjectableEnhanced() 
  return <T extends new (...args: any[]) => InstanceType<T>>(target: T) => 
    Injectable( providedIn: "root" )(target);
  ;

在启用 Ivy 时,使用装饰器 InjectableEnhanced(见上文)不起作用。 以下代码被截断会导致运行时错误。

@InjectableEnhanced() // -> does not work
export class MyService 
  constructor() 

运行时错误

使用 @InjectableEnhanced 和 angular/cli 编译服务有效,但浏览器中显示以下错误。对应的项目可以在https://github.com/GregOnNet/ng-9-inject.git找到。

也许,Angular 编译器做了一些代码转换,但不能再在其他装饰器中解析 @Injectable。 查看 Angular 存储库,可以在 injectable.ts 中找到对 JIT 编译器的引用(请参阅:https://github.com/angular/angular/blob/master/packages/core/src/di/injectable.ts#L14)。

问题

还有没有办法抽象出@Injectable?

复制库

https://github.com/GregOnNet/ng-9-inject.git

【问题讨论】:

【参考方案1】:

可以使用一些 Angular 的内部 API 创建自定义提供程序:

import  ɵɵdefineInjectable, ɵɵinject  from "@angular/core";

export function InjectableEnhanced() 
  return <T extends new (...args: any[]) => InstanceType<T>>(target: T) => 
    (target as any).ɵfac = function() 
      throw new Error("cannot create directly");
    ;

    (target as any).ɵprov = ɵɵdefineInjectable(
      token: target,
      providedIn: "root",
      factory() 
        // ɵɵinject can be used to get dependency being already registered
        const dependency = ɵɵinject(Dependency); 
        return new target(dependency);
      
    );
    return target;
  ;

可以在https://github.com/GregOnNet/ng-9-inject找到工作示例

【讨论】:

似乎在 Angular 12 中 ɵfac 只是一个吸气剂,不可能为它分配一个函数。任何想法如何克服这个问题? 嗨,我最近更新了我的库,我的库仍然可以使用。但我会仔细检查。 好的,我也验证过了。所以它与 AOT=true 一起工作。但是对于使用 Ivy Angular 的非 aot 构建,添加了您无法修改的 ɵfacɵprov getter。对于这种情况,创建普通的 Injectable 实例仍然像以前一样工作。我只想知道是否有办法知道构建是否在运行时... 好的,如果其他人有同样的问题,这里是如何解决它。而不是(target as any).ɵprov = ɵɵdefineInjectable 使用Object.defineProperty(target, 'ɵprov', .....)【参考方案2】:

装饰器按预期附加到构造函数,但是当 AppComponent 创建时,注入器尝试解析提供程序并崩溃。

我认为错误消息只是组件构造失败时的一般错误,但是当 Angular 尝试为 AppComponent 构造函数获取可注入时发生错误。

如果您记录服务的构造函数,您可以看到提供者元数据已附加:

@InjectableEnhanced()
export class MyService 
  constructor() 
  



console.log((MyService as object).prototype.constructor.hasOwnProperty('ɵprov'));
// prints "true"

当我尝试检查该属性时,它会触发错误:

try 
  console.log((MyService as object).prototype.constructor.ɵprov);
 catch (err) 
  console.log(err); // prints the same error message

我认为该属性是一个 getter 属性,它解析为提供者的实例,这就是崩溃的原因。

我能找到的关于 Angular 的最接近的问题是这个,但它仍然是开放的:

https://github.com/angular/angular/issues/31495

所以我觉得 Ivy 编译器可能正在搜索 @Injectable() 的源代码并构建预期提供者的列表,但它没有看到这个新的装饰器,因此 MyService 被排除在列表之外。稍后在运行时,装饰器的元数据就在那里,但注入器不知道它的用途并崩溃。

我试图找到一些文档,您可以在其中使用 Ivy 编译器注册一个新的装饰器,但没能做到,我不知道这样的事情是否存在。

仅供参考:我在我的其他一个项目中做同样的事情,所以我认为会有很多人受此影响。

【讨论】:

谢谢您的回答。 ? 它真的帮助我更深入地挖掘。我读了github.com/angular/angular/issues/31495,也认为这可能是相关的。不过,我将打开另一个问题,因为 IoC 也在这里受到影响。 @GregorWoiwode 你能用你打开的问题的链接更新你的问题,以便其他人可以找到它。谢谢。

以上是关于使用 Angular Ivy 抽象 @Injectable 不起作用的主要内容,如果未能解决你的问题,请参考以下文章

Angular 8 with Ivy 如何验证它的使用

在 Angular 10 中启用 Ivy 的问题

在没有 Ivy 的 Angular 10 库中导入 RouterModule

Angular Ivy 兼容性问题

Angular CLI 9 (Ivy) 使用本地库 (mono-repo) 构建应用程序失败

使用 Cypress 触发 Angular Ivy 变化检测