a class overwrites the properties of the class it is extending :
class ClassA {
msg = 'ClassA'
}
export default class ClassB extends ClassA {
msg = 'ClassB'
onClick() {
console.log(this.msg) // will log "ClassB"
}
}
a decorator overwrites the properties of the class it is decorating :
function ClassA(BaseClass) {
return class extends BaseClass {
msg = 'ClassA'
}
}
@ClassA
export default class ClassB {
msg = 'ClassB'
onClick() {
console.log(this.msg) // will log "ClassA"
}
}
extending multiple classes is not allowed - you cannot do (pseudocode):
export default class ClassC extends [ClassB, ClassA] {}
but you can do:
@ClassA
@ClassB
export default class ClassC {}
The problem with the above is that the "extension" is reversed. The decorators
will overwrite any properties that ClassC
has defined. But I don't want that.
In other words, I want to use decorators
to emulate multiple class extension but I don't want my decorators
to overwrite any properties that the decorated class has already defined.
I could do the following:
function ClassA(BaseClass) {
return class extends BaseClass {
constructor() {
super(...arguments)
if (!this.hasOwnProperty('msg')) {
this.msg = 'ClassA'
}
}
}
}
@ClassA
export default class ClassB {
msg = 'ClassB'
onClick() {
console.log(this.msg) // will log "ClassB"
}
}
but this is too verbose.
Imagine a class with many properties, some of which are even decorated themselves. Having to implement the above solution below seems unsustainable:
function ClassA(BaseClass) {
return class extends BaseClass {
msg = 'ClassA' // this should "register" for ClassB, not for ClassC
@service('x') aa // this should "register" for ClassC, not for ClassB
@computed('aa')
get bb() { // this should "register" for ClassC, not for ClassB
return this.aa + 5000
}
}
}
@ClassA
export default class ClassB {
@service('y') aa
@computed('aa')
get bb() {
return this.aa + 3
}
}
@ClassA
export default class ClassC {
msg = 'Class C'
}
Is there a easier way of having a decorator not overwrite the properties of the class it is decorating if they already exist?
Or is what I'm trying to achieve impossible?
Note 1: I do not want to use class extension because I want to extend multiple classes which is impossible so the idea is to use multiple decorators as a workaround
Note 2: Please do not suggest multiple class extension examples. I've seen them all and they are all pretty hacky and only work with very basic simple classes with primitive properties, not with classes which themselves contain decorated properties, complicated methods, other class instances etc..
EDIT: I just thought of this solution but have yet to test it. It's meant to emulate multiple class extension (ClassA, ClassB) using decorators, while at the same not allowing the decorators to overwrite properties that ClassC has already defined:
@ClassA
@ClassB
class MyDecorators { /* empty on purpose */ }
export default class ClassC extends MyDecorators {}
I found a solution to my problem so I'm posting it here in case it helps.
function decoratorB(BaseClass) { // Class B
return class extends BaseClass {
unchangedClassBproperty = 'unchanged Class B property'
originallyDefinedByClassB = 'originally Defined By Class B'
originallyDefinedByClassC = 'overwritten by Class B'
}
}
function decoratorC(BaseClass) { // Class C
return class extends BaseClass {
unchangedClassCproperty = 'unchanged Class C property'
originallyDefinedByClassC = 'originally Defined By Class C'
originallyDefinedByClassD = 'overwritten by Class C'
}
}
function decoratorD(BaseClass) { // Class D
return class extends BaseClass {
unchangedClassDproperty = 'unchanged Class D property'
originallyDefinedByClassD = 'originally Defined By Class D'
showInheritance() {
console.log('unchangedClassAproperty = ', this.unchangedClassAproperty)
console.log('unchangedClassBproperty = ', this.unchangedClassBproperty)
console.log('unchangedClassCproperty = ', this.unchangedClassCproperty)
console.log('unchangedClassDproperty = ', this.unchangedClassDproperty)
console.log('originallyDefinedByClassB = ', this.originallyDefinedByClassB)
console.log('originallyDefinedByClassC = ', this.originallyDefinedByClassC)
console.log('originallyDefinedByClassD = ', this.originallyDefinedByClassD)
}
}
}
@decoratorB
@decoratorC
@decoratorD
class ClassesBCD {}
class A extends ClassesBCD {
unchangedClassAproperty = 'unchanged Class A property'
originallyDefinedByClassB = 'overwritten by Class A'
}
const a = new A()
a.showInheritance()
will log:
unchangedClassAproperty = unchanged Class A property
unchangedClassBproperty = unchanged Class B property
unchangedClassCproperty = unchanged Class C property
unchangedClassDproperty = unchanged Class D property
originallyDefinedByClassB = overwritten by Class A
originallyDefinedByClassC = overwritten by Class B
originallyDefinedByClassD = overwritten by Class C
See jsFiddle