Ensure that the method is triggered

I have a builder class that implements an interface which it is expected to build.

However, I would like to enforce one method of this class to be called at compile time, rather than runtime. The class is designed to be used as a chain of method calls and then passed to a function as the interface it implements. It would be ideal to require the method call immediately after the constructor, but not absolutely necessary.

For example: playground

interface ISmth {
  x: number;
  y?: string[];
  z?: string[];
}

class SmthBuilder implements ISmth {
  x: number;
  y?: string[];
  z?: string[];

  constructor(x: number) {
    this.x = x;
  }

  useY(y: string) {
    (this.y = this.y || []).push(y)
    return this
  }

  useZ(z: string) {
    (this.z = this.z || []).push(z)
    return this
  }
}

declare function f(smth: ISmth): void

f(new SmthBuilder(123)
  .useY("abc") // make this call required
  .useZ("xyz")
  .useZ("qwe")
)

Answer №1

It seems logical to enhance ISmth by indicating that useY() has been invoked, as shown below:

interface ISmthAfterUseY extends ISmth {
  y: [string, ...string[]];
}

Subsequently, the useY() method of your SmthBuilder can return an ISmthAfterUseY:

  useY(y: string) {
    (this.y = this.y || []).push(y)
    return this as (this & ISmthAfterUseY);
  }

If your f() function requires an ISmth with a specified, non-empty y property, it should specifically request an ISmthAfterUseY instead of an ISmth:

declare function f(smth: ISmthAfterUseY): void

f(new SmthBuilder(123)
  .useY("abc")
  .useZ("xyz")
  .useZ("qwe")
) // okay

f(new SmthBuilder(123).useZ("xyz")) // error!
// Types of property 'y' are incompatible.

That's the gist of it; I wish you good fortune!

Playground link

Answer №2

Utilizing Typescript allows for flexibility in defining interfaces. By omitting explicit interface declarations, the compatibility with the interface can be altered until adjustments are made within the useY function call: playground

interface ISomething {
  x: number;
  y?: string[];
  z?: string[];
}

class SomethingBuilder {
  x: ISomething["x"];
  y?: ISomething["y"] | "You have to call 'useY' at least once";
  z?: ISomething["z"];

  constructor(x: number) {
    this.x = x;
  }

  useY(y: string): { y: ISomething["y"] } & this {
    (this.y = this.y as ISomething["y"] || []).push(y)
    return this as any
  }

  useZ(z: string) {
    (this.z = this.z || []).push(z)
    return this
  }
}

declare function func(something: ISomething): void

func(new SomethingBuilder(123)
  .useY("abc") // this call is required
  .useZ("xyz")
  .useZ("qwe")
)

To enforce a single call restriction on useY, modification only needs to be applied to the definition of useY:

