'Dynamic call of chalk with typescript
I am using typescript and want to call the chalk method dynamically, see my code :
import chalk from 'chalk';
const color: string = "red";
const message: string = "My Title";
const light: boolean = false;
const colorName = `bg${capitalize(color)}${light ? 'Bright' : ''}`;
console.log(chalk[colorName](message));
So, the color function takes as a value the color of the background of the message. The message is the content and the light is the boolean used to know if the background should have its bright version used.
The problem is that typescript throws this error :
Element implicitly has an 'any' type because expression of type 'any' can't be used to index type 'ChalkInstance'.
on the chalk[colorName]
part.
I already tried to put (chalk[colorName] as keyof ChalkInstance)
. And already looked at this post (which is the basically the same problem). Obviously, the solutions answered aren't working with my case.
Solution 1:[1]
I found the answer to my question myself. The problem was that the colorName
variable was of type any
and chalk couldn't recognize it as an index of the chalk
class. Here the value of the colorName
variable could contain all the value of the backgroun colors whether they were bright or not.
I then had two solutions :
- Hard code all the values of the colors (which would take a long time)
- Find the type representing the colors and explicit set as the type of the
colorName
variable
My solution is close from the second idea, however, the Chalk
library doesn't provide a type for the colors. However, we can import the explicit list of all the BackgroundColors
. Then, we only have to asign the type of these keywors as the type of the colorName
variable.
import { BackgroundColor } from 'chalk';
import chalk from 'chalk';
const color: string = "red";
const message: string = "My Title";
const light: boolean = false;
const colorName = `bg${capitalize(color)}${light ? 'Bright' : ''}` as typeof BackgroundColor;
console.log(chalk[colorName](message));
PS: Note that the type of the variabkes color
, message
and light
are hard-coded. On the application, these values are dynamic, that's a way to make it more clear.
Solution 2:[2]
There are a few things you need to do.
- Remove the redundant explicit types from your variables
color
,message
, andlight
. Doing so is not only unnecessary, it actually interferes with TypeScript properly inferring the literal value of those types. In other words, if you defineconst color = "red";
TypeScript will know, because it is aconst
, that it will never change and it can always be treated as the string literal"red"
instead of the more genericstring
. (As a general rule, you should never explicitly define the type of aconst
variable.)
const color = "red";
const message = "My Title";
const light = false;
- Make sure your
capitalize()
function properly defines its return type. In this case, there is actually an awesome built-in utility typeCapitalize<>
which you can use here. In combination with a TypeScript generic, you can define the function in such a way that TypeScript knows that e.g. if"red"
is what goes in,"Red"
is what comes out.
function capitalize<S extends string>(c: S) {
return c.replace(/\b\w/g, firstLetter => firstLetter.toUpperCase()) as Capitalize<S>;
}
- Use the
as const
assertion when you definecolorName
(and any other similar variables you may need to define). If you don't do this, the type ofcolorName
will be inferred asstring
, which is no good for indexingchalk
. Withas const
, you are basically telling TypeScript to treat the resulting expression as a literal string value. In this case, the type ofcolorName
becomes"bgRedBright" | "bgRed"
, both of which are valid indices ofchalk
.
const colorName = `bg${capitalize(color)}${light ? 'Bright' : ''}` as const;
^^^^^^^^
import chalk from 'chalk';
const color = "red";
const message = "My Title";
const light = false;
function capitalize<S extends string>(c: S) {
return c.replace(/\b\w/g, firstLetter => firstLetter.toUpperCase()) as Capitalize<S>;
}
const colorName = `bg${capitalize(color)}${light ? 'Bright' : ''}` as const;
console.log(chalk[colorName](message));
Edit: It's possible in the future that you would need to define your color
variable in such a way that it is dynamic and not just a hard coded literal "red"
value. In that case you need a way to satisfy TypeScript that color
will always be something that is valid given all the other inferences that we just set up.
Here, we actually do want to explicitly define a type for certain variables, particularly let
and function arguments. Fortunately, chalk
provides a very useful type for this case, ForegroundColor
which is essentially all of the valid "base" colors and happen to be compatible with BackgroundColor
when put into the form `bg${ForegroundColor`
, which is exactly what we need here.
import chalk, { ForegroundColor } from 'chalk';
let color: ForegroundColor = "red";
color = "blue";
We could even improve our capitalize()
function by more strictly controlling what the type of the argument can be:
function capitalize<S extends ForegroundColor>(c: S) {
return c.replace(/\b\w/g, firstLetter => firstLetter.toUpperCase()) as Capitalize<S>;
}
Another playground example that puts those further improvements into practice.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Lukas Laudrain |
Solution 2 |