Reputation: 140
Is there a way to validate the type of the property that is decorated in Typescript? I would like a property decorator that only works on boolean
class properties, but not on e.g. string
(example below). Is this possible?
(Note: I don't want runtime validation via reflect-metadata, just a compile type warning with Typescript.)
function booleanProperty<T extends HTMLElement>(target: T, prop: string) {
// `target` will be the class' prototype
Reflect.defineProperty(target, prop, {
get(this: T): boolean {
return this.hasAttribute(prop);
},
set(this: T, value: boolean): void {
if (value) {
this.setAttribute(prop, '');
} else {
this.removeAttribute(prop);
}
}
});
}
class MyElement extends HTMLElement {
@booleanProperty
foo: boolean;
@booleanProperty
bar: string; // TS compile error
}
customElements.define('my-element', MyElement);
Upvotes: 6
Views: 2261
Reputation: 29906
I've found a way to match the type of the property to the type of the decorator, which also works on private fields, and allows for specifying dataflow direction. I've created a library of it, hope it helps.
import { TypedPropertyDecorator } from 'typesafe-decorators';
declare const StringLogger: TypedPropertyDecorator<string, 'get'>;
declare const BooleanValidator: TypedPropertyDecorator<boolean, 'set'>;
declare const EnumValidator: TypedPropertyDecorator<'foo' | 'bar' | 'baz', 'set'>;
class Foo {
@StringLogger // OK
private x1!: 'a' | 'b';
@StringLogger
// ^^^^^^^^^^ Type of property is not assignable to type of decorator
private x2!: number;
// This logger annotation can only log strings
@BooleanValidator // OK
private x3!: boolean;
@EnumValidator // OK
private x4!: string;
@EnumValidator
// ^^^^^^^^^^^ Type of decorator is not assignable to type of property
private x5!: 'foo' | 'bar';
// Validator would allow 'baz' too, which does not fit into the property
}
Upvotes: 2
Reputation: 327819
What about this?
function booleanProperty<
T extends HTMLElement & Record<K, boolean>,
K extends string>(target: T, prop: K) {
// ... impl here
}
The passed-in target
needs to be an HTMLElement
which has a boolean
property at the key K
, where K
is the type of the passed-in prop
. Let's see if it works:
class MyElement extends HTMLElement {
@booleanProperty // okay
foo: boolean;
@booleanProperty // error
bar: string;
}
Looks good to me. Does that work for you?
Upvotes: 13