Extending TypeScript Map to restrict data type

Viewed 4907

I am trying to create a class FooMap that extends the default TypeScript Map class by restricting the type of parameters it accepts. The reason I am extending the Map class is because I want to write methods for that class that I will be accessing from different files.

The default Map class is Map<any,any>, however the FooMap class would be similar to a Map<number,Foo> using the built-in number type and user-defined Foo type. The FooMap would only accept these key/value pairs.

My attempt to to this was by passing a map in the constructor but I feel like there is a better way to do this.

// Say Foo class is defined as    
export class Foo {
    id: number;
    name: string;
}

Now, the FooMap class would be:

export class FooMap extends Map{
    constructor(otherMap : Map<number,Foo>){
        super(otherMap);
    }
}

But this does not work since super() only accepts an iterable, so there must be a better way to extend the Map class.

2 Answers

I am will extend zerkms' answer here with a succinct example:

tl;dr

  1. Create new class FooMap implementing Map<number, Foo>
  2. Add private inner Map<number, Foo>
  3. Expose all the functionality of the Map class

3a. Use IDE to declare all the interface functions of Map

3b. For each function return the inner Map's same function call

class OptionalMap <K, V> implements Map<K, V> {

    // Inner map
    private _innerMap: Map<K, V>;

    constructor(optionalFunctions: { fnName: string, fn: (value: V, ...args) => void }[]) {
        this._innerMap = new Map<K, V>();
        
        // My additional functionality to the standard Map. Note functionality is applied to this class not the inner Map
        if(optionalFunctions) {
            for (let optionalFunction of optionalFunctions) {
                if(this[optionalFunction.fnName]) {
                    throw new Error(`Reserved function name: ${optionalFunction.fnName}`);
                } else {
                    this[optionalFunction.fnName] = (key, ...args) => {
                        if(this.has(key)) {
                            optionalFunction.fn(this.get(key), ...args);
                        }
                    }
                }
            }
        }
    }

    // Exposing all the inner map's inherit functions here in implementing the interface 

    clear(): void {
        return this._innerMap.clear();
    }
    delete(key: K): boolean {
        return this._innerMap.delete(key);
    }
    forEach(callbackfn: (value: V, key: K, map: Map<K, V>) => void, thisArg?: any): void {
        return this._innerMap.forEach(callbackfn, thisArg);
    }
    get(key: K): V {
        return this._innerMap.get(key);
    }
    has(key: K): boolean {
        return this._innerMap.has(key);
    }
    set(key: K, value: V): this {
        this._innerMap.set(key, value);
        return this;
    }
    size: number;
    entries(): IterableIterator<[K, V]> {
        return this._innerMap.entries();
    }
    keys(): IterableIterator<K> {
        return this._innerMap.keys();
    }
    values(): IterableIterator<V> {
        return this._innerMap.values();
    }
    [Symbol.iterator](): IterableIterator<[K, V]> {
        return this._innerMap.entries();
    }
    [Symbol.toStringTag]: string;

}

export default OptionalMap;
Related