reflect-metadata icon indicating copy to clipboard operation
reflect-metadata copied to clipboard

Decorated class inheritance issue

Open joesonw opened this issue 7 years ago • 10 comments

When multiple instances extends from the same base class that has decorated properties. Children decorators 'pollutes' parent metadata. They were also attached to parent prototype.

Example:

following decorators use reflect-metadata to 'define' and 'get' metadata.

class Base {
    @SomeDecorator
    prop
}
class A extends Base {
    @SomeDecorator
    propA
}
class B extends Base {
    @SomeDecorator
    propB
}

This will result class B has three metadata, prop, propA and propB

joesonw avatar Feb 28 '17 12:02 joesonw

What does SomeDecorator look like? If it's anything like the decorator mentioned in #53, check https://github.com/rbuckton/reflect-metadata/issues/53#issuecomment-274906502

rbuckton avatar Mar 02 '17 00:03 rbuckton

I am also seeing this. Although, I am using decorators on constructor parameters rather than as class properties. I imagine the code path is roughly the same though.

class AbstractComponent {
    constructor (@Inject('form') @Host() formController: IFormController) { ... }
}

class ComponentA extends AbstractComponent { ... } // no constructor

class ComponentB {
    constructor (@Inject('form') @Host() formController: IFormController,
                 @Inject('$http') $http: IHttpService) { ... }
}

Angular will actually throw an error here,

Error: [$compile:ctreq] Controller '$http', required by directive 'componentA', can't be found!

It looks like it's registering all metadata against the parent class and not the child classes. Using ng-metadata to wire everything up behind the scenes.

mtraynham avatar Jun 19 '17 19:06 mtraynham

@joesonw @rbuckton You can close this issue, it's not part of reflect metadata this was issue in angular it self. I misused api as well and it was bug in my code not in reflect metadata.

igorzg avatar Dec 04 '17 16:12 igorzg

@igorzg does angular change something with Reflect? Because I get this issue when not running it through angular (though angular is present...):

import 'reflect-metadata';

const formatMetadataKey = Symbol('format');
const formKey = 'formData';

function Field (target: any, key: string) {
  const formData = getFields(target) || [];
  formData.push(key);
  Reflect.metadata(formatMetadataKey, formData)(target, formKey);
}

export function getFields(target: any) {
  return Reflect.getMetadata(formatMetadataKey, target, formKey);
}

class A {
  @Field
  foo: string;

  @Field
  bar: string;
}

class B extends A {
  @Field
  biz: string;
}

class C {
  @Field
  baz: string;
}

console.log(getFields(new A())); // ['foo', 'bar', 'biz']
console.log(getFields(new B())); // ['foo', 'bar', 'biz']
console.log(getFields(new C())); // ['baz']

EDIT: solved by being more defensive (ie: this comment, suggested above

schtauffen avatar Apr 14 '18 22:04 schtauffen

I'm facing the same issue.

I have classes that extend from the same parent class and the metadata is stored on the parent and not the individual classes (or so it seems).

Unfortunately I have to admit that I don't understand the comments above. Is there a way to be "defensive" so that the metadata is only stored on the child class (whilst remaining that of the parent)?

Update: A theory I had was that the amount of alcohol consumed would affect my ability to both process the information and apply it properly. Further research has proven this theory right. I shall now slowly move backwards into the shadows and bow my head in shame.

RWOverdijk avatar Mar 30 '19 21:03 RWOverdijk

Is there any update on this, I'm facing the exact same problem.

Tonio-lavanda avatar Oct 14 '21 13:10 Tonio-lavanda

Same here.

import 'reflect-metadata';

const Searchable = (target: Object, property: string) => {
  Reflect.defineMetadata('searchable', true, target, property);
  console.log(target);// <--- this prints ParentClass, not ChildClass
}

abstract class ParentClass {
  public id: string;
}

class ChildClass extends ParentClass {
  @Searchable
  public name: string;
}

robertmain avatar May 19 '23 18:05 robertmain

Hi All, you can use https://typeix.com/packages/metadata/ which solves problem for you.

igorzg avatar May 25 '23 06:05 igorzg

@igorzg I'm not sure that adding another npm package is really the solution here.

More broadly, however - the solution ended up being:

- console.log(target)
+ console.log(target.constructor)

robertmain avatar May 30 '23 19:05 robertmain

Facing this same issue in version 0.2.1. If the decorator exists in only the child classes it works great. But as soon as the base class has a the same decorator it references the base class instead of the passed in target

class BaseEntity {
  @Nullable() // <---- Remove this decorator to fix
  SomeProp: string = '';
}

class FooEntity extends BaseEntity {
  @Nullable()
  AnotherProp: string = '';
}

class BarEntity extends BaseEntity {
  @Nullable()
  ThirdProp: string = '';
}

Reflect.getMetadata(Symbol('nullableKey'), FooEntity ) returns [SomeProp, AnotherProp, ThirdProp] instead of [AnotherProp] if the @Nullable() decorator exists on the base class

polklabs avatar Feb 03 '24 22:02 polklabs