I started this quest a couple hours ago by trying to extend the built-in Window interface to support my globals:
export interface IWindowWithMyStuff extends Window {
foo: string
}
And when I tried to use the new interface:
const w = window as IWindowWithMyStuff
I receive error:
TS2352: Conversion of type Window & typeof globalThis to type IWindowWithMyStuff may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to unknown first. Property foo is missing in type Window & typeof globalThis but required in type IWindowWithMyStuff
And so began my search to figure out what I was doing wrong. I've read this question, including the comment from Mr. Cavanaugh and I still don't understand things well enough to get it. Most of the guidance seems to suggest (or flat-out assert) that declaring a global interface augmentation is the way. But I prefer a different named interface, to me it's much more readable and clear what the intent is.
I get it, that "my way" seems to be impossible, but can someone explain to me like a noob (because I pretty much am) why it's not possible? I don't doubt the answer is in the aforementioned questions and comments, but it likely "whooooshed" right past me.
In short, you can. The error does not implicate the interface itself, but rather the way you are casting it. The compiler is weary about this sketchy cast and informs you how to beat it into submission:
If this was intentional, convert the expression to unknown first.
const w = window as IWindowWithMyStuff; // NOT OK
const w = window as unknown as IWindowWithMyStuff; // OK
Most of the guidance seems to suggest (or flat-out assert) that declaring a global interface augmentation is the way.
This is for good reason. If you wish to use the window
singleton to expose APIs, documenting those properties with declare global
is the responsible thing to do.
declare global {
interface Window {
readonly foo: string;
}
}
Object.defineProperty(window, "foo", {
value: "bar",
writable: false
});
Mandating that consumers cast the window
singleton to some intermediary type does not make much sense. The window
singleton can either implement your custom properties or it can not. If it does, then all objects of type Window
do, assuming you're not doing something very strange.
Perhaps a hybrid approach would be satisfactory. In the following example, the cast to unknown
is not required as the properties are known to sometimes exist in Window
, we are just asserting that they actually do.
interface YourAPI {
readonly foo: string;
}
declare global {
interface Window {
// Remove the ? if you don't want to actually
// require the cast; just inherit from YourAPI
[K in keyof YourAPI]?: YourAPI[K]
}
}
const w = window as YourAPI; // OK