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

The Problem of Unspecified Return Type in Vue 3 Functions Using Typescript

Here is the code snippet I am working with: <template> <div> <ul v-if="list.length !== 0"> {{ list }} </ul> </div> </template> < ...

Obtain the specific generic type that is employed to broaden the scope of a

I am working on a class that involves generics: abstract class Base<P extends SomeType = SomeType> { // ... } In addition, there is a subclass that inherits from it: class A extends Base<SomeTypeA> { // ... } I'm trying to figure out ...

The input of type 'Observable<true | Promise<boolean>>' cannot be assigned to the output of type 'boolean | UrlTree | Observable<boolean | UrlTree> | Promise<boolean | UrlTree>'

I'm currently using a Guard with a canActivate method: canActivate( next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree { return this.fi ...

When using Typescript, I am encountering an issue where declared modules in my declaration file, specifically those with the file

One of the declarations in my ./src/types.d.ts file includes various modules: /// <reference types="@emotion/react/types/css-prop" /> import '@emotion/react'; import { PureComponent, SVGProps } from 'react'; declare mod ...

Obtain non-numeric parameters from the URL in Angular 2 by subscribing to

How do I handle subscribing to a non-numeric parameter from a URL? Can the local variable inside my lambda function params => {} only be a number? Here's my code: getRecordDetail() { this.sub = this.activatedRoute.params.subscribe( ...

Transforming a string such as "202309101010" into a date entity

Need to convert a string in the format "YYYYMMDDHHMM" (e.g. "202309101010") into a Date object in TypeScript? Check out this code snippet for converting the string: const dateString: string = "202309101010"; const year: number = parseInt(dateString.subst ...

Using routerLink for linking to multiple components

Below is the anchor tag provided: <a class="uppercase" routerLink="settings/pressure" routerLinkActive="text-success" (click)="closeModal()" > <div class="pad-btm"> PRESSURE </div> </a> I need to include another route ...

Validate if the program is currently running as "ionic serve" before implementing a conditional statement

Is there a method to determine if the ionic serve CLI is currently active (indicating it's not running on a physical device) within the code and use it as a condition? My problem: I have a Cordova plugin that returns a response to Cordova. When usin ...

fill the designated column in accordance with the specific criteria

Is there a method to automatically fill in a specific column based on certain conditions? I am looking to populate the column labeled [Last] when the column index is 1 and the corresponding name is [First]. import {Component, OnInit} from '@angular ...

Rearranging items within an array in a React component

Currently, I am facing a situation where I have created a list that dynamically adds a React Node upon clicking a button. The final layout of the model looks like this: Here is the code snippet for your reference: import * as React from 'react' ...

React Typescript: The element is implicitly assigned an 'any' type as the type does not have an index signature

While attempting to locate a key of an object using an item from an array, I encountered an error... An Element implicitly has an 'any' type because type lacks an index signature I've replicated the issue in this sandbox https://codesandbo ...

Updating the displayed data of an angular2-highcharts chart

I am facing an issue with rendering an empty chart initially and then updating it with data. The charts are rendered when the component is initialized and added through a list of chart options. Although the empty chart is successfully rendered, I am strugg ...

Converting hexadecimal to binary using Javascript or Typescript before writing a file on an Android or iOS device

Hey everyone! I'm facing a puzzling issue and I can't seem to figure out why it's happening. I need to download a file that is stored in hex format, so I have to first read it as hex, convert it to binary, and then write it onto an Android/ ...

Struggling with setting up Angular Material and SCSS configuration in my application -

Hey there, I encountered an error or warning while trying to launch my angular app. Here's the issue: ERROR in ./src/styles/styles.scss (./node_modules/@angular-devkit/build- angular/src/angular-cli-files/plugins/raw-css- loader.js!./n ...

Effectively enhance constructor by incorporating decorators

Is it possible to properly extend a class constructor with decorators while maintaining the original class name and static attributes and methods? Upon reading the handbook, there is a note that cautions about this scenario: https://www.typescriptlang.or ...

Combine an array of arrays with its elements reversed within the same array

I am working with an array of numbers that is structured like this: const arrayOfArrays: number[][] = [[1, 2], [1, 3]]; The desired outcome is to have [[1, 2], [2, 1], [1, 3], [3, 1]]. I found a solution using the following approach: // initialize an e ...

managing commitments in TypeScript

Is there a way to convert a promise into a string, or is there another method for handling this result? I am encountering an error stating "You cannot use an argument of type 'Promise' for a parameter of type 'string'." const pokemonIma ...

TypeScript: implementing function overloading in an interface by extending another interface

I'm currently developing a Capacitor plugin and I'm in the process of defining possible event listeners for it. Previously, all the possible event listeners were included in one large interface within the same file: export interface Plugin { ...

Demonstrate JSON data using ngFor loop in Angular

Need some assistance here. Trying to display data from a .json file using a ngFor loop. However, I keep running into the following error in my code: Error: Cannot find a differ supporting object '[object Object]' of type 'object'. NgF ...

Altering the dimensions of radio buttons

I am a newcomer to using material-ui. I am currently working on incorporating radio buttons in a component and would like to reduce its size. While inspecting it in Chrome, I was able to adjust the width of the svg icon (1em). However, I am unsure how to a ...