TypeScript Builder Design Pattern mirroring Java's approach

In TypeScript 4.6.2, I am working on incorporating the Builder Pattern similar to Java. While I have come across suggestions that this may not be the ideal approach, I have certain limitations when it comes to module exports.

export class HttpRequest {

    static Builder = class {

        private body: string;

        setBody(body: string): HttpRequest.Builder {
            this.body = body;
            return this;
        }

        build(): HttpRequest {
            return new HttpRequest(this);
        }

    }

    private readonly body: string;

    constructor(builder: any) {
        this.body = builder.body;
    }

    getBody(): string {
        return this.body;
    }

}

However, I have encountered an issue with the setBody method not being able to return a HttpRequest.Builder type, resulting in the error message:

TS2702: 'HttpRequest' only refers to a type, but is being used as a namespace here.

It appears that adding // @ts-ignore before the method declaration or changing the method signature to setBody(body: string): any resolves the issue.

My goal is to implement this using nested classes to avoid having separate classes for HttpRequest and Builder. Is there a way to achieve this?

Answer №1

I wouldn't recommend using a builder with static classes. For more information, you can visit the link.

If you want to implement it in your own style, you can follow these steps:

class HttpRequest {

    static Builder = class {

        public body!: string;

        setBody(body: string): typeof HttpRequest.Builder {
            this.body = body;
            return this as any;
        }

        build(): HttpRequest {
            return new HttpRequest(this);
        }

    }

    private readonly body: string;

    constructor(builder: any) {
        this.body = builder.body;
    }

    getBody(): string {
        return this.body;
    }

}

Another approach would be to use the full pattern like this:

interface IHttpRequest
{
 setRequest(body:any):void
}

class HttpRequest implements IHttpRequest
{
  body!:string;
  setRequest(body:string){
    console.log('hook: setRequest called')
    this.body = body;
  }
}

interface IHttpBuilder{
  buildRequest(body:string): void
}

class HttpBuilder implements IHttpBuilder{
  private http = new HttpRequest();
  buildRequest(body:string){
    console.log('hook: buildRequest called')
    this.http.setRequest(body);
  }
}

class Basement{
  private http!: IHttpBuilder;
  constructor(http:IHttpBuilder){
    this.http = http;
  }
  build(body:string){
    console.log('hook: build called')
    this.http.buildRequest(body);
  }
}

let httpReq = new HttpBuilder();
let builder = new Basement(httpReq);

let body = "i am body!";
builder.build(body);

Answer №2

From my personal experience, utilizing a fluent API has proven to be most effective when creating typescript builders. Let me demonstrate how I would approach it in a more typescript-oriented manner.

interface HttpRequest {
    body: string;
}

export class HttpRequestBuilder {
    private body: string = "";

    public withBody(newBody: string): HttpRequestBuilder {
        this.body = newBody;
        return this;
    }

    public build(): HttpRequest {
        return {
            body: this.body
        }
    } 
}

To implement this, you simply instantiate the class

const httpRequest = new HttpRequestBuilder()
    .withBody("add a body here")
   .build();

This design also offers various possibilities for further extension.

Update: here's a potential example of an extension for added flexibility.

export interface HttpRequest {
    body: string;
    contentType: string;
}

export interface HttpRequestExtension extends HttpRequest {
    message: string;
}

export class HttpRequestBuilder {
    private body: string = "";
    private contentType: string = "";

    public withBody(newBody: string): HttpRequestBuilder {
        this.body = newBody;
        return this;
    }

    public withContentType(newContentType: string): HttpRequestBuilder {
        this.contentType = newContentType;
        return this;
    }

    public build(): HttpRequest {
        return {
            body: this.body,
            contentType: this.contentType
        }
    } 
}

export class HttpRequestExtensionBuilder extends HttpRequestBuilder {
    private message: string = "";

    public withMessage(newMessage: string): HttpRequestExtensionBuilder {
        this.message = newMessage;
        return this;
    }

    public build(): HttpRequestExtension {
        return {
            ...super.build(),
            message: this.message
        }
    }
}

This way, we have the option to use either builder:

const httpRequest = new HttpRequestBuilder()
    .withBody("add a body here")
    .withContentType("text/html")
    .build();

const httpRequest = new HttpRequestExtensionBuilder()
    .withMessage("add a message here")
    .withBody("add a body here")
    .withContentType("text/html")
    .build();

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

What steps do I need to take to implement Dispatch in a React Native project?

Context App.tsx import React, { createContext, useContext, useReducer, useEffect, ReactNode, Dispatch, } from "react"; import AsyncStorage from "@react-native-async-storage/async-storage"; import { StateType, ActionType } f ...

SlidingPane header in React disappearing behind Nav bar

