A tutorial to use @loopback/metadata module to create new decoratorsNote: This page was generated from the loopback-next/packages/metadata/README.md.

@loopback/metadata

This module contains utilities to help developers implementTypeScript decorators,define/merge metadata, and inspect metadata.

  • Reflector: Wrapper ofreflect-metadata
  • Decorator factories: A set of factories for class/method/property/parameterdecorators to apply metadata to a given class and its static or instancemembers.
  • MetadataInspector: High level APIs to inspect a class and/or its members toget metadata applied by decorators.

Basic Use

To create a class decorator

  1. import {ClassDecoratorFactory} from '@loopback/metadata';
  2. export interface MyClassMetadata {
  3. name: string;
  4. description?: string;
  5. }
  6. function myClassDecorator(spec: MyClassMetadata): ClassDecorator {
  7. return ClassDecoratorFactory.createDecorator<MyClassMetadata>(
  8. 'metadata-key-for-my-class-decorator',
  9. spec,
  10. {decoratorName: '@myClassDecorator'},
  11. );
  12. }

Alternatively, we can instantiate the factory and create a decorator:

  1. function myClassDecorator(spec: MyClassMetadata): ClassDecorator {
  2. const factory = new ClassDecoratorFactory<MyClassMetadata>(
  3. 'metadata-key-for-my-class-decorator',
  4. spec,
  5. );
  6. return factory.create();
  7. }

Now we can use @myClassDecorator to add metadata to a class as follows:

  1. @myClassDecorator({name: 'my-controller'})
  2. class MyController {}

To create a method decorator

  1. import {MethodDecoratorFactory} from '@loopback/metadata';
  2. export interface MyMethodMetadata {
  3. name: string;
  4. description?: string;
  5. }
  6. function myMethodDecorator(spec: MyMethodMetadata): MethodDecorator {
  7. return MethodDecoratorFactory.createDecorator<MyMethodMetadata>(
  8. 'metadata-key-for-my-method-decorator',
  9. spec,
  10. );
  11. }

Now we can use @myMethodDecorator to add metadata to a method as follows:

  1. class MyController {
  2. @myMethodDecorator({name: 'my-method'})
  3. myMethod(x: string): string {
  4. return 'Hello, ' + x;
  5. }
  6. @myMethodDecorator({name: 'another-method'})
  7. anotherMethod() {}
  8. @myMethodDecorator({name: 'my-static-method'})
  9. static myStaticMethod() {}
  10. }

To create a property decorator

  1. import {PropertyDecoratorFactory} from '@loopback/metadata';
  2. export interface MyPropertyMetadata {
  3. name: string;
  4. description?: string;
  5. }
  6. function myPropertyDecorator(spec: MyPropertyMetadata): PropertyDecorator {
  7. return PropertyDecoratorFactory.createDecorator<MyPropertyMetadata>(
  8. 'metadata-key-for-my-property-decorator',
  9. spec,
  10. );
  11. }

Now we can use @myPropertyDecorator to add metadata to a property as follows:

  1. class MyController {
  2. @myPropertyDecorator({name: 'my-property'})
  3. myProperty: string;
  4. @myPropertyDecorator({name: 'another-property'})
  5. anotherProperty: boolean;
  6. @myPropertyDecorator({name: 'my-static-property'})
  7. static myStaticProperty: string;
  8. }

To create a parameter decorator

  1. import {ParameterDecoratorFactory} from '@loopback/metadata';
  2. export interface MyParameterMetadata {
  3. name: string;
  4. description?: string;
  5. }
  6. function myParameterDecorator(spec: MyParameterMetadata): ParameterDecorator {
  7. return ParameterDecoratorFactory.createDecorator<MyParameterMetadata>(
  8. 'metadata-key-for-my-parameter-decorator',
  9. spec,
  10. );
  11. }

Now we can use @myParameterDecorator to add metadata to a parameter asfollows:

  1. class MyController {
  2. constructor(
  3. @myParameterDecorator({name: 'logging-prefix'}) public prefix: string,
  4. @myParameterDecorator({name: 'logging-level'}) public level: number,
  5. ) {}
  6. myMethod(
  7. @myParameterDecorator({name: 'x'}) x: number,
  8. @myParameterDecorator({name: 'y'}) y: number,
  9. ) {}
  10. static myStaticMethod(
  11. @myParameterDecorator({name: 'a'}) a: string,
  12. @myParameterDecorator({name: 'b'}) b: string,
  13. ) {}
  14. }

