Developing Custom Methods with TypeScript Factories - Step 2

Working in a factory setting, I find myself needing to incorporate custom methods at some point.

Thanks to the valuable input from the community, I've made progress towards my goal with this helpful answer, although I'm required to include a seemingly unnecessary argument for it to function as intended.

Below is an adjusted version of the code based on the aforementioned solution.

type Base = { id: string }
type Factory<E> = new () => E;

function factory<E extends Base>(name: string): Factory<E>;
function factory<E extends Base, M extends Record<string, <S extends M>(this: E & S) => unknown>>(
  name: string, methods: M, useless?: (this: E & M) => void
): Factory<E & M>;
function factory<E extends Base, M extends Record<string, <S extends M>(this: E & S) => unknown>>(
  name: string, methods?: M, useless?: (this: E & M) => void
): Factory<E> {
  const ret = function(this: Base) {
    this.id = "0"
  };

  Object.defineProperty(ret, "name", { value: name });
  if(methods) Object.assign(ret.prototype, methods);

  return ret as unknown as Factory<E>;
}

const T2 = factory(
  "T2",
  {
    test: function(repeat = true) {
      if(repeat) this.test(false);
      return "test";
    }
  },
  function() {} // If we comment out this line, the TypeScript magic breaks (but not the JavaScript one)
);
const t2 = new T2();
console.log(t2, t2.id, t2.test());

Removing the useless argument disables the visibility of the test method within its definition!

Any thoughts on how to address this issue without relying on the presence of the useless argument?

Feel free to explore and experiment using this interactive playground.

Answer №1

methods defines a set of key-value pairs where each key is a string and the value is a function. This means that you must redefine each function within the methods collection. For instance, consider the following example:

type Base = { id: string }
type Factory<E> = new () => E;

function factory<E extends Base>(name: string): Factory<E>;
function factory<E extends Base, M extends Record<string, <S extends M>(this: E & S) => unknown>>(
  name: string, methods: M & Record<string, (this: E & M) => void>
): Factory<E & M>;
function factory<E extends Base, M extends Record<string, <S extends M>(this: E & S) => unknown>>(
  name: string, methods?: M
): Factory<E> {
  const ret = function (this: Base) {
    this.id = "0"
  };

  Object.defineProperty(ret, "name", { value: name });

  if (methods) for (const method in methods) Object.defineProperty(ret.prototype, method, { value: methods[method] });

  return ret as unknown as Factory<E>;
}

const T1 = factory("T1");
const t1 = new T1();
console.log(t1, t1.id);

const T2 = factory(
  "T2",
  {
    test: function (repeat = true) {
      this.foo()
      if (repeat) this.test(false);
      return "test";
    },
    foo: function (repeat = true) {
      this.test()
      return "foo";
    }
  },
);
const t2 = new T2();
console.log(t2, t2.id, t2.test());

Try it out here

It's important to note that function intersection results in function overloading. In the code provided, I combined M with a record containing string keys and function values, which may have seemed unnecessary at first glance.

P.S. The inclusion of the foo function was solely for testing purposes, so use caution when implementing it in your own code without proper condition statements.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Sending a XML file from a Vue application to an ASP.NET Core backend using axios

I'm encountering difficulties when trying to upload an xml file using axios to my asp .net server. Below is the code snippet I am using on the vue side to retrieve and upload the xml file: uploadXmlFile(file: any) { const rawFile = new XMLHttpRequ ...

The module 'PublicModule' was declared unexpectedly within the 'AppModule' in the Angular 4 component structure

My goal is to create a simple structure: app --_layout --public-footer ----public-footer.html/ts/css files --public-header ----public-header.html/ts/css files --public-layout ----public-layout.html/ts/css files In public-layout.html, the stru ...

In TypeScript, enhancing an interface with additional properties

