How to Share Constants in Typescript Project
Avoid the magic strings, and make your app maintainable and strongly typed
We often need to share common values between classes and modules. These values can be error codes, status indicators, environment settings, configuration values, etc. It is a best practice to store those values in constants, instead of hard-coding magic strings. The use of constants also helps to avoid duplicating those values in multiple places, improve readability and maintainability, and enforce type safety .
In this article, I will discuss two common approaches to managing constants in a TypeScript project.
Use Class with static properties
One way to manage constants in TypeScript is to use the class. In the following example, the AppSettings class is created with static properties to define constants. Those properties are applied with readonly modifiers, thus they can not be reassigned.
1 | export class AppSettings |
To use the defined constants, we just need to import the class and directly reference the class and property name as below.
1 | import {AppSettings} from "./appsetting.ts"; |
The class approach is simple and serves the purpose. But it has a couple of limitations:
The readonly modifier only works at compile time, it has no effect at runtime.
As we only need the static properties, the overhead associated with Class is unnecessary.
A better approach is to use const.
const keyword
The const keyword makes a variable a constant, so its value can’t be modified. Compared with the class approach, the advantage of const is that it makes variables immutable in both compile time and run time.
export const payGrades = {
low: "1",
average: "2",
high: "3"
} as const;
In this example, we use the TypeScript feature “as const”. “as const” is a TypeScript construct for literal values called const assertion . It does two things:
Apply readonly modifier to properties
Tell the compiler not to widen the literal types.
An additional benefit that comes with the use of const assertion is that we can derive types from the declared constants.
export const payGrades = {
low: "1",
average: "2",
high: "3"
} as const;
type t = typeof payGrades;
type payGradeType = keyof t; // 'low' | 'average' | 'high'
type payValueType = t[keyof t]; // '1' | '2' | '3'
const hisPay: payValueType = '3'; //okay
const myPay: payValueType = '4'; // error
The derived types will be useful in applying type constraints.
1 | calculateSalary(payGrade: string) // payGrade can be any string |
The above example illustrates how to use the types derived from constants to enforce type safety.
Multiple Constants Files and
For a small app, a single global constants file will be sufficient. Below is an example
1 | // constants.ts |
However, for a large app, the number of required constants may grow quickly. Some of the constants may be applicable only to a particular module. One global constants file won’t be ideal for this scenario.
Thus for large apps, it is better to create multiple constants files, one for each module. For example, you might create a constant file for component constants, another file for database query constants, and so on.
Another way to organize different categories of constants is to use namespaces. Using the Typescript namespace allows you to group related constants and access them using dot notation. For example, we defined two namespaces: Report and Database, each with related constants.
1 | // constants/report.ts |
Using namespace also helps prevent naming conflicts since constants within a namespace are scoped to that namespace and are not accessible outside.
Generate types using typeof
When there are multiple constants files, we may need to extract and combine a few constants from different files. The following example shows how to take two constants from two different modules, and generate a union type from them using typeof type operator.
// Tech Module Constants
export const TechStaffPayGrades = {
low: "T1",
average: "T2",
high: "T3"
} as const;
// Admin Module Constants
export const AdminStaffPayGrades = {
low: "A1",
average: "A2",
high: "A3"
} as const;
// import both constants file
type allPayGrades = typeof TechStaffPayGrades | typeof AdminStaffPayGrades;
type allPayValues = allPayGrades[keyof allPayGrades]; //"T1" | "T2" | "T3" | "A1" | "A2" | "A3"
The benefit of this approach is that we avoid duplication. At the same time, we can apply the derived type constraints to achieve better type safety . The beauty of this method is that the type is automatically updated when a new constant is added to one of the constants files.
Summary
In this article, we walk through different ways to manage constants in TypeScript App.
Using const to define constants has several advantages compared with using static properties of Class. Organizing the constants in a logical and modular way will improve the readability and maintainability of your code base. Creating shared constants files will prevent magic strings, and the derived types can also help build a strongly typed application.
Happy Programming!
- Title: How to Share Constants in Typescript Project
- Author: Sunny Sun
- Created at : 2021-07-25 00:00:00
- Updated at : 2024-08-16 19:46:17
- Link: http://coffeethinkcode.com/2021/07/25/how-to-share-constannts/
- License: This work is licensed under CC BY-NC-SA 4.0.