Reputation: 9013
I have a model class which manages an internal property _record
which is a record from the database. The model class is a generic base class and each model type extends this base class.
I want to provide two convenience methods for modifying properties on the record: setValue for setting a discrete data structure and pushValue to push a discrete data structure onto an array.
/**
* Allows setting individual properties on the record
*
* @param key the property on the record you want to set
* @param value the value of the property
*/
public setValue<K extends keyof T>(key: K, value: T[K]) {
this._record[key] = value;
}
/**
* Allows setting an individual property on a record where the property
* must be an array and result of the operation is the addition
* of a new array element
*
* @param key the property name on the model's record
* @param value an element to add to the existing array
*/
public pushValue<K extends keyof T>(key: K, value: keyof T[K]) {
if (!this._record[key]) {
this._record[key] = [ value ] as T[K];
} else if (Array.isArray(this._record[key])) {
this._record[key] = this._record[key].concat(value);
}
}
The setValue
works fine but I'm struggling with the appropriate typings for the pushValue
. The errors are:
I'm using Typescript 2.4.0
Upvotes: 0
Views: 251
Reputation: 51629
Array.isArray(this._record[key])
does not work as type guard, this._record[key]
remains typed as T[K]
after it, probably because of indexed property access (there is similar issue reported here: https://github.com/Microsoft/TypeScript/issues/11483 , resolution is "declined for performance reasons".)
The suggested workaround for that issue is to pass intermediate variable instead of this._record[key]
to the type guard, and it indeed works. For a simple variable, its type gets inferred as const v: T[K] & any[]
, with any[]
part coming from Array.isArray
type guard, so this code compiles with --noImplicitAny
:
abstract class Model<T> {
_record: T;
public setValue<K extends keyof T>(key: K, value: T[K]) {
this._record[key] = value;
}
public pushValue<K extends keyof T, V>(key: K, value: V) {
const v = this._record[key];
if (!v) {
this._record[key] = [value] as any;
} else if (Array.isArray(v)) {
v.push(value);
}
}
}
Upvotes: 1