Use Cases For TypeScript Discriminated Union Types and Generics

Use Cases For TypeScript Discriminated Union Types and Generics

Sunny Sun Lv4

Enhancing Your Refactoring Skills with Practical Examples Using TypeScript’s Discriminated Union Types and Generics

What I love about TypeScript is its Type system. It is practical and feature-rich. Applying the types in the right use cases can make our code cleaner and easier to maintain.

This article aims to share an example of code refactoring with TypeScript discriminated union types and Generics.

The original code

This piece of code represents user roles and feature/permissions mapping, which is used to build a user access matrix in an Angular App.

The code above contains the following:

  1. FeatureAccess: A constant representing two features (“UserManagement” and “Order”) and their permissions.
  2. SalesRole: A Role class that describes the list of permissions a Sales user has and “canAccess” method based on that.

Issues

What are the problems with the above code?

Firstly, the return type of functions Getter in SalesRole Class is not strong enough. In the IUserPermission interface, the permissions are a string array. Although it is clearly stated in FeatureAccess constant that each feature has different permission (i.e. Order feature does not have Create/Delete Permission), if someone adds a “Create” permission into the SalesRole functions Getter, the compiler will not complain.

Secondly, the code is not DRY. There are many repeated “namespace” codes like “FeatureAccess.UserManagement.Permissions.xxx”, it is used to reduce the chance of incorrectly adding wrong permission into a role function list. In a real-world project with lots of roles and permissions, it makes the code hard to read.

My first attempt to refactor the code is to use the Discriminated union type.

Discriminated union type

A discriminated union type differentiates the types from each other via a property called discriminator. The power of discriminated type is that the additional type checking is enforced at compile time and also carries the type info into run time.

By applying the discriminated union type, the code is refactored as follows.

As the above code shows, the new UserTypes and OrderTypes are union types that represent the allowed permissions for each feature. The new functionList type is a discriminated union type that enforces the relationship between the feature and its permissions.

Now, if someone adds a Create permission to the Order feature, the compiler will not be happy.

The repeat “namespace” code is removed because we don’t have to use the naming conversion to make it “safer,” and instead, with much better compiler type checking, we can use the simple PermList constant and not need to worry about adding the wrong permission into the role function list.

As Const

Another thing worth highlighting is the “Const Assertion” being used here.

The as const is the const assertion expression. It has two effects, make all the properties readonly and prevent the string literal widening. The latter is an often forgotten feature but can be super useful sometimes. For example, without this magic expression, the PermList.View will be a string instead of a literal “View” type, then the whole discriminated type won’t work for this case.

And it is not the end. We can improve the code further by using Generics in TypeScript.

Generics

You may already notice that the IUserFunctions and IOrderFunctions are similar. They all have the “name” and “permissions” properties. Again in the real world, those interfaces can grow quickly as well as duplicates.

As you can see, it is much cleaner after the Generics is applied.

Conclusion

The TypeScript Type system is powerful. This use case touches the surface, and I hope it can trigger your interest in exploring it further.

Happy programming!

  • Title: Use Cases For TypeScript Discriminated Union Types and Generics
  • Author: Sunny Sun
  • Created at : 2020-02-28 00:00:00
  • Updated at : 2024-08-16 19:46:17
  • Link: http://coffeethinkcode.com/2020/02/28/a-practical-usage-of-typescript-discriminated-union-type-and-generics/
  • License: This work is licensed under CC BY-NC-SA 4.0.