Search code examples
typescriptinterface

Adding fields into interface


Description and code examples

I'm building a project on vanilla JS using TS.

I have a custom storage class which is stored in window object so I can interact with it wherever:

CustomStorage.ts

class CustomStorage {
    private static _core: Record<string, any> = {};
    private _core: Record<string, any>;

    constructor() {
        if (!CustomStorage._core)
            CustomStorage._core = {};

        this._core = CustomStorage._core;
        (window as any).CustomStorage = this;
    }

    get items(): Record<string, any> {
        return this._core;
    }

    setItem(prop: string, value: any): void {
        this._core[prop] = value;
    }

    clearStorage(): void {
        this._core = {};
    }
}

export default CustomStorage;

I use this storage in many files throughout the project, and I want to know which fields my storage has in any external file

For example I have a separate file where I fill my storage to use this data later:

(Not full file):

export default function fillStorage() {
    /**
     * TableHeaders - needed for the table to print only exact columns
     * Also stores into the Storage to be able to be called later
    */
    const tableHeaders: string[] = ['ProdCode', 'Customer', 'ProdName', 'HostName', 'MatNum', 'ArticleNum', 'WkStNmae', 'AdpNum', 'ProcName', 'AVO', 'FPY', 'CountPass', 'CountFail', 'tLogIn', 'tLogOut', 'tLastAcc'];
    Storage.setItem('tableHeaders', tableHeaders);

    Storage.setItem('selectedHeaders', []);

    if (Storage.items.data) {
        // StaticData - stored to be a full version of initial array
        Storage.setItem('staticData', Storage.items.data);

        // AllHeaders - needs for reset listener to fill dropdown immediately
        Storage.setItem('allHeaders', Object.keys(Storage.items.staticData[0]));

        // StaticDataLength - stored, not to calculate length later
        Storage.setItem('staticDataLength', Storage.items.staticData.length);
        Storage.setItem('headers', Object.keys(Storage.items.data[0]));

        // AllValues - as same as allHeaders. not to calculate later. Receives all present value from the static data
        Storage.setItem('allValues', DropdownValues(Storage.items.staticData, Storage.items.tableHeaders));

        // Stored not to keep text present as it takes lot of memory
        Storage.setItem('inputTextLength', Storage.items.data.length);
    }

And when I want to use this data later in a file like

index.ts:

const handleFiltersEraserClick = async (e: MouseEvent) => {
    const target = e.target as HTMLElement;

    if (target?.id.substring(0, 6) === 'eraser') {
        const targetId: string = target?.id.slice(7);

        Storage.items.inputFields[+targetId - 1].value = '';
        Storage.items.dbSelects[+targetId - 1].selectedIndex = 0;

        Storage.items.dataSourceOption === 'Datenbank'
            ? await DBQuery()
            : Storage.setItem('data', getFilters() as object[]);
        if (rowsAmount) {
            Storage.items.data.length === 0
                ? rowsAmount.innerHTML = '0'
                : rowsAmount.innerHTML = Storage.items.data.length;
        }

        let dropdownValues: { values: string[], valueToHeaderMap: object } | null = DropdownValues(Storage.items.data, Storage.items.tableHeaders);

        Storage.items.datalists.forEach((datalist: HTMLDataListElement) => {
            datalist.innerHTML = '';

            dropdownValues?.values.forEach((value: string) => {
                const option: HTMLOptionElement = document.createElement('option');
                option.className = 'datalist-option';
                option.value = value;
                datalist.appendChild(option);
            });
        });

        dropdownValues = null;
    }
};

I shall guess if I chose right field or not. That's why I want to have an interface (probably) to know all the time what fields I have.

I could just create a static interface, but I have many cases when new fields are being added to the storage after fillStorage() call

Question:

Is it possible to extend initial interface when something is being added into storage? Making static interface could lead to issues when I have such field in the interface, but it will be undefined cause it still doesn't exist

How I see it

I have initial interface

interface Storage {
   staticData: object[],
   allHeaders: string[],
   staticDataLength: number,
   headers: string[],
   allValues: object[],
   inputTextLength: number
}

after something happened and I added f.e.

Storage.setItem('numbers', [1, 2, 3])

I need then this number to appear in interface Storage so I will have

interface Storage {
   staticData: object[],
   allHeaders: string[],
   staticDataLength: number,
   headers: string[],
   allValues: object[],
   inputTextLength: number,
   numbers: number[]
}

And if I delete field from Storage to delete corresponding field from the interface


Hope I've been clear in my question. Any recommendations and advices would be highly appreciated


Solution

  • I think you are looking in the wrong places, and I recommend you look at the compiled JavaScript output from TypeScript. Interfaces do not emit any code at all, they are only there to describe objects within TypeScript. That's the "type" in TypeScript!

    A few tips about types:

    • avoid using object for a type, that is far too generic. Better describe your objects instead.
    • avoid using the any type - since it can be anything why bother using TypeScript at all? It's best to describe your objects instead.
    • prefix interfaces & type names with I so it's easier to tell what is an interface and what is not
    • You can add optional properties with a ? suffix on the property name
    • Types cannot be modified at runtime because they are not code, you can however extend types

    Here's a demo rewrite of your type with some made up interfaces to use instead of object. Note the optional properties

    interface IData {
      foo: string;
      bar?: number;
    }
    
    interface IValue {
      baz: string;
      val?: number;
    }
    
    interface IStorage {
       staticData: IData[],
       allHeaders: string[],
       staticDataLength: number,
       headers: string[],
       allValues: IValue[],
       inputTextLength: number
    }
    
    interface IStorageWithNumbers extends IStorage {
       numbers: number[]
    }
    
    const myStorage: IStorageWithNumbers = {
      staticData: [{foo: 'a'}, {foo: 'b', bar: 2}],
      allHeaders: [''],
      staticDataLength: 0,
      headers: [''],
      allValues: [{baz: 'c', val: 3}, {baz: 'd'}],
      inputTextLength: 0,
      numbers: [1, 2, 3]
    };
    

    Please check this TS Playground link and see the JS output from it by selecting ".JS" on the right side