Currently, I am working on an Angular project and have developed this interface to display some data: export interface UserData { name: string, vorname: string, strasse: string, plz: string, ort: string, handynummer: string, telefonnummer: s ...

Upon running `npm run build` in vue.js, an error occurs stating that the interface 'NodeRequire' cannot extend types 'Require' simultaneously

ERROR in C:/phpStudy2018/PHPTutorial/WWW/Tms.Web/node_modules/@types/node/globals.d.ts(139,11): 139:11 The 'NodeRequire' interface cannot extend both 'Require' and 'RequireFunction' at the same time. The named property &apos ...

Is there a way to utilize req.query, req.params, or req.* beyond its original scope without the need to store it in a database?

Looking to streamline my code and apply the DRY pattern, I've been working on creating a helper function for my express http methods. The structure of each method is similar, but the req.params format varies between them. Here's how I attempted t ...

"Unearthing a skeleton within the client component while the server action unfolds in next

One of the challenges I'm encountering involves a client component that initiates a server action. The server action returns a result, which triggers an update in the UI. Take a look at the code snippet provided below for reference export default func ...

Error: The `ngMetadataName` property cannot be accessed because it is undefined or null in Internet Explorer version 10

Encountered an issue in IE 10 that is not present in IE 11: Error: TypeError: Unable to get property 'ngMetadataName' of undefined or null reference The property ngMetadataName can be found in the file vendor.js. This is the content of polyf ...

The deployment on Heroku is encountering issues due to TypeScript errors related to the MUI package

As someone relatively new to TypeScript and inexperienced in managing deployments in a production setting, I've been working on a project based on this repository: https://github.com/suren-atoyan/react-pwa?ref=reactjsexample.com. Using this repo has a ...

Transform the string property extracted from the API into a JSON object

When making a request to an API, the data returned to the front end is in the following format: { name: 'Fred', data: [{'name': '"10\\" x 45\\" Nice Shirts (2-pack)"', 'price' ...

How to extract a type from a nested type using TypeScript

I am trying to define a type structure where both a and foo are optional: type Something = { a?: { foo?: { bar: { c: { id: string, countryCode: number, animal: { ... } } } } } } Now I n ...

What is the best way to pinpoint and eliminate a Subject from an Observable?

Currently, I am utilizing a service to gather user responses from a questionnaire. The sequence of events is outlined below: questionnaire.component.ts : serves as the main container that receives data from question.service.ts question-shell.component.ts ...

Typescript's confidential variables

Currently, I am delving into the world of Angular2 and familiarizing myself with working with classes in javascript for the first time. I'm curious about the significance of using the private parameter in the constructor, as opposed to simply writing ...

Ways to determine the generic type of a property value from a decorated property within a decorator

While experimenting with some code, I encountered an issue where the generic type of a property value wasn't being resolved correctly when changing from TValue to (t: TValue) => TValue. Instead of being recognized as the expected number, it was now ...

Typescript's Nested Type Assignments

Simply put, I'm making an API call and receiving the following data: { getUserInfo: { country: 'DE', email: '<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="3c48594f487c59445d514c5059125f5351">[e ...

What is the method for incorporating a customized button into the header of a Dynamic Dialog using PrimeNG?

I'm currently working on a project using Angular v17 and PrimeNG. I need to add buttons to the header of a dynamic dialog, but I've been struggling to find a solution. Here's the code snippet from my dialog component: show(j): void { ...

What could be the reason why the getParentWhileKind method in ts-morph is not returning the anticipated parent of the

Utilizing ts-morph for code analysis, I am attempting to retrieve the parent CallExpression from a specific Identifier location. Despite using .getParentWhileKind(SyntaxKind.CallExpression), the function is returning a null value. Why is this happening? I ...

Exciting updates revealed upon new additions (angular)

I'm working with three components in my Angular application: app.component, post-create.component, and post-list.component. Let's take a look at the structure of these components: <app-header></app-header> <main> <app-post-c ...

What are the steps for implementing the ReactElement type?

After researching the combination of Typescript with React, I stumbled upon the type "ReactElement" and its definition is as follows: interface ReactElement<P = any, T extends string | JSXElementConstructor<any> = string | JSXElementConstructor< ...

Upon upgrading to Angular 8, the function this._delegate.setNgStyle is not recognized

Following the update of my project from Angular 7 to Angular 8 and resolving all errors caused by breaking changes, I am encountering a new issue: <div fxFill ngStyle.xs="overflow:auto"> This line is resulting in the following error: ERROR Type ...

Utilizing a Function's Parameter Type in TypeScript: A Beginner's Guide

Currently, I am creating a custom method that wraps around Angular's HttpClient service. I want users to have the ability to pass in options, but I am struggling to find the proper way to reference that type in my method parameter definition. For exa ...