I am working on an Angular library that will serve as an API client. The issue I'm running into is that some of the consuming applications are using an HttpInterceptor to automatically convert date strings into Javascript Date objects while others just use the string value passed in the response.
I currently have these properties typed as string | Date
but I would really like a way to have the union type narrowed to either string
or Date
based on which application is consuming the library.
I first tried declaring a type alias type DateType = string | Date
and hoped I could override it using a declare global
block in the consuming application but that didn't work.
I also considered defining an interface interface DateType extends String
and then overriding that in the consuming application but I abandoned that route as I don't want to have the consuming applications have to manage the conversion between the String object and primitive.
TypeScript doesn't support arbitrary modifications to existing types. In particular you can't modify type aliases. But TypeScript does support certain type modifications through declaration merging, which you can use (or abuse) to get the behavior you're looking for.
Essentially you can "re-open" an interface declaration and add properties to it. If the library's types are defined like this:
interface DateTypeConfig {
[x: string]: string | Date
}
type DateType = DateTypeConfig["type"]
declare function acceptDate(date: DateType): void;
declare function produceDate(): DateType;
declare function modifyDate(date: DateType): DateType;
Then DateType
is an indexed access into the type
property of the DateTypeConfig
interface. Now the DateTypeConfig
interface doesn't currently have a specific type
property, but it does have a string
index signature of type string | Date
, so to start with, DateType
is string | Date
.
So by default, a consuming app would see this union.
const d = produceDate();
// const d: string | Date
const m = modifyDate(d);
// const m: string | Date
acceptDate(m);
But the consuming app could re-open the DateTypeConfig
interface and add an explicit type
property whose type is some subtype of string | Date
. (If this is in a module or your library is in a module you might need to use global
augmentation or module
augmentation to get the right namespace.)
For example:
interface DateTypeConfig {
type: Date;
}
And then automatically, the DateType
type alias becomes Date
instead of string | Date
:
const d = produceDate();
// const d: Date
const m = modifyDate(d);
// const m: Date
acceptDate(m);
Or instead the app could do this:
interface DateTypeConfig {
type: string;
}
and then DateType
would be string
:
const d = produceDate();
// const d: string
const m = modifyDate(d);
// const m: string
acceptDate(m);