Search code examples
typescriptloopbackjsloopback4

Loopback 4 - Not able to get metadata from array of classes injected with @inject.tag()


Have a couple of classes registered as services:

@processor('some_value')
export class MyService {}

@processor('another_value')
export class AnotherService {}

This services are decorated with the processor() decorator, defined like this:

const PROCESSOR_CLASS_META = MetadataAccessor.create<
    string,
    ClassDecorator
  >('processor-decorator-metadata');

export function processor(value): ClassDecorator {
  return (
    target: Function & {value?: string},
  ) => {
    bind({tags: 'processor'})(target);

    const decorator = ClassDecoratorFactory.createDecorator(
      PROCESSOR_CLASS_META,
      value,
      {decoratorName: '@processor'},
    );
    decorator(target);
  };
}

The decorator do two things - tags the class as 'processor' and sets a string type metadata.

Now I have a third service. In this service I inject all the services tagged 'processor' like this:

export class consumerService {
  constructor(
    @inject.tag('processor')
    private readonly processorClasses: Constructor<Function>,
  )
}

When I try to get the metadata of the injected classes I get undefined:

export class consumerService {
  constructor(
    @inject.tag('processor')
    private readonly processorClasses: Constructor<Function>,
  )
  
  public getMetaData() {
    return this.processorClasses.map((processorClass) => {
      // Returns undefined
      return MetadataInspector.getClassMetadata(
        PROCESSOR_CLASS_META,
        processorClass,
      );
    })
  }
}

The undefined comes only when I inject an array of bindings with @inject.tag() decorator. When I inject only one of the classes I get the expected result:

export class consumerService {
  constructor(
    @inject('services.MyService')
    private readonly myService: MyService,
  )
  
  // Returns 'some_value' as expected.
  public getMetaData() {
      return MetadataInspector.getClassMetadata(
        PROCESSOR_CLASS_META,
        this.myService,
      );
  }
}

Solution

  • The decorator @inject.tag() is injecting service instances, not classes. You can access the class (constructor function) via .constructor property.

    Here is a fixed version of your ConsumerService implementation:

    class ConsumerService {
      constructor(
        @inject.tag('processor')
        private readonly processors: object[],
      ) {
        console.log('processors', this.processors);
        for (const proc of this.processors) {
          console.log(
            it.constructor.name,
            MetadataInspector.getClassMetadata(
              PROCESSOR_CLASS_META,
              proc.constructor,
            ),
          );
        }
      }
    }
    

    When I run it using your example processors, I get the following output:

    processors [ MyService {}, AnotherService {} ]
    MyService some_value
    AnotherService another_value