Using TypeScript to implement a nested static class with enforced generic type constraints

As an illustration, let's consider a basic linked list class in TypeScript version 3.7.5. A LinkedList<T> is composed of a series of ListNode<T>, where the type variable T remains consistent between them. In this scenario, a private static field is utilized within LinkedList to conceal ListNode, which is considered extraneous from an implementation standpoint.

class LinkedList<T> {
  private head: ??? = null;
  private tail: ??? = null;

  private static ListNode = class ListNode<T> {
    constructor(
      public val: T | null,
      public next: ListNode<T> | null) {}
  };

  append(item: T): this {
    if (!this.tail) {
      this.tail = {val: item, next: null};
      this.head = this.tail;
    } else {
      this.tail.next = {val: item, next: null};
      this.tail = this.tail.next;
    }
    return this;
  };

  remove(): T {
    if (!this.head || this.head.val === null) {
      throw Error();
    } else {
      const t = this.head.val;
      this.head = this.head.next;
      return t;
    }
  }
}

What should be used as the placeholder type ??? in the code above? It's neither List.ListNode nor List.ListNode<T>. This isn't recognized as valid TypeScript (at least not in version 3.7.5). Another option is not

InstanceType<typeof List.ListNode>
. This may be a valid type, but it disregards the generic parameter T, thus failing to enforce that both the enclosing and nested classes are parameterized by the same type.

Now, we make adjustments to the class by introducing a dummy head and relying on type inference for further guidance:

class LinkedList<T> {
  private head = LinkedList.makeNode<T>();
  private tail = this.head.next;

  private static makeNode<T>() {
    return new this.ListNode<T>(null, null);
  }

  private static ListNode = class ListNode<T> {
    constructor(
      public val: T | null,
      public next: ListNode<T> | null) {}
  };

  append(item: T): this {
    if (!this.tail) {
      this.head.next = {val: item, next: null};
      this.tail = this.head.next;
    } else {
      this.tail.next = {val: item, next: null};
      this.tail = this.tail.next;
    }
    return this;
  };

  remove(): T {
    if (!this.head.next || this.head.next.val === null) {
      throw Error();
    } else {
      const t = this.head.next.val;
      this.head.next = this.head.next.next;
      return t;
    }
  }
}

With this modified code, TypeScript can verify that instances of T are indeed returned by the remove() method. Upon hover-over, Visual Studio Code indicates that the type of head is ListNode<T>. How can this type be explicitly expressed?

Answer №1

Storing the type of the list node in a separate field doesn't seem necessary to me. It could simply be an object type instead of a class. Here's a more simplified approach: [example playground]

type Node<T> = {
  val: T;
  next: Node<T> | null;
};

class LinkedList<T> {
  private head: Node<T> | null = null;
  private tail: Node<T> | null = null;


  add(item: T): this {
    if (!this.tail) {
      this.tail = {val: item, next: null};
      this.head = this.tail;
    } else {
      this.tail.next = {val: item, next: null};
      this.tail = this.tail.next;
    }
    return this;
  };

  delete(): T {
    if (!this.head) {
      throw Error();
    } else {
      const t = this.head.val;
      this.head = this.head.next;
      return t;
    }
  }
}

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 is the best method for transitioning to a new page in React Native using Ignite Bowser?

Recently I ventured into the world of React Native with Ignite Bowser. My current project involves building a React Native app using Ignite Bowser. At the start of my app, there's a welcoming screen that pops up. It features a 'continue' bu ...

Ways to modify a mongoose find query to return null instead of throwing a CastError in case of incompatible types

Issue When utilizing mongoose to search for docs by a particular field, if the data type of the field differs from the data type of the queried value, mongoose will try to convert the query value to match the field's data type. However, if the conve ...

Determine the data type of an input field using PHP

Is there a way to determine the type of an input field text in order to make it read-only using PHP instead of JavaScript? I would like to confirm before proceeding with my implementation. Best regards. ...

Distinguishing variations within subcategories that stem from a common origin

In my code example, I have two interfaces that both extend a common base interface. The "String" function takes an argument of type "StringAsset". My expectation was that if I were to call the "String" function and pass it a value of "NumberAsset", TypeScr ...

A colleague's dependency is taking precedence over an NX Library