Here is the code snippet from my App.js file: export class App extends React.Component { render() { return ( <BrowserRouter> <NavigationBar /> <Routes /> </BrowserRout ...

Can a type alias be created for more than one parameter of a class or function with multiple type parameters?

When using Vue, there are situations where a generic function may require 3, 4, or even 5 type parameters. Is it possible to create a type alias for these parameters in order to avoid typing them out repeatedly? Something like this perhaps: // Example of ...

The error states that the type '() => string | JSX.Element' cannot be assigned to the type 'FC<{}>'

Can someone help me with this error I'm encountering? I am fairly new to typescript, so I assume it has something to do with that. Below is the code snippet in question: Any guidance would be greatly appreciated. const Pizzas: React.FC = () => { ...

Monitor the true/false status of each element within an array and update their styles accordingly when they are considered active

Currently, I am attempting to modify the active style of an element within an array. As illustrated in the image below - once a day is selected, the styles are adjusted to include a border around it. https://i.stack.imgur.com/WpxuZ.png However, my challe ...

Navigating global variables and functions in Vue3 with TypeScript

Feeling lost in the world of Vue.js, seeking guidance here. Attempting to handle global data and its corresponding functions has led me on a journey. Initially, I experimented with declaring a global variable. But as more functions came into play, I trans ...

Injecting Dependencies with Angular 2 and the Ability to Include Optional Parameters

One issue I'm facing is that I have multiple components in my Angular 2 application that require the same dependency. This specific dependency needs a string for the constructor. How can I instruct angular2 to use a specific instance of this type for ...

In React TypeScript, the property types of 'type' are not compatible with each other

I have a unique custom button code block here: export enum ButtonTypes { 'button', 'submit', 'reset', undefined, } type CustomButtonProps = { type: ButtonTypes; }; const CustomButton: React.FC<CustomButtonProp ...

What is the importance of adding the ".js" extension when importing a custom module in Typescript?

This is a basic test involving async/await, where I have created a module with a simple class to handle delays mymodule.ts: export class foo { public async delay(t: number) { console.log("returning promise"); ...

What is the best way to locate and access a JSON file that is relative to the module I am currently working

I am in the process of creating a package named PackageA, which includes a function called parseJson. This function is designed to accept a file path pointing to a JSON file that needs to be parsed. Now, in another package - PackageB, I would like to invok ...

Utilizing the useContext hook within a strictly Typescript-based class component

I have developed a pure Typescript class that serves as a utility class for performing a specific task. Within this class, I have created a context that is intended to be used universally. My goal is to utilize this context and its values within the pure T ...

Utilize Pipe for every instance of a variable in the Controller

In my controller, I have multiple requests (POST, GET etc.) where the path includes an id parameter that needs to be a number string. I want to validate this parameter once and have it apply to all instances. Here is the current code snippet: @Get(&apo ...

NextJS introduces a unique functionality to Typescript's non-null assertion behavior

As per the typescript definition, the use of the non-null assertion operator is not supposed to impact execution. However, I have encountered a scenario where it does. I have been struggling to replicate this issue in a simpler project. In my current proj ...

Encountering a getStaticProps error while using Typescript with Next.js

I encountered an issue with the following code snippet: export const getStaticProps: GetStaticProps<HomeProps> = async () => { const firstCategory = 0; const { data }: AxiosResponse<MenuItem[]> = await axios.post( ...

Using NextJS's API routes to implement Spotify's authorization flow results in a CORS error

I am currently in the process of setting up the login flow within NextJS by referring to the guidelines provided in the Spotify SDK API Tutorial. This involves utilizing NextJS's api routes. To handle this, I've created two handlers: api/login.t ...

Can someone please explain how to prevent Prettier from automatically inserting a new line at the end of my JavaScript file in VS Code?

After installing Prettier and configuring it to format on save, I encountered an issue while running Firebase deploy: 172:6 error Newline not allowed at end of file eol-last I noticed that Prettier is adding a new line at the end when formatting ...

Typescript controller inheritance leading to Error: $injector:unpr Unknown Provider due to minification

LATEST UPDATE: 2019/07/16 The issue I am facing is actually a result of misusing $inject. Instead of declaring it as private $inject in api-service.ts, it should have been public static $inject = [...]. During the minification process, explicit injection ...

How can I effectively test static navigationOptions using Jest and Enzyme in a React Navigation and TypeScript environment?

Currently, I am developing a React Native app using TypeScript. For component testing, I rely on Jest and Enzyme. Additionally, I have integrated React Navigation into my project. On one of the screens, the navigationOptions are as follows: static naviga ...

Guide to importing OBJ file into three.js using TypeScript

Currently, I am utilizing TypeScript along with three.d.ts obtained from definitely typed. While I have successfully been using THREE.JSONLoader, I am encountering difficulties with implementing an OBJLoader from here in a TypeScript project. It seems th ...

How can I load a separate component.html file using a component.ts file?

Hey there, I'm a beginner with Angular and I have a question about loading a different home.component.html file from a method within books.component.ts. Here's the code snippet from my books.component.ts file: import { Component, OnInit } from ...