class Car {
engine:number;
detials:{
good:'Boy'
}
}
Class ModelProperty<T>
when constructed with new ModelProperty<Car>('engine',22);
should work as engine
is a property of Car
and 22
as same type as engine
i.e. number
.
export class ModelProperty<T, P extends keyof T, V = T[P]> {
constructor(public name: P, public value: V) { }
fun(t: T){
let value: any = t[this.name]; // Should not be any
let valueWhyError: V = t[this.name]; //Error.. Why?
}
}
let engine2 = new ModelProperty<Car,'engine'>('engine','22'); // Gives error as '22' should be number.. working great.
let engine1 = new ModelProperty<Car,'engine'>('engine',2); // But there is repeatation 'engine', 'engine'
engine
V
should mean number
. But that line in function do
gives error.<Car,'engine'>
but only <Car>
. Its following properties should be by default depending upon the argument.new ModelProperty<Car>(['details','good'],'Girl')
.For the first issue, the problem with your approach for the V
parameter is that you specify the default for it, but that does not mean V
MUST extend T[P]
, just that that is the default, you can invoke the constructor with really any type parameter for V
. Just use T[P]
where appropriate, as even if you constrain it properly (V extends T[P] = T[P]
) the compiler it will still not be able to correctly follow that V
is assignable from T[P]
.
export class ModelProperty<T, P extends keyof T> {
constructor(public name: P, public value: T[P]) { }
fun(t: T){
let value = t[this.name]; // is T[P]
}
}
As for your second issue of repetition, this is an unfortunate side effect of the way type parameters and inference work, if you specify a default for the generic parameter, that default will be used and no inference will take place. If you don't specify the default for K
, you can't specify just the value for T
, you must also specify K
. The simple workaround is to use a two function approach :
export class ModelProperty<T, P extends keyof T> {
constructor(public name: P, public value: T[P]) { }
static for<T>() {
return function <P extends keyof T>(name: P, value: T[P]){
new ModelProperty<T, P>(name, value);
}
}
}
const carModelCreator = ModelProperty.for<Car>();
let engine2 = carModelCreator('engine','22'); // Gives error as '22' should be number.. working great.
let engine1 = carModelCreator('engine',2); // But there is repeatation 'engine', 'engine'
As for the third issue of nested paths, classes can't have a variable number of type parameters, so you have the option of creating dedicated classes for each path length.
export class ModelProperty2<T, P extends keyof T, P2 extends keyof T[P]> {
constructor(public name: [P, P2], public value: T[P][P2]) { }
static for<T>() {
return function <P extends keyof T, P2 extends keyof T[P]>(name: [P, P2], value: T[P][P2]){
new ModelProperty2<T, P, P2>(name, value);
}
}
}
const carModelCreator = ModelProperty2.for<Car>();
let engine2 = carModelCreator(['detials', 'good'],'22'); //error
let engine2 = carModelCreator(['detials', 'good'],'Boy'); //ok
Or if you want you can create a single overloaded function that returns an instance ModelProperty
where the only type parameter is the value of the last property, and the path is string[]. You get type safety when you create the instance but the info is lost after
export class ModelProperty<T, V> {
constructor(public name: string[], public value: V) { }
static for<T>() {
function helper<P extends keyof T, P2 extends keyof T[P]>(name: [P, P2], value: T[P][P2])
function helper<P extends keyof T>(name: [P], value: T[P])
function helper(name: string[], value: any){
return new ModelProperty<T, any>(name, value);
}
return helper;
}
}
const carModelCreator = ModelProperty.for<Car>();
let engine1 = carModelCreator(['engine'], 22); // ok
let engine2 = carModelCreator(['detials', 'good'],'Boy'); //ok
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With