I'm working in a monorepo environment with nx, structured as follows: apps | - my-app libs | - common | - my-client After deployment, the libraries are published on npm with the names @my-org/my-client and @my-org/common. I have set up path ali ...

Abundant with whole numbers

I am facing a challenge converting a large decimal number to hexadecimal due to its size exceeding the capacity of an int datatype. An example of this issue can be seen with the statement: int a = pow(10,17); The use of a double datatype to handle this l ...

Changing true/false values to Yes or No in Angular array output

I am working with an array that is structured as follows: { "Tasks": [ { "TaskID": 303691, "TaskName": "Test1", "TaskType": "Internal", "Status": "Processing", "IsApproved": false, "RowNumber": 1 }, { ...

Retrieving information from the sessionStorage within app.module.ts

During the initialization of my application, it automatically redirects to the Login component. Here, I collect user data (username and password) and upon clicking the "Sign In" button, I send this information to the server. Upon receiving the Authorizatio ...

Is it possible to utilize the OnBlur prop based on a certain condition?

To display a component when the input is focused, follow the steps below: Click here for not focused state When you click on the text input, the component should appear like this: Click here for focused state The code snippet provided works correctly. ...

Is it possible to use a Jasmine spy on a fresh instance?

In need of assistance with testing a TypeScript method (eventually testing the actual JavaScript) that I'm having trouble with. The method is quite straightforward: private static myMethod(foo: IFoo): void { let anInterestingThing = new Interesti ...

node-ts displays an error message stating, "Unable to locate the name '__DEV__' (TS2304)."

I recently inserted __DEBUG__ into a TypeScript file within my NodeJS project. Interestingly, in VSCode, no error is displayed. However, upon running the project, I encounter an immediate error: error TS2304: Cannot find name '__DEBUG__'. I att ...

Setting the useState hook to a User type in React - the ultimate guide!

As someone new to hooks, I'm unsure about what the initial value for useState should be set to. Currently, an empty object is set as the default value for useState with const [user, setUser] = useState({}); This is causing ${crafter.id} to throw an e ...

The argument passed cannot be assigned to the parameter required

Currently, I am in the process of transitioning an existing React project from JavaScript to TypeScript. One function in particular that I am working on is shown below: const isSad = async (value: string) => { return await fetch(process.env.REACT_AP ...

How can I use a string variable in Angular 2 to create a dynamic template URL

@Component({ selector: 'bancaComponent', templateUrl: '{{str}}' }) export class BancaComponent implements OnInit { str: String; constructor(private http: Http) { } ngOnInit(): void { this.str = "./file.component.html"; } An ...

The positioning of Material UI InputAdornment icons is located beyond the boundaries of the TextField input area

I am struggling to understand why my InputAdornment is not positioned correctly. There doesn't seem to be any style in my code that would affect the location of the icon within the TextField (such as padding or flex properties). Currently, the calen ...

Converting JSON object to Typescript using type assertion in HTTP requests

I've been researching interfaces and type assertion. I've come across some helpful pages: Typescript parse json with class and interface How do I cast a json object to a typescript class (this one has nothing to do with TS!) Parse complex json ...

This browser does not recognize the tag <>. To render a React component, ensure its name starts with an uppercase letter

MyIcons.tsx export const ICONCAR = () => ( <span className="svg-icon svg-icon-primary svg-icon-2x"><svg xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" width="24px" height=&qu ...

I'm currently working on creating an online store using Next.js and TypeScript, but I'm struggling to effectively incorporate my fake product data array into the site

"using client" import Container from "@/components/Container"; import ProductDetails from "./ProductDetails"; import ListRating from "./ListRating"; import { products } from "@/utils/products"; interface I ...

Incomplete type 'unsigned char[]' error occurred during compilation with SUN C++ on a Solaris system

I encountered an issue with the declaration unsigned char tmp[]; as a member of a structure. Interestingly, when compiling with g++4 on a Linux Redhat system, there were no complaints. However, when attempting to compile with Sun C++ on a Solaris 5.10 mach ...

Challenges Faced with Implementing Active Reports in Angular 9

After following all the necessary steps outlined in this website to integrate Active Reports with Angular 9 (), I encountered an error when trying to compile my app: ERROR in The target entry-point "@grapecity/activereports-angular" has missing dependen ...