To create method decorator for parameters

  1. import {MethodParameterDecoratorFactory} from '@loopback/metadata';
  2. export interface MyParameterMetadata {
  3. name: string;
  4. description?: string;
  5. }
  6. function myMethodParameterDecorator(
  7. spec: MyParameterMetadata,
  8. ): MethodDecorator {
  9. return MethodParameterDecoratorFactory.createDecorator<MyParameterMetadata>(
  10. 'metadata-key-for-my-method-parameter-decorator',
  11. spec,
  12. );
  13. }

Now we can use @myMethodParameterDecorator to add metadata to a parameter asfollows:

  1. class MyController {
  2. @myMethodParameterDecorator({name: 'x'})
  3. @myMethodParameterDecorator({name: 'y'})
  4. myMethod(x: number, y: number) {}
  5. }

WARNING: Using method decorators to provide metadata for parameters isstrongly discouraged for a few reasons:

  • Method decorators cannot be applied to a constructor
  • Method decorators depends on the positions to match parametersWe recommend that ParameterDecorator be used instead.

Decorator options

An object of type DecoratorOptions can be passed in to create decoratorfunctions. There are two flags for the options:

  • allowInheritance: Controls if inherited metadata will be honored. Default totrue.
  • cloneInputSpec: Controls if the value of spec argument will be cloned.Sometimes we use shared spec for the decoration, but the decorator functionmight need to mutate the object. Cloning the input spec makes it safe to usethe same spec (template) to decorate different members. Default to true.
  • decoratorName: Name for the decorator such as @inject for error anddebugging messages.

Customize inheritance of metadata

By default, the decorator factories allow inheritance with the following rules:

  • If the metadata is an object, we merge the spec argument from thedecorator function into the inherited value from base classes. For metadataof array and other primitive types, the spec argument is used if provided.

    • We can override inherit method of the decorator factory to customize howto resolve spec against the inherited metadata. For example:
  1. protected inherit(inheritedMetadata: T | undefined | null): T {
  2. // Ignore the inherited metadata
  3. return this.spec;
  4. }
  • Method/property/parameter level metadata is applied to the class or itsprototype as a map keyed method/property names. We think this approach isbetter than keeping metadata at method/property level as it’s not easy toinspect a class to find static/instance methods and properties withdecorations. The metadata for a class is illustrated below:

    • MyClass (the constructor function itself)
  1. {
  2. // Class level metadata
  3. 'my-class-decorator-key': MyClassMetadata,
  4. // Static method (including the constructor) parameter metadata
  5. 'my-static-parameter-decorator-key': {
  6. '': [MyConstructorParameterMetadata], // Constructor parameter metadata
  7. 'myStaticMethod1': [MyStaticMethodParameterMetadata],
  8. 'myStaticMethod2': [MyStaticMethodParameterMetadata],
  9. },
  10. // Static method metadata
  11. 'my-static-method-decorator-key': {
  12. 'myStaticMethod1': MyStaticMethodMetadata,
  13. 'myStaticMethod2': MyStaticMethodMetadata,
  14. },
  15. // Static property metadata
  16. 'my-static-property-decorator-key': {
  17. 'myStaticMethod1': MyStaticPropertyMetadata,
  18. 'myStaticMethod1': MyStaticPropertyMetadata,
  19. }
  20. }
  • MyClass.prototype
  1. {
  2. // Instance method parameter metadata
  3. 'my-instance-parameter-decorator-key': {
  4. 'myMethod1': [MyMethodParameterMetadata],
  5. 'myMethod2': [MyMethodParameterMetadata],
  6. },
  7. // Instance method metadata
  8. 'my-instance-method-decorator-key': {
  9. 'myMethod1': MyMethodMetadata,
  10. 'myMethod2': MyMethodMetadata,
  11. },
  12. // Instance property metadata
  13. 'my-instance-property-decorator-key': {
  14. 'myProperty1': MyPropertyMetadata,
  15. 'myProperty2': MyPropertyMetadata,
  16. }
  17. }

