5 features I wish TypeScript had

If you are coming from another programming language you might feel some features are missing from Typescript

5 features I wish TypeScript had

TypeScript is one of the newer programming languages out there, improving on Javascript by adding static type safety among other nice features. It is quite loved by its users with the most recent StackOverflow developer survey placing it on number 3 as the most loved programming language. It is also gaining traction among projects worldwide with the same StackOverflow survey placing it as the number 5 most used programming language while a similar survey from Jetbrains showing it at number 8.

I personally think TypeScript is a great language and quite a pleasure to work with, still if you are coming from another programming language you might feel some features are missing. I mostly worked in the JVM/Java world before starting with TypeScript so here is my perspective on 5 features that I would like the language to have and are not there yet.

The final keyword

untitled-6

Maybe one of the most requested features out there (judging by the number of comments and upvotes on the open TypeScript issues) and also one of the most jarringly missing from the language. It is present in both C++ and Java, arguably the most popular OOP languages.

If you are not familiar with it from other languages then here is a short explanation: The final keyword can be applied to both class declarations and class methods and has the following effect:

  • When applied to a class declaration it informs the compiler(and the programmers reading the code) that no other class should extend from that class. This is useful in expressing that a class is not designed to be extended and it effectively disallows anyone from doing so, otherwise they will be hit with a compiler error. Because of the vast overuse of inheritance over the years, it has actually been recommended in the languages that do support this feature to use it for all class declarations unless there is a very good reason not to. That advice has been followed even more closely by some new programming languages(e.g. Kotlin) that make new classes by default non-extendable.
  • When applied to a method it tells the compiler to not allow any attempt of overriding that method in a derived class. This follows the same principle as above where the programmer should be explicit about the intent of extensibility: by default mark the methods as final unless the explicit intent is to allow extensibility
public final class Parent {}

// compiler error: Cannot inherit from final `Parent`
public class Child extends Parent {} 

For now, the decision seems to be that this feature will not be implemented. If you want it then make sure to have your voice heard by commenting or upvoting on the Github issue.

Rich enums

While enums are present in a lot of languages, allowing you to declare a type with a limited set of values, Java enums are richer in features than what is usually offered in most programming languages. I wrote an entire post about what you can do with enums in Java, but to summarize they allow you to define methods and fields for the enum types. Basically, it allows enums to represent type declarations as powerful as a typical class type declaration while limiting the number of possible instances and enforcing those at compile time. This allows a couple of interesting things for example implementing Strategy or State patterns.

If this sounds too abstract a simple example:

enum Team{
    BARCELONA("Lose"){
        @Override
        public final void play() {
            //prints "Lose"
            System.out.println(result);
        }
    },
    REAL("Win"){
        @Override
        public final void play() {
            //prints "Win"
            System.out.println(result);
        }
    };

    public final String result;
    public abstract void play();

    Team(String result){
        this.result = result;
    }
}

In TypeScript this is not possible, enums are just a set of values.

Still, you can actually do something similar using a pattern called pseudo-enums. You can declare a normal class, mark its constructor private and expose different instances of that class as public static fields.

class Team {
  static readonly BARCELONA = new Team(
    'Lose'
  );
  static readonly REAL = new Team(
    'Win'
  );
 
  private constructor(
    public readonly result: string,
  ) {}

  public play() {
    console.log(this.result);
  }
}

Another thing that you can do is add static methods to enums by using declaration merging

Reflection and annotations

In the JVM world, reflection and annotations are at the heart of most frameworks. The Java reflection API allows you at runtime to introspect the structure of your program. You can get all kinds of metadata about classes(e.g. what are the public methods of the class, how many private fields it has, etc.), methods(number and the type of parameters, the return type, etc.) and even more. Annotations allow you to add extra metadata to your program that you can then also access using the reflection API.

@Deprecated
public class Person {
    private String name;
    private int age;
}

public void reflectionExample() {
    Object person = new Person();
    //will contain @java.lang.Deprecated()
    Annotation[] annotations = person.getClass().getAnnotations();
}

This is the magic behind frameworks like Spring which provide such annotations and once classes, fields or methods are enhanced with them, it will know what to do behind the scenes. Even tools like IDEs make use of reflection. For example, Jetbrains Intellij makes extended use of reflection for its autocompletion feature. You can check a quite simple example of reflection in action by checking the code of my open-source Intellij plugin.

Sadly in TypeScript this is just not a thing, one of the reasons being that the code transpiles to Javascript which is missing a lot of the static type information present in TypeScript. There is some level of support for it by using decorators. Decorators look like annotations, but be warned that they are actually functions that wrap the annotated body and have only experimental support at this moment and are not enabled by default in TypeScript. If you are interested in how you can partially simulate the rich reflection capabilities present in the JVM, you can look up more details about the reflect-metadata project.

Nominal types

Even though TypeScript provides static typing, it may not be exactly what you are expecting if you are coming from C++ or Java. TypeScript has a structural type system which means that type compatibility is achieved based on the members of the type aka the structure of the type. The nominal type system is the one you are more familiar with if you have a Java or C++ background where the type compatibility is based on the explicit name of the types.

Structural type system in TypeScript

interface Jedi {
  name: string;
}
interface Sith {
  name: string;
}

let jedi: Jedi = { name : "Anakin" };
// OK, because of structural typing
let sith: Sith = jedi;

Nominal type system in Java

public class Jedi{
    String name;
    
    Jedi(String name){ 
        this.name = name
    };
}

public class Sith{
    String name;
    
    Sith(String name){ 
        this.name = name
    };
}

Jedi jedi = new Jedi();
// NOT OK, because of nominal typing
Sith sith = jedi;

I won't go into a full analysis of structural vs nominal type systems here(maybe in a future article) but I think it is fair to say both have advantages and disadvantages. In broad terms, structural is more flexible, while nominal is safer. I would like to have an option to choose between the two when declaring a type. There are some tricks for simulating nominal types in TypeScript, but they have disadvantages compared to languages that fully support nominal types. There are languages like Scala that do provide both structural and nominal types as an option to the programmer.

The throws keyword

This may seem controversial since it looks like I am proposing something similar to checked exceptions in Java which are not really loved by a lot of people. But you can put your pitchforks down, I am actually thinking of something a lot tamer. A way for a developer to declare in code what errors a function might throw, but without enforcing calling functions to catch those types of errors. I found an issue proposing exactly that in the TypeScript repo.

This would look something like this:

//declare a custom error
class MyError extends Error { }

//specify in the function declaration that it might throw a specific error
function fn(): string throws MyError { }

The override keyword(apparently I am too late for this one)

I wanted to put on the list also the fact that the language is missing something similar to the @override annotation in Java which informs the compiler that a method is supposed to override another method from up in the inheritance chain. This way the compiler can throw an error if a mistake is made(e.g. wrong number of parameters, typo in the name of the function, wrong parameter types) and the new method doesn't actually do an override.

But relatively recently TypeScript actually implemented this. Starting with version 4.3 you can use the override keyword when declaring a method and the compiler will do the check that indeed the new method is doing an override.

class Parent{
    ...
    public doSomething(): void { ... };
}

class Child extends Parent{
    ...
    // compiler error because of the typos in the name of the function
    public override doSometin(): void { ... } 
}