skip to Main Content

I have a React application, and in order to make refactoring easier, I need to add an extension method on my Product type:

product.hasAbc() // boolean check that references the attributes property
product.hasDef() // boolean check

Currently my API returns a response which then gets bound to a typescript interface type.

Below is my Product Interface, and then decoder that decodes the API json response. My actual axios function uses the product decoder.

export interface Product {
  readonly id: string;
  readonly attributes: ReadOnlyArray<string>,
  ...
}

export const productDecoder = JsonDecoder.object<Product>(
  {
    id: JsonDecoder.string,
    ...
  },
  "Product"
);

async function decodeResponse<T>(decoder: JsonDecoder.Decoder<T>, responseData: any): Promise<T> {
  return decoder.decodePromise(responseData);
}



export async function getProduct(): Promise<Product> {
  return new Promise((resolve, reject) => {
    customAxios.get("/api/products/123")
      .then(response =>
        decodeResponse<Product>(productDecoder, response.data).then(resolve).catch(reject)
      )
      .catch(error => reject(pathOr("Error...", errorMsgPath, error)));
  });
}

How can I add extension methods on the attributes so I can do:

const hasAbc = () => {
   this.attributes.contains("abc")
}

Reference: The project is using ts.data which is a decoding library: https://github.com/joanllenas/ts.data.json

2

Answers


  1. You can define a new method on Array object prototype. E.g:

    Array.prototype.IsTrue = function () {
        return true;
    }
    

    Then use it:

    product.attributes.IsTrue()
    

    But this method will exist on every instance of Array object. E.g: [1,'abc'].IsTrue() is a valid usage.

    You can’t define the extension method for only one interface of your choice, because javascript actually don’t have the notion of Interface or Type. It’s just a language feature that Typescript bring up to help your developer life easier.

    The recommended approach in React world would be some Utility functions, you import and use them on demand.

    Login or Signup to reply.
  2. I’m going to recommend a slightly different approach that I think gets at the functionality you are looking for.

    First, as noted by others, an interface is a Typescript language feature not a Javascript language feature. It’s used for static type checking during compile time, and that’s ir; there is no object or class that gets created in the resulting JavaScript. As such, you can’t extend it. If you wanted to be able to extend it you’d need to use a class instead of an interface.

    What you can do, however, is define special functions that will evaluate a given instance of your Product interface and inform Typescript whether or not it it has additional properties. These special functions are referred to as type guards (https://www.typescriptlang.org/docs/handbook/advanced-types.html).

    So, for instance, you could write a hasAbc type guard like so:

    function hasAbc (p : Product): p is Product & { abc: string } {
      return _.isString((p as any).abc);
    }
    

    Now if you use this function in an if-block, the typescript compiler will be happy when you reference the field abc from a Product:

    function work(p: Product) {
      …
      if (hasAbc(p)) (
        // no type script error!
        const abc = p.abc; 
        …
      }
      …
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search