The following methods in DecoratorFactory allow subclasses to customize how tomerge the spec with existing metadata for a class, methods, properties, andmethod parameters. Please note M is a map for methods/properties/parameters.

  1. protected mergeWithInherited(
  2. inheritedMetadata: M,
  3. target: Object,
  4. member?: string,
  5. descriptorOrIndex?: TypedPropertyDescriptor<any> | number,
  6. ): M {
  7. // ...
  8. }
  9. protected mergeWithOwn(
  10. ownMetadata: M,
  11. target: Object,
  12. member?: string,
  13. descriptorOrIndex?: TypedPropertyDescriptor<any> | number,
  14. ): M {
  15. // ...
  16. }
  • The default implementation throws errors if the same decorator function isapplied to a given target member (class/method/property/parameter) more thanonce. For example, the following usage will report an error at runtime.
  1. @myClassDecorator({name: 'my-controller'})
  2. @myClassDecorator({name: 'your-controller'})
  3. class MyController {}

Inspect metadata

MetadataInspector provides API to inspect metadata from a class and itsmembers.

Inspect metadata of a class

  1. import {MetadataInspector} from '@loopback/metadata';
  2. const meta = MetadataInspector.getClassMetadata(
  3. 'my-class-decorator-key',
  4. MyController,
  5. );

Inspect own metadata of a class

  1. import {MetadataInspector} from '@loopback/metadata';
  2. const meta = MetadataInspector.getClassMetadata<MyClassMetaData>(
  3. 'my-class-decorator-key',
  4. MyController,
  5. {
  6. ownMetadataOnly: true,
  7. },
  8. );

Inspect metadata of a method

  1. import {MetadataInspector} from '@loopback/metadata';
  2. const allMethods = MetadataInspector.getAllMethodMetaData<MyMethodMetadata>(
  3. 'my-method-decorator-key',
  4. MyController.prototype, // Use MyController for static methods
  5. );
  6. const myMethod = MetadataInspector.getMethodMetaData<MyMethodMetadata>(
  7. 'my-method-decorator-key',
  8. MyController.prototype, // Use MyController for static methods
  9. 'myMethod',
  10. );

Inspect metadata of a property

  1. import {MetadataInspector} from '@loopback/metadata';
  2. const allProps = MetadataInspector.getAllPropertyMetaData<MyPropertyMetadata>(
  3. 'my-property-decorator-key',
  4. MyController.prototype, // Use MyController for static properties
  5. );
  6. const myProp = MetadataInspector.getMethodMetaData<MyMethodMetadata>(
  7. 'my-property-decorator-key',
  8. MyController.prototype, // Use MyController for static properties
  9. 'myProp',
  10. );

Inspect metadata of method parameters

  1. import {MetadataInspector} from '@loopback/metadata';
  2. const allParamsForMyMethod = MetadataInspector.getAllParameterMetaData<
  3. MyParameterMetadata
  4. >(
  5. 'my-parameter-decorator-key',
  6. MyController.prototype, // Use MyController for static methods,
  7. 'myMethod',
  8. );
  9. const firstParamForMyMethod = MetadataInspector.getMyParameterMetaData<
  10. MyParameterMetadata
  11. >(
  12. 'my-parameter-decorator-key',
  13. MyController.prototype, // Use MyController for static methods
  14. 'myMethod',
  15. 0, // parameter index
  16. );
  17. const allParamsForConstructor = MetadataInspector.getAllParameterMetaData<
  18. MyParameterMetadata
  19. >('my-parameter-decorator-key', MyController, '');

Use strong-typed metadata access key

You can use MetadataAccessor to provide type checks for metadata access viakeys. For example,

  1. const CLASS_KEY = MetadataAccessor.create<MyClassMetadata, ClassDecorator>(
  2. 'my-class-decorator-key',
  3. );
  4. // Create a class decorator with the key
  5. const myClassDecorator = ClassDecoratorFactory.createDecorator(CLASS_KEY);
  6. // Inspect a class with the key
  7. const myClassMeta = MetadataInspector.getClassMetaData(CLASS_KEY, MyController);

Please note MetadataKey can be an instance of MetadataAccessor or a string.

Inspect design-time metadata of properties/methods

  1. import {MetadataInspector} from '@loopback/metadata';
  2. const myPropType = MetadataInspector.getDesignTypeForProperty(
  3. MyController.prototype,
  4. 'myProp',
  5. );
  6. const myConstructor = MetadataInspector.getDesignTypeForMethod(
  7. MyController,
  8. '',
  9. );
  10. const myMethod = MetadataInspector.getDesignTypeForMethod(
  11. MyController.prototype, // Use MyController for static methods
  12. 'myMethod',
  13. );

Installation

  1. npm install --save @loopback/metadata

Contributions

Tests

Run npm test from the root folder.

Contributors

Seeall contributors.

License

MIT