useY(y: string): { y: ISomething["y"] } & this & Omit<this, "useY"> {

It's important to note that this behavior is dependent on chaining the calls together. Storing the instance in a variable freezes the type of the variable while allowing changes to the internal state with each subsequent call.

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

Transmit information using JSON format in Angular 8 using FormData

i am struggling with sending data to the server in a specific format: { "name":"kianoush", "userName":"kia9372", "email":"<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="bcd7d5ddd8ce85...@example.com</a>" } H ...

Problems with the zoom functionality for images on canvas within Angular

Encountering a challenge with zooming in and out of an image displayed on canvas. The goal is to enable users to draw rectangles on the image, which is currently functioning well. However, implementing zoom functionality has presented the following issue: ...

What is the best way to link multiple select tags in an HTML document?

I am working with a data grid that contains student information such as Name, Class, and Score. Each row has a checkbox for selection. The requirement is that when the user selects one or more rows and clicks on the "show information" Button, a new windo ...

Is there a method to globally import "typings" in Visual Code without having to make changes to every JS file?

Is there a method to streamline the process of inputting reference paths for typings in Visual Studio Code without requiring manual typing? Perhaps by utilizing a configuration file that directs to all typings within the project, eliminating the need to ...

Unable to replicate the function

When attempting to replicate a function, I encountered a RootNavigator error Error in duplicate function implementation.ts(2393) I tried adding an export at the top but it didn't resolve the issue export {} Link to React Navigation options documen ...

Error: Unable to access the 'filter' property as it is undefined. TypeError occurred

findLoads(){ if(this.loggedInUser.userFullySetupFlag === 0 || this.loggedInUser.businessFullySetupFlag === 0){ swal( 'Incomplete Profile', 'To find loads and bid, all the details inside User Profile (My Profile) and Business Profil ...

Angularv9 - mat-error: Issue with rendering interpolated string value

I have been working on implementing date validation for matDatepicker and have run into an issue where the error messages do not show up when the start date is set to be greater than the end date. The error messages are supposed to be displayed using inter ...

Is there support for TypeScript in express-openid-connect?

Is there any documentation available for using express-openid-connect with TypeScript, or if it is supported at all? ...

Creating a read-only DIV using Angular - a step-by-step guide

Is there a simple way to make all clickable elements inside a div read only? For example, in the provided HTML code, these divs act like buttons and I want to disable them from being clicked. Any tips or shortcuts to achieve this? Thank you. #html < ...

How can we implement `injectReducer` in Redux with TypeScript?

I have been working on a TypeScript React application with Redux to manage state. To dynamically add reducers, Redux suggested implementing an injectReducer function. In a JavaScript project, I successfully implemented this function. However, I am strugg ...

The array containing numbers or undefined values cannot be assigned to an array containing only numbers

Currently facing an issue with TypeScript and types. I have an array of IDs obtained from checkboxes, which may also be empty. An example of values returned from the submit() function: const responseFromSubmit = { 1: { id: "1", value: "true" }, 2: ...

Trying to enter the function, but it exits without executing

I'm facing an issue with my function that involves making multiple calls to an observer. I need the function to wait until all the calls are complete before returning. I attempted putting the return statement inside the subscribe method, but it result ...

[Simple TypeScript]: Assign the parameter value as the key of the object returned by the function

How do I modify the key of a function returned object to match its parameter's value? Here is my current code: const createCache = <A, T extends string>(name: T, base = {}) => { const cache = base; return { [name]: cache, } as { ...

Transforming button click from EventEmitter to RXJS observable

This is the functionality of the component utilizing EventEmitter: import { Component, Output, EventEmitter } from "@angular/core"; @Component({ selector: "app-my-component", template: ` <button (click)="clickEvent($event)& ...

How to immediately set focus on a form control in Angular Material without needing a click event

Currently working with Angular 9 and Material, we have implemented a Stepper to assist users in navigating through our workflow steps. Our goal is to enable users to go through these steps using only the keyboard, without relying on mouse clicks for contro ...

Support for dark mode in Svelte with Typescript and TailwindCSS is now available

I'm currently working on a Svelte3 project and I'm struggling to enable DarkMode support with TailwindCSS. According to the documentation, it should be working locally? The project is pretty standard at the moment, with Tailwind, Typescript, and ...

Encountering issues in d3.js following the transition to Angular 8

After upgrading my Angular 4 app to Angular 8, I encountered an issue where the application works fine in development build but breaks in production build. Upon loading the application, the following error is displayed. Uncaught TypeError: Cannot read p ...

Type definitions in Typescript for the style property of Animated.View

One of my components has a Props interface that extends ViewProps from React Native, like this: export interface Props extends ViewProps { // Custom props } As a result, this also extends the style prop. However, I am facing an issue while using Animat ...

Tips for verifying the response and status code in Angular 8 while uploading a file to an S3 Presigned URL and receiving a statusCode of 200

Looking to Upload a File: // Using the pre-signed URL to upload the file const httpOptions = { headers: new HttpHeaders({ 'Content-Disposition': 'attachment;filename=' + file.name + '', observe: 'response' }) }; ...

Creating a nested type using template literal syntax

Given a two-level nested type with specific properties: export type SomeNested = { someProp: { someChild: string someOtherChild: string } someOtherProp: { someMoreChildren: string whatever: string else: string } } I am looking ...