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

Abstracting @Injectable with Angular Ivy does not work

更新

简介

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

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

抽象@Injectable

在 Ivy 之前,可以为 @Injectable 构建抽象(例如,动态配置提供者,增强服务 class)。

以下代码段显示了如何包装 @Injectable 的示例。

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

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

@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

装饰器正按预期附加到构造函数,但在创建 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 被排除在外从列表中。稍后在 运行-time 装饰器的元数据在那里,但注入器不知道它是做什么用的并且崩溃了。

我试图找到可以在 Ivy 编译器中注册新装饰器的文档,但无法做到,我不知道是否存在这样的东西。

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

可以使用 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

找到工作示例