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

4

更新

介绍

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

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

抽象化 @Injectable

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

以下代码片段展示了如何包装@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() {}
}

运行时错误

使用 angular/cli 编译服务时,使用 @InjectableEnhanced 是可以工作的,但是浏览器会显示以下错误。相应的项目可以在 https://github.com/GregOnNet/ng-9-inject.git 找到。

enter image description here

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

问题

是否仍有一种方法来抽象 @Injectable?

复现存储库

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

2个回答

4

可以使用 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 只是一个 getter,无法将函数分配给它。有什么想法可以克服这个问题吗? - Kirill Metrik
嗨,我最近更新了我的库,它仍然可以工作。但我会再次检查确认一下。 - Gregor Woiwode
好的,我也验证过了。所以它在 AOT=true 的情况下可以工作。但是对于使用 Ivy 的非-AOT构建,Angular 会添加 ɵfacɵprov getter,这些getter不可修改。针对这种情况,创建普通的 Injectable实例仍然像以前一样工作。我只想知道是否有一种方法在运行时知道构建是否为AOT... - Kirill Metrik
1
好的,如果其他人遇到相同的问题,这里是解决方法。不要使用(target as any).ɵprov = ɵɵdefineInjectable,而是使用Object.defineProperty(target, 'ɵprov', {.....}) - Kirill Metrik

1
装饰器按预期附加到构造函数上,但当创建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
}

我认为该属性是一个获取器属性,解析为提供程序的实例,这就是导致崩溃的原因。
我在Angular中找到的最接近的问题是这个,但它仍然没有解决:

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

所以我觉得Ivy编译器可能会搜索源代码中的@Injectable(),并建立一个预期提供程序列表,但它没有看到这个新的装饰器,所以MyService被排除在列表之外。稍后在运行时,装饰器的元数据是存在的,但注入器不知道它是什么,并崩溃了。
我试图找到一些文档,可以在Ivy编译器中注册一个新的装饰器,但没有找到,我不知道是否存在这样的东西。
FYI:我在我的另一个项目中做了完全相同的事情,所以我认为会有很多人受到影响。

谢谢你的回答。它真的帮助我深入了解。我阅读了 https://github.com/angular/angular/issues/31495 ,也认为这可能有关。尽管如此,我会打开另一个问题,因为 IoC 在这里也受到影响。 - Gregor Woiwode
@GregorWoiwode,你能否更新你的问题,并附上你打开的问题链接,以便其他人可以找到它。谢谢。 - Reactgular

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接