Declaring dynamic prototype methods to typescript - javascript

I'm converting a javascript class with several "generated" prototype methods to typescript. The generated methods are mapped to an internal object so the API is cleaner/more convenient for 80% of its use cases.
However, I'm find no way to properly tell typescript which methods exist without actually implementing them.
class A {}
A.prototype.test = function() {}
var a = new A().test();
It errors with error TS2339: Property 'test' does not exist on type 'A'
It seems like I can get around it by defining the property manually, but that hinders the usefulness of automatically mapping these methods.
class A {
test: any;
}
A.prototype.test = function() {}
var a = new A();
a.test();

However, I'm find no way to properly tell typescript which methods exist without actually implementing them.
If the methods are generated why even define the class? i.e. instead of class A you really should only be declaring the class i.e. you should be doing:
declare class A {
test();
}

I can get around it by defining the property manually
Yes. Because of the dynamic part, the type cannot be statically inferred by TypeScript. It has to be manually defined:
class A {
test: () => void;
}
A.prototype.test = function() {}
Or, another way that I use sometimes:
Dynamically build the API in TypeScript module files, that export some members;
Compile to JavaScript code;
Write a file index.d.ts that declares the exported module members of the API (like the answer of #basarat);
In a separated project (with another tsconfig.json), import the module members of the API, from the JavaScript code, with the help of the declarations in index.d.ts.
This process is particularly suited to publish a npm package.

Related

When should we load the function, if we want to add a function to a prototype using React?

I am trying to add a toCamelCase function to the String prototype in React with TypeScript.
Here is what I did, in a toCamelCase.d.ts file:
interface String {
toCamelCase(): string;
}
String.prototype.toCamelCase = function (): string {
return this[0].toUpperCase() + this.slice(1).toLowerCase();
};
Now I am just wondering when and in which file should I load this script so I can get access to it.
I am aware that I can simply define a function and use that.
However, I am curious how to do it with prototype and what would be the downside doing things like this if there are any.
Where to do it?
In main.js or index.js or any other file that bootstraps your application
What are the downsides?
You can never know if one of the libraries that you use using relies on String.prototype being unaltered. It can cause major issues that you will have a hard time finding.

How to reference `window` variable in a mixed Javascript/Typescript project [duplicate]

I setup global namespaces for my objects by explicitly setting a property on window.
window.MyNamespace = window.MyNamespace || {};
TypeScript underlines MyNamespace and complains that:
The property 'MyNamespace' does not exist on value of type 'window'
any"
I can make the code work by declaring MyNamespace as an ambient variable and dropping the window explicitness but I don't want to do that.
declare var MyNamespace: any;
MyNamespace = MyNamespace || {};
How can I keep window in there and make TypeScript happy?
As a side note I find it especially funny that TypeScript complains since it tells me that window is of type any which by definitely can contain anything.
I just found the answer to this in another Stack Overflow question's answer.
declare global {
interface Window { MyNamespace: any; }
}
window.MyNamespace = window.MyNamespace || {};
Basically, you need to extend the existing window interface to tell it about your new property.
To keep it dynamic, just use:
(<any>window).MyNamespace
Note that this may not work with TSX because the compiler might think that the <any> is a TSX element. Check out this answer for type assertion that is compatible with TSX.
Using Svelte or TSX? None of the other answers were working for me.
Here's what I did:
(window as any).MyNamespace
As of TypeScript ^3.4.3, this solution no longer works
Or...
you can just type:
window['MyNamespace']
And you won’t get a compile error and it works the same as typing window.MyNamespace.
Globals are "evil" :) I think the best way to also have the portability is:
First you export the interface: (for example, ./custom.window.ts)
export interface CustomWindow extends Window {
customAttribute: any;
}
Second, you import
import {CustomWindow} from './custom.window.ts';
Third, cast the global variable window with CustomWindow:
declare let window: CustomWindow;
In this way you also don't have a red line in a different IDE if you use it with existent attributes of the window object, so at the end try:
window.customAttribute = 'works';
window.location.href = '/works';
Tested with TypeScript 2.4.x and newest!
For those using the Angular CLI, it's straightforward:
File src/polyfills.ts
declare global {
interface Window {
myCustomFn: () => void;
}
}
File my-custom-utils.ts
window.myCustomFn = function () {
...
};
If you're using IntelliJ IDEA, you also needed to change the following setting in the IDE before your new polyfills pick up:
> File
> Settings
> Languages & Frameworks
> TypeScript
> check 'Use TypeScript Service'.
The accepted answer is what I used to use, but with TypeScript 0.9.* it no longer works. The new definition of the Window interface seems to completely replace the built-in definition, instead of augmenting it.
I have taken to doing this instead:
interface MyWindow extends Window {
myFunction(): void;
}
declare var window: MyWindow;
UPDATE: With TypeScript 0.9.5 the accepted answer is working again.
If you need to extend the window object with a custom type that requires the use of import, you can use the following method:
window.d.ts
import MyInterface from './MyInterface';
declare global {
interface Window {
propName: MyInterface
}
}
See Global Augmentation in the 'Declaration Merging' section of the Handbook.
Create a file called global.d.ts, e.g., /src/#types/global.d.ts, and then define an interface like:
interface Window {
myLib: any
}
Reference: Global .d.ts
Most of the other answers are not perfect.
Some of them just suppress the type inference for show.
Some of the others only care about global variables as namespaces, but not as interfaces/classes
I also encountered a similar problem this morning. I tried so many "solutions" on Stack Overflow, but none of them produced absolutely no type errors and enabled triggering type jumping in the IDE (WebStorm or Visual Studio Code).
Finally, from Allow module definitions to be declared as global variables #3180
I found a reasonable solution to attach typings for a global variable that acts as interface/class and namespace both.
The example is below:
// typings.d.ts
declare interface Window {
myNamespace?: MyNamespace & typeof MyNamespace
}
declare interface MyNamespace {
somemethod?()
}
declare namespace MyNamespace {
// ...
}
The code above merges the typings of namespace MyNamespace and interface MyNamespace into the global variable myNamespace (the property of window).
I don't need to do this very often. The only case I have had was when using Redux DevTools with middleware.
I simply did:
const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
Or you could do:
let myWindow = window as any;
And then myWindow.myProp = 'my value';
From the version 3.4, TypeScript has supported globalThis. See Type-checking for globalThis.
From the above link:
// in a global file:
var abc = 100;
// Refers to 'abc' from above.
globalThis.abc = 200;
window.abc = 300; // window object can also be used.
Playground
A "global" file is a file which does not have any import/export statements. So the declaration var abc; can be written in .d.ts.
After finding answers around, I think this page might be helpful:
Global augmentation
I am not sure about the history of declaration merging, but it explains why the following could work.
declare global {
interface Window { MyNamespace: any; }
}
window.MyNamespace = window.MyNamespace || {};
Using create-react-app v3.3 I found the easiest way to achieve this was to extend the Window type in the auto-generated react-app-env.d.ts:
interface Window {
MyNamespace: any;
}
TypeScript does not perform typechecking on string properties.
window["newProperty"] = customObj;
Ideally, the global variable scenario should be avoided. I use it sometimes to debug an object in the browser console.
If you are using TypeScript 3.x, you may be able to omit the declare global part in the other answers and instead just use:
interface Window {
someValue: string
another: boolean
}
This worked with me when using TypeScript 3.3, Webpack and TSLint.
Using
window["MyNamespace"] = window["MyNamespace"] || {};
should be all right as it is using a string property, but if you really want to have a separated window and organised your code, you can extend the window object:
interface MyNamespacedWindow extends Window {
MyNamespace: object;
}
declare var window: MyNamespacedWindow;
Here's how to do it, if you're using TypeScript Definition Manager!
npm install typings --global
Create typings/custom/window.d.ts:
interface Window {
MyNamespace: any;
}
declare var window: Window;
Install your custom typing:
typings install file:typings/custom/window.d.ts --save --global
Done! Use it! TypeScript won't complain any more:
window.MyNamespace = window.MyNamespace || {};
// In typings.d.ts(is Global)
export declare global {
interface Window {
__PUBLIC__: string;
}
}
First you need to declare the window object in the current
scope. Because TypeScript would like to know the type of the
object. Since the window object is defined somewhere else, you can not redefine it.
But you can declare it as follows:
declare var window: any;
This will not redefine the window object nor will it create another variable with name window. This means window is defined somewhere else and you are just referencing it in the current scope.
Then you can refer to your MyNamespace object simply by:
window.MyNamespace
Or you can set the new property on the window object simply by:
window.MyNamespace = MyObject
And now the TypeScript won’t complain.
For reference (this is the correct answer):
Inside a .d.ts definition file
type MyGlobalFunctionType = (name: string) => void
If you work in the browser, you add members to the browser's window context by reopening Window's interface:
interface Window {
myGlobalFunction: MyGlobalFunctionType
}
The same idea for Node.js:
declare module NodeJS {
interface Global {
myGlobalFunction: MyGlobalFunctionType
}
}
Now you declare the root variable (that will actually live on window or global):
declare const myGlobalFunction: MyGlobalFunctionType;
Then in a regular .ts file, but imported as side-effect, you actually implement it:
global/* or window */.myGlobalFunction = function (name: string) {
console.log("Hey !", name);
};
And finally use it elsewhere in the codebase, with either:
global/* or window */.myGlobalFunction("Kevin");
myGlobalFunction("Kevin");
Full & Short answer
1- Add typeRoots property to tsconfig.json:
{
"compilerOptions": {
...
"typeRoots": ["src/#types", "node_modules/#types"]
...
}
}
2- Extend Window type
// file: src/#types/global.d.ts
declare global {
interface Window { customProperty: <type>; }
}
[2022]:We have to extend the "window" object in our React or Nextjs project .
We can use the following step to solve this issue.
Make a folder inside src folder name as types.
Make a file inside types folder name as index.d.ts
write this code inside index.d.ts file.
export {};
declare global {
interface Window {
NameSpace: any;
}
}
window.NameSpace= window.NameSpace|| {};
save this file.
Now one last change .
Change the "tsConfig.json" file. to inherit both the node module type and our types.
{
"compilerOptions": {
...
"typeRoots": [
"./node_modules/#types",
"./src/types"
],
....
}
Make a custom interface that extends the Window and add your custom property as optional.
Then, let the customWindow use the custom interface, but valued with the original window.
It's worked with the TypeScript 3.1.3.
interface ICustomWindow extends Window {
MyNamespace?: any
}
const customWindow:ICustomWindow = window;
customWindow.MyNamespace = customWindow.MyNamespace {}
For those who want to set a computed or dynamic property on the window object, you'll find that not possible with the declare global method. To clarify for this use case
window[DynamicObject.key] // Element implicitly has an 'any' type because type Window has no index signature
You might attempt to do something like this
declare global {
interface Window {
[DyanmicObject.key]: string; // error RIP
}
}
The above will error though. This is because in TypeScript, interfaces do not play well with computed properties and will throw an error like
A computed property name in an interface must directly refer to a built-in symbol
To get around this, you can go with the suggest of casting window to <any> so you can do
(window as any)[DynamicObject.key]
(window as { test: string } & Window & typeof globalThis).test = `Hello World`;
I wanted to use this in an Angular (6) library today and it took me a while to get this to work as expected.
In order for my library to use declarations, I had to use the d.ts extension for the file that declares the new properties of the global object.
So in the end, the file ended up with something like:
/path-to-angular-workspace/angular-workspace/projects/angular-library/src/globals.d.ts
Once created, don't forget to expose it in your public_api.ts.
That did it for me.
TypeScript prevents accessing an object without assigning a type that has the desired property or
already assigned to any, so you can use optional chaining:
window?.MyNamespace = 'value'
I use without declarate.
My example looks like:

Class.name always 'e' for every class after uglify / webpack for production

I have an app which is working great in development enviroment but it is not working in production, which is caused by uglify (I think so)
I have a data which user builds and I am saving that either to file or to LocalStorage (json in both cases so doesn't matter).
The structure is built from 3 type of nodes. I have implemented property in the base class ( all inherit from one class): type =this.constructor.name and it is working great in development. When I load the app and read the cache, I go through the JSON and rebuild objects using switch (obj.type) case class1.name... etc. It is working well.
However, when I build for production, when I call class1.name or class2.name or class3.name it all returns e which makes it impossible to restore proper objects...
I do not think it is framework specific issue, but if someone would need to know I build using VueJS with Quasar Framework.
Any ideas?
constructor.name or any other function name property should never be relied in client-side JavaScript, exactly because minification is a must in production environment, and functions with meaningful names become one-letter named functions. e is a common name for a function minified with UglifyJS. This is the reason why uglified JS file has much lesser footprint than unminified file.
If function/class names are used for anything but debugging, they should be explicitly specified as static properties. Since name cannot be redefined in some engines, non-standard displayName (can also be useful for debugging) or any other property name can be used:
class Foo {
static get id() { return 'Foo' }
...
}
Or:
class Foo {
...
}
Foo.id = 'Foo';
For whoever comes after
Webpack uses UglifyJS to compress and hide aspects of your code (referred to as 'mangle'), with regards to your question specifically, it transforms all your classes to e, you have to specific either in your webpack config or cli to not do this if you'd like to preserve your classnames and/or function names.
You can disable this behavior by removing the minimization or creating your own optimization instance as shown here
Depending on your goal you can also use the below approach. In my case I just needed to be able to distinguish between class types so the below was sufficient:
class MyClass {
constructor(){
this.createClassId()
}
createClassId(){
if(!this.constructor.prototype._customClassId){
this.constructor.prototype._customClassId = uuidv4() //you choose what you want this _customClassId to be. Or have a look at nanoId which can produce shorter names
}
}
then new MyClass()._customClassId would be the same for all instances of the same class. And if, for example, you inherit from MyClass, the subclasses will have the same _customClassId month all instances of subclasses but it will be different from what the parent class holds.
If you need to control what exactly _customClassId should be for each class, you can do it as well, though it will require a bit more setup using inheritance like below:
class MyBaseClass {
constructor(){
this.createClassId()
}
createClassId(){
if(!this.constructor.prototype._customClassId){
this.constructor.prototype._customClassId = this.getCustomClassName() //you choose what you want this _customClassId to be. Or have a look at nanoId which can produce shorter names
}
getCustomClassName(){
throw new Error("Make sure to implement 'getCustomClassName' in your subclass")
}
}
Then you can extend every class where you need to access their classType/className from this MyBaseClass and override getCustomClassName providing your value.

Augment types for imported module in TypeScript

I'm converting an existing codebase from js/react/jsx setup to TypeScript. Naturally, I'd like to do it file by file, and have a question about approaches for making TS compiler work with the existing js codebase.
I convert file index.js but want to leave foo.js in JavaScript for now:
// index.ts
import { fooFunction } from 'foo';
foo({ val: 1 });
// foo.js
export const fooFunction = ({ val, optionalProp }) => {
//...
}
The problem in this example that TypeScript automatically infers argument to foo and complains that Property 'optionalProp' is missing in type { val:string, optionalProp:any }
Needing to "stub" the type of fooFunction sent me looking and I found a few ways i may be able to do it:
1) Use require instead of import
// index.ts
var fooFunction: any = require('foo').fooFunction;
2) Merge declarations
?
3) Add a d.ts file with custom declarations for foo - haven't attempted but seems inconvenient
Ideally I don't have to do (1) because I want to keep using import syntax and I don't have to do (3) because that will require me to add declaration files only to remove them later when I'm ready to convert foo.
(2) sounds awesome, but I can't figure out a working solution.
// index.ts
declare module 'foo' {
function fooFunction(options: any): any
}
Doesn't work and throws Cannot redeclare block-scoped variable 'fooFunction'
How do I do that? Are there docs that have examples of what I'm trying to do and/or have more explanation about declaration merging and how to work with namespace/interface/value?
Are there better approaches for incrementally transitioning to TypeScript?
This may not work, just wanted to preface this with that - I don't know much at all about React - but I know for sure that you can use ? on a variable to make it "optional". I've used this before in functions
myFooFunction(requiredParam: typeA, optionalParam?: typeB) { ... }
Another thing you should consider is the fact that Typescript can add some type checking at compile time, so IMO you should leverage that as much as possible.
Edit: to work with the optional parameter, you could just check that it exists.
myFooFunction(requiredParam: typeA, optionalParam?: typeB) {
if (optionalParam) { // if this results to true, the param was given
//do something
}
}

Closures in Typescript (Dependency Injection)

I'm getting my butt kicked trying to use TypeScript in a functional style with dependencies. Let's say I want to make a module that depends on another module.
If I wasn't using Dependency Injection it would look like this (in node).
SomeOtherModule = require("SomeOtherModule")
exports.doSomething = function() {
SomeOtherModule.blah()
}
This is how I do it with Dependency Injection
module.exports = function(SomeOtherModule) {
function doSomething() {
SomeOtherModule.blah()
}
return {doSomething: doSomething};
}
In typescript if you define a concrete class or module you can just type the functions as you export them or include them in the class. It's all right next to each other.
But since I can't define a module inside the DI function, the only way to do this that I can see would be to define an interface for the object I'm returning separately, which is annoying, because I want to have the type annotations in line with the definitions.
What's a better way to do this?
This will probably give you a good start: http://blorkfish.wordpress.com/2012/10/23/typescript-organizing-your-code-with-amd-modules-and-require-js/
I don't know if this is the best way to set it up. But I got it to work.
I ended up dropping AMD on my project, since I'm also using AngularJS and they step on each other's toes. I did keep using that same DI pattern through, so it looks like this in the end.
I'm pretty happy with it. I experimenting uses classes instead (you can get really close if you keep your module stateless and have the constructor be the injector function), but I didn't like having to use this for all the dependencies.
Also, classes don't actually buy me anything, because if I were coding to an interface I'd have to define the types twice anyway.
interface IMyService {
doSomething();
}
module.exports = function(SomeOtherModule) {
return {doSomething: doSomething}
function doSomething() {
SomeOtherModule.blah()
}
}

Categories

Resources