将变量传递给注入的服务以用作装饰器参数

Posted

技术标签:

【中文标题】将变量传递给注入的服务以用作装饰器参数【英文标题】:Passing variables to injected services to use as decorator argument 【发布时间】:2021-07-12 17:34:48 【问题描述】:

我有一个 Angular 项目,它使用的服务经常通过典型的 create/read/update/delete/read-list-of-all-entries 重复出现。我也经常想在收到数据后以类似的方式处理收到的数据。所以我编写了使用apply 的装饰器,它以传递给它们的参数确定的方式“转换”数据(参数始终是对象类)。

正常使用时是这样的:

//rule.service.ts
@Injectable(
  providedIn: 'root'
)
export class RuleService 

  rulesUrl: string = `$Constants.wikiApiUrl/rule/`;

  constructor(private http: HttpClient) 

  @TransformObservable(RuleObject)
  getRule(pk: number): Observable<Rule>
    return this.http.get<Rule>(`$this.rulesUrl/$pk`);
  


这种情况经常发生,所以我想编写一个可以扩展的父服务:

//generic-object.service.ts

@Injectable(
  providedIn: 'root'
)
export abstract class GenericObjectService
  baseUrl: string;

  constructor(
    private http: HttpClient,
    public objectClass: any,
  ) 

  @TransformObservable(this.objectClass) //This line won't work
  read(pk: number): Observable<any>
    return this.http.get(`$this.baseUrl/$pk`);
  

问题是我不能使用“this”作为装饰器参数,它在解析 IIRC 时不存在。但是有没有办法可以以某种方式将装饰器参数传递给我的注入服务,以便装饰器可以使用它?

在下面还可以找到“TransformObservable”装饰器:

export function TransformObservable(modelClass: any)
    /**Decorator to apply transformObservableContent */
    return function(target: any, propertyKey: string, descriptor: PropertyDescriptor)

        const originalMethod = descriptor.value;
        descriptor.value = function()
            const observable = originalMethod.apply(this, arguments);
            return transformObservableContent(observable, modelClass);
        
        return descriptor;
    

【问题讨论】:

【参考方案1】:

this 关键字在函数范围之外将不可用。但是,您可以将所需的对象定义为同一类中的字段/属性,并且在装饰器注释中,您可以只传递装饰器函数将查找的字符串,或传递实际的对象引用。

在装饰器中,检查传递给注解的参数:

if(typeof modelClass === 'string')
  // lookup a property in decorated class definition
  modelClass = target[modelClass]; // or, this[modelClass]
else 
  // assume modelClass is the needed object

这将允许您使用:

@TransformObservable('propertyName');
// or
@TransformObservable(someObjectReference);

targetthis 之间的区别:

传递给装饰器函数的target 是对象,它定义了使用装饰器的类。它将具有类中定义的所有属性和方法,并可用于检索它们,如上所示。但是target 不是您通常在类方法中使用的运行时this。它唯一的替换方法 (descriptor.value) 将在其中包含 this,就像原始方法一样。

因此,如果您真的需要this 而不仅仅是修饰类定义,您必须将所有代码移入替换函数descriptor.value。在那里您可以访问this 以及传递给装饰器和装饰器工厂的所有参数。

【讨论】:

哦,所以 target = 写入装饰器的对象。 “this” = ...我实际上并不太确定装饰器内部的哪个上下文“this”在那时代表。启动调用的子服务? 会不会是你编辑到现在的方式不对,我回复的时候this还写成target(供以后的读者)。 thisTransformObservable 中是未定义的,target 确实包含一个对象,该对象似乎是在其中写入装饰器的对象,也就是GenericObjectService。但是,出于某种原因,GenericObjectService 的属性不会显示在该对象中,只有函数会显示。 @PhilippDoerner 已更新答案。 啊,我终于明白modelClass的检查必须发生在我放入descriptor.value的匿名函数中。对于错误的读者,我会将您的建议作为下面的另一种解决方案付诸实践,但您的建议仍将被接受。 @PhilippDoerner 是的。您必须将代码移动到替换函数中并在那里进行检查,您可以在那里使用this【参考方案2】:

基于@S.D.的回答我终于明白了他一直想说的:你可以在你放入descriptor.value的匿名函数中访问this。因此,为了让我的装饰器能够正确执行此操作,我必须使用this 从该匿名函数内部访问modelClass,我很高兴。以下是 S.D. 的解决方案。看来要付诸实践了:

export function TransformObservable(modelClass: any)
    /**Decorator to apply transformObservableContent */
    return function(target: any, propertyKey: string, descriptor: PropertyDescriptor)

        const originalMethod = descriptor.value;
        descriptor.value = function()
            const observable = originalMethod.apply(this, arguments);

            //If the decorator argument was not a class but instead a property
            // name of a property on the object that uses the decorator that 
            // contains the class, update the modelClass variable
            const decoratorArgumentIsProperty = ["String", "string"].includes(typeof modelClass);
            if(decoratorArgumentIsTargetProperty)
                modelClass = this[modelClass];
            

            return transformObservableContent(observable, modelClass);
        
        return descriptor;
    

【讨论】:

以上是关于将变量传递给注入的服务以用作装饰器参数的主要内容,如果未能解决你的问题,请参考以下文章

如何将装饰器中的变量传递给装饰函数中的函数参数?

将实例变量传递给装饰器

角 |将服务注入装饰器

Python 将 self 传递给装饰器

python3 如何给装饰器传递参数

从 Sitemesh 装饰器传递一个变量