The transition from C# .NET backend development to the world of frontend web development was a significant one for me. What made this journey smoother and more enlightening was the embrace of a heavily typed programming language: Typescript.

Over the years, my attachment to Typescript has deepened, leading me to explore the many facets of the language, especially its handy utility types. Today I would like to introduce you to one of my favourite utility types which also has become an integral part of my programming toolkit: Record<Keys, Type>. Let's dive into its areas of application and see how it offers a refreshing alternative to traditional coding practices.

My experience with Typescript has been so enriching that even after all these years, I still fondly remember my beginnings with this robust language. So it's no wonder that I set out to explore all facets of Typescript, especially its fascinating utility types.
Learn more about Typescript Utility Types here.

Since it is impossible to list the sheer power of these utility types in a short list and go through the endless possibilities they offer, I want to start small and pick one type and one use case that I really like and use frequently.

So my Typescript utility type of the day is Record<Keys, Type>.

Replacing switch case statements

My favourite use case is replacing the controversial switch case statements.
So how does this work? And why should we do it?

Let's consider an example. Imagine a specific enum and a class structure.

enum ExportType {
  html,
  csv,
  pdf,
}
abstract class ExportService {
  public abstract export(): void;
}

class HtmlExportService extends ExportService {
  export(): void {
    throw new Error('Method not implemented');
  }
}

class CsvExportService extends ExportService {
  export(): void {
    throw new Error('Method not implemented');
  }
}

class PdfExportService extends ExportService {
  export(): void {
    throw new Error('Method not implemented');
  }
}

If we now want to create a function to export to different files, it might look something like this with a switch case:

function exportData(exportType: ExportType): void {
  switch (exportType) {
    case ExportType.html:
      new HtmlExportService().export();
      break;
    case ExportType.csv:
      new CsvExportService().export();
      break;
    case ExportType.pdf:
      new PdfExportService().export();
      break;
    default:
      throw new Error('Not supported!');
  }
}

So there are several things that I don't like. First of all, we have to write a lot of repetitive code. Basically, we want to call the same export method for each of the corresponding services. That could definitely be shortened a bit. The second thing that immediately sticks out is the default condition. Yes, it's great if someone extends the enum, or maybe it's defined in the backend and automatically generated in the frontend client, but then again, we might not even see when there's a new entry and or forget to add the according case statement. The user will then experience the error we're throwing here (hopefully we have that sorted somewhere else in our application).

Wouldn't it be better if we could identify such problems in the development phase rather than at runtime?

Introduction of Typescript Records

To solve these problems, we can use the power of Typescript Records. Here's how:

Set up the record:
First off, we need to create the Record and set up the mapping. In our case we could use a ExportService Factory.

const EXPORTS: Record<ExportType, new () => ExportService> = {
  [ExportType.html]: HtmlExportService,
  [ExportType.csv]: CsvExportService,
  [ExportType.pdf]: PdfExportService,
};

It's actually not that hard and looks pretty neat. It is also quite easy to expand as we can keep it in one place and do all the mapping.

Refine the exportData function:

function exportDataOptimized(exportType: ExportType): void {
  new EXPORTS[exportType]().export();
}

Wow, this is really neat compared to the huge switch-case statement we had to maintain before. We no longer have to worry about which export type does what in the exportData function, we just have to export the data and let the right service do the work for us. We got rid of all the repetitive code and saved a lot of lines of code.

Catching Errors Early

One of the outstanding features of records is the type safety that allows us to find errors easily during development. Every key in a record must be defined. So when we introduce a new entry into the enumeration, we are immediately warned with a compile error. This immediate feedback allows us to fix potential problems long before they reach runtime.

In Conclusion

Typescript's utility types, specifically Record<Keys, Type>, provide a compelling toolkit for developers seeking cleaner, more efficient code. By favouring Records over traditional switch-case statements, we not only improve code maintainability but also minimise errors and ensure a seamless user experience.

I always look forward to exploring such topics in more depth and discussing new ideas. If you share a similar enthusiasm or have any questions, please don't hesitate to reach out to me at hannes@guidnew.com or connect with me on LinkedIn. I look forward to our enriching discussions!

Thank you for your time and continue to explore our insights on Guid.New.