Reputation: 8471
I just want to declare a static property in typescript interface? I have not found anywhere regarding this.
interface myInterface {
static Name:string;
}
Is it possible?
Upvotes: 246
Views: 266808
Reputation: 437
At least trick below works for me for some limited cases:
interface StaticMethods {
staticMethod(): string;
}
const MyClass = class MyClass {
static staticMethod(): string {
return "Hello from static!";
}
static anotherMethod(): string {
return "This is another method!";
}
} satisfies StaticMethods;
console.log(MyClass.staticMethod()); // "Hello from static!"
console.log(MyClass.anotherMethod()); // "This is another method!"
Upvotes: 0
Reputation: 54965
Static modifiers cannot appear on a type member (TS1070). That's why I recommend to use an abstract class and inheritance to solve the mission:
Example
// Interface definition
abstract class MyInterface {
static MyName: string;
abstract getText(): string;
}
// Interface implementation
class MyClass extends MyInterface {
static MyName = 'TestName';
getText(): string {
return `This is my name static name "${MyClass.MyName}".`;
}
}
// Test run
const test: MyInterface = new MyClass();
console.log(test.getText());
Upvotes: 39
Reputation: 351
For trying to do this with TypeScript 5.0 (ie. ECMAScript decorators instead of --experimentalDecorators
), and getting The runtime will invoke the decorator with 2 arguments, but the decorator expects 1
error, the following worked for me (adapting @val's answer):
interface StaticFooInterface {
doFoo(fooName: string, fooAmount: number): FooInterface;
redoFoo(theFoo: FooInterface): FooInterface[];
new (fooName: string, fooAmount: number, fooBars: string[]): FooInterface;
}
interface FooInterface {
get fooName(): string;
get fooAmount(): number;
get fooBars(): string[];
addBar(bar: string): this;
removeBar(bar: string): this;
asBaz(): Symbol;
}
function behaves<T>() {
return <U extends T>(target: U, _context: ClassDecoratorContext): U => {
return target;
};
}
@behaves<StaticFooInterface>()
class FooWithBarsAndBaz {
static doFoo(fooName: string, fooAmount: number): FooInterface {
return new FooWithBarsAndBaz(fooName, fooAmount, []);
}
static redoFoo(theFoo: FooInterface): FooInterface[] {
return [theFoo, theFoo];
}
#fooName: string;
#fooAmount: number;
#fooBars: string[];
constructor(fooName: string, fooAmount: number, fooBars: string[]) {
this.#fooName = fooName;
this.#fooAmount = fooAmount;
this.#fooBars = fooBars;
}
get fooName(): string {
return this.#fooName;
}
get fooAmount(): number {
return this.#fooAmount;
}
get fooBars(): string[] {
return this.#fooBars;
}
addBar(bar: string): this {
this.#fooBars.push(bar);
return this;
}
removeBar(bar: string): this {
this.#fooBars = this.#fooBars.filter((aBar) => aBar !== bar);
return this;
}
asBaz(): Symbol {
return Symbol.for(this.fooName);
};
}
Aside from the contrived example, the key is:
function behaves<T>() {
return <U extends T>(target: U, _context: ClassDecoratorContext): U => {
return target;
};
}
This is a "new style" decorator.
Hope this helps :)
Upvotes: 0
Reputation: 231
Returns an instance type of I
, makes sure I
is a valid concrete constructor and C
extends I
:
type StaticImplements<I extends new (...args: any[]) => any, C extends I> = InstanceType<I>;
Or a most general version, that should work for more cases. Returns any
and makes sure C
extends I
:
type StaticImplements<I, C extends I> = any;
Interface with instance method:
interface MyInstance {
instanceMethod();
}
Interface with static method:
interface MyClassStatic {
new (...args: any[]): MyInstance;
staticMethod();
}
Class requiring static method and extending with its own method:
class MyClass implements StaticImplements<MyClassStatic, typeof MyClass> {
static staticMethod();
static ownStaticMethod();
instanceMethod();
ownInstanceMethod();
}
C extends I
I
can be changed as neededimplements
; For InstanceType<I>
class would actually implement an instance type, although object
, {}
or any
should workDefining static methods in interfaces is being discussed in #33892, and abstract static methods are being discussed in #34516.
Based on Val's and Aleksey's answers (thank you), this solution:
As is - Playground Link:
MyClass.staticMethod(); // OK
MyClass.ownStaticMethod(); // OK
new MyClass().instanceMethod(); // OK
new MyClass().ownInstanceMethod(); // OK
If to remove staticMethod
from MyClass
- Playground Link:
class MyClass implements StaticImplements<MyClassStatic, typeof MyClass> {} // Type 'typeof MyClass' does not satisfy the constraint 'MyClassStatic'. Property 'staticMethod' is missing in type 'typeof MyClass' but required in type 'MyClassStatic'.
If to remove instanceMethod
from MyClass
- Playground Link:
class MyClass implements StaticImplements<MyClassStatic, typeof MyClass> {} // Class 'MyClass' incorrectly implements interface 'MyInstance'. Property 'instanceMethod' is missing in type 'MyClass' but required in type 'MyInstance'.
Upvotes: 22
Reputation: 18143
Simple example
interface Person {
name: string;
age: number;
}
abstract class Trackable {
static TrackInstances: number;
}
class Pablo extends Trackable implements Person {
constructor(public name: string, public age: number) { Pablo.TrackInstances+=1; }
}
const person = new Pablo();
console.log(Pablo.TrackInstances);
Upvotes: -1
Reputation: 2124
Winterhotlatte had a good answer that got me on the right track.
However it is highly inconvenient to force inclusion of a constructor in the static interface.
Reversing the extends gives:
type Static<TClass extends IStaticInterface & { new(...args) }, IStaticInterface>
= InstanceType<TClass>;
No need to add a constructor to the interface:
interface IMeow { readonly IsMeow: boolean; }
And used conveniently like so:
class Cat implements Static<typeof Cat, IMeow> {
readonly static IsMeow = true;
}
Just like before it gives a very clear error if static IsMeow
is missing from Cat
.
Also like before it still works like normal in every other regard.
Require an "implements" string after the type:
type Static<TClass extends IStaticInterface & { new(...args) },
_txt extends "implements", IStaticInterface>
= InstanceType<TClass>;
To demonstrate here is our cat again:
class Cat implements Static<typeof Cat, "implements", IMeow> {
static readonly IsMeow = true;
}
This really isn't needed, you can just repeat Static<...>
, but here goes:
type Static<TClass extends InterfacesCombined & { new(...args) }, _txt extends "implements",
IStaticInterface, IStatic2 = {}, IStatic3 = {}, IStatic4 = {}, IStatic5 = {},
InterfacesCombined extends IStaticInterface & IStatic2 & IStatic3 & IStatic4 & IStatic5
= IStaticInterface & IStatic2 & IStatic3 & IStatic4 & IStatic5>
= InstanceType<TClass>;
To demonstrate lets upgrade our Cat with more complexity:
interface IPurr { purr(): string; }
interface ILick { Lick(human: any): void; }
And the usage looks like:
class Cat implements IPurr, Static<typeof Cat, "implements", IMeow, ILick> {
static readonly IsMeow = true;
static Lick(human: any) { /* obey me homan! */ }
purr() { return "Prrrrr"; }
}
If you use a static interface often, you don't want to type out all that Static<...
stuff.
First lets just get this little helper out of the way:
type New<T> = { new(...args: any): T };
Now lets "bake" a static ILick
interface:
type IStaticLick<TClass extends ILick & New<InstanceType<TClass>> = InstanceType<TClass>;
Voilá:
class Cat2 implements IStaticLick<typeof Cat2> {
static Lick(human: any) { /* obey me homan! */ }
}
We are simply requiring that typeof T
implements something for it to be a valid parameter to our "static interface".
So if interface IFoo { stuff }
+ class Foo implements IFoo
says Foo is "stuff"
then what we are saying is "stuff" must be in T for T to be allowed inside Static<T, ...>
.
So the implements
part of class Foo implements Static<...>
doesn't really say anything about Foo having anything special. Rather in a roundabout way, we are just saying that we have these fancy <
brackets >
and inside them is a VIP club that only accepts "stuff" rollers!
In other words we can write:
class FakeCat implements IStaticLick<typeof Cat2> { }
...see what I mean then?
We try to lessen this glaring problem a little by requiring Static<TClass, ...>
to actually implement the instance type of TClass
. But this does nothing at all if InstanceType<TClass>
doesn't have any instance members - such as our Cat2
class.
FakeCat
does implement Cat2
- because it's not very hard to implement: { }
.
Upvotes: 7
Reputation: 125
My solution has worked great for my use case of adding additional static constructors. I have tested it and it has passed all tests. If anyone finds a buggy edge case please let me know.
I made a generic type which takes in the interface and static interface.
It works for concrete and abstract classes.
I have designed it using conditional types such that all errors get propagated to the class implementing the Interface rather than the interface itself.
Note: The error propagation allows for vscode quick fixes (implement all methods). The only drawback is you will have to apply the static keyword yourself because there is no quick fix available for that error.
type Class<T = any> = new (...args: any[]) => T;
type AbstractClass<T = any> = abstract new (...args: any[]) => T;
type Interface<C extends Class<InstanceType<C>> | AbstractClass<InstanceType<C>>, SI, I = {}> =
C extends Class<InstanceType<C>>
// ConcreteClass
? InstanceType<C> extends I
? C extends (SI & Class<InstanceType<C>>)
? (InstanceType<C> & I)
: (SI & Class<InstanceType<C>>) // Indicate StaticInterface Error
: I // Indicate Interface Error
// AbstractClass
: InstanceType<C> extends I
? C extends (SI & AbstractClass<InstanceType<C>>)
? (InstanceType<C> & I)
: (SI & AbstractClass<InstanceType<C>>) // Indicate StaticInterface Error
: I // Indicate Interface Error
interface MyInterface {
instanceMethod(): number;
}
interface MyStaticInterface {
staticMethod(): number;
}
class MyClass implements Interface<typeof MyClass, MyStaticInterface, MyInterface> {
static staticMethod(): number {
return 50;
}
instanceMethod(): number {
return 100;
}
static otherStatic() {
return "HELLO"
}
otherInstance() {
return "GOODBYE"
}
}
abstract class MyClass1 implements Interface<typeof MyClass1, MyStaticInterface, MyInterface> {
static staticMethod(): number {
return 50;
}
instanceMethod(): number {
return 20;
}
static otherStatic() {
return "HELLO"
}
otherInstance() {
return "GOODBYE"
}
abstract abstractMethod() : number;
}
Upvotes: 1
Reputation: 2327
Here's a fairly simple way to do it:
interface MyClass {
new (): MyClassInstance;
staticMethod(): string;
}
interface MyClassInstance {
instanceMethod(): string;
}
const Class: MyClass = class {
static staticMethod() {
return "This is a static method";
}
instanceMethod() {
return "This is an instance method";
}
}
Class.staticMethod();
// Has type MyClassInstance
const instance = new Class();
instance.instanceMethod();
Note that this doesn't allow you to make a class extend an interface like you could normally, but for many situations this is good enough.
Upvotes: 3
Reputation: 133
I'm somewhat surprised at how overly complicated the top answers are! But maybe that's just because this thread is so old.
Edit: Actually my initial attempt proved to be practically useless after some testing, and this problem turned out to be somewhat harder to tackle than I had originally expected.
After about an hour or so of tinkering however, I think I may have just found the best/cleanest solution so far (building upon my initial idea)! If the question posed is "How do I include static properties in an interface?", then I think this is a fairly decent answer. This is at least better than extending a class if all you need is an interface (compiletime typing/requirements/restraints). There's no real drawback (well, maybe one small one) to this either since the solution is 100% ambient (unlike extends
-based class extension like some answers are suggesting) and classes are constants regardless (immutable references that are not hoisted when using the standard class declaration syntax instead of a class expression as I'm doing here). This incurs no runtime overhead and doesn't require runtime class inheritance. You can define the entire class (both static and non-static members) all in one (ambient class used as an) interface!
Here is how it's done!
/** ./interface.ts */
// In a file module (best), or wrap in ts module or namespace block
// Putting this in a module provides encapsulation ensuring that no one is
// at risk of misusing this class (it must be used as a type only).
// Export only a type reference which will make it error is someone tries
// to use it as a value (such as in an `extends` clause, or trying to
// instantiate it).
/**
* Other Ideas For Names To Differentiate From Actual Classes/Non-Ambient Values:
* MyClassInterface or _Interface_MyClass or MyClass_Interface or Interface_MyClass
**/
declare class _MyClassInterface {
static staticProp: string;
static staticMethod(): number;
readonly prop: boolean
/**
* Note: Above, readonly won't need to be specified in the real class
* but the prop *will* still be readonly anyway.
*
* Now for the only caveat!
* It does appear however that you cannot mark anything private or
* protected in this pseudo-interface which is a bummer, only props
* and methods that appear only in the real class can be.
*/
prop2: boolean;
method(): Function;
constructor(p1: string, p2: number);
}
export type MyClassInterface = typeof _MyClassInterface;
now to consume the interface
/** ./consumer.ts */
import { MyClassInterface } from "./interface" // type import
const MyClass: MyClassInterface = class {
static staticProp: string;
prop: boolean;
prop2: boolean;
protected onlyOnRealClass: boolean; /* this is ok since this prop doesn't exist on the interface */
static staticMethod() {
return 5;
}
method() {
return () => {};
}
constructor(p1: string, p2: number) {}
};
note that the typeof
keyword is absolutely essential here (if I recall correctly, it is because without it typescript thinks we are specifying the instance type when what we really want is the type of the class itself). For example when we do
const a: MyClass = new MyClass()
without the typeof
keyword, we're saying that a
should be an instance of MyClass, not MyClass itself.
abstract
ensures you don't accidentally try to instantiate the class...
Edit: Actually I'm removing the abstract keyword from my answer because it turns out that the real class actually inherits the property of being abstract from the ambient class (makes sense), and will therefore not instantiate w/o the compiler complaining if the ambient class that provides its type is marked abstract... just gonna have to deal with ts not erroring if the ambient class is ever accidentally instantiated. It might be a decent idea then to prefix the ambient class declaration/name with an underscore and/or include the word Interface
in the name so its proper use is clear (edit: I have since tackled this issue by encapsulating the interface in a file module thereby rendering it private to all other code and then exporting only a type reference to it).
and that is all there is to it really!
Putting the interface into a module isn't totally necessary but it provides a couple small benefits including:
the "publicly" widely-used type-annotation that is used throughout implementation code becomes slightly smaller since it no longer includes the keyword typeof
unlike the wrapped ambient class/interface declaration, the exported/outward-facing identifier is strictly a type (alias), so an error will now occur if anyone tries to instantiate it or use it in an extends clause (or use it anywhere else a runtime value is expected)
I'm not supplying a class name for the Class Expression in this example because Class Expressions, like all Function Expressions, will just inherit the identifier that they are assigned to if a class name if not provided. So if your identifier is identical to the name you want for that class or function anyways, you can just leave it off. Or, you may provide one inline as usual and it will take precedence over the identifier. A class or function name can also be changed after function/class creation, but only via Object.defineProperty or Object.defineProperties.
FWIW classes can actually be implemented
(at least in recent versions of TS) by another class, but the static properties will just be ignored anyways. It seems that implementing
anything only applies to the prototype
in both directions (to/from).
Upvotes: 9
Reputation: 10564
The other solutions seem to stray from the blessed path and I found that my scenario was covered in the Typescript documentation which I've paraphrased below:
interface AppPackageCheck<T> {
new (packageExists: boolean): T
checkIfPackageExists(): boolean;
}
class WebApp {
public static checkIfPackageExists(): boolean {
return false;
}
constructor(public packageExists: boolean) {}
}
class BackendApp {
constructor(public packageExists: boolean) {}
}
function createApp<T>(type: AppPackageCheck<T>): T {
const packageExists = type.checkIfPackageExists();
return new type(packageExists)
}
let web = createApp(WebApp);
// compiler failure here, missing checkIfPackageExists
let backend = createApp(BackendApp);
Upvotes: 2
Reputation: 22807
Follow @Duncan's @Bartvds's answer, here to provide a workable way after years passed.
At this point after Typescript 1.5 released (@Jun 15 '15), your helpful interface
interface MyType {
instanceMethod();
}
interface MyTypeStatic {
new():MyType;
staticMethod();
}
can be implemented this way with the help of decorator.
/* class decorator */
function staticImplements<T>() {
return <U extends T>(constructor: U) => {constructor};
}
@staticImplements<MyTypeStatic>() /* this statement implements both normal interface & static interface */
class MyTypeClass { /* implements MyType { */ /* so this become optional not required */
public static staticMethod() {}
instanceMethod() {}
}
Refer to my comment at github issue 13462.
visual result:
Compile error with a hint of static method missing.
After static method implemented, hint for method missing.
Compilation passed after both static interface and normal interface fulfilled.
Upvotes: 204
Reputation: 105
I implemented a solution like Kamil Szot's, and it has an undesired effect. I have not enough reputation to post this as a comment, so I post it here in case someone is trying that solution and reads this.
The solution is:
interface MyInterface {
Name: string;
}
const MyClass = class {
static Name: string;
};
However, using a class expression doesn't allow me to use MyClass
as a type. If I write something like:
const myInstance: MyClass;
myInstance
turns out to be of type any
, and my editor shows the following error:
'MyClass' refers to a value, but is being used as a type here. Did you mean 'typeof MyClass'?ts(2749)
I end up losing a more important typing than the one I wanted to achieve with the interface for the static part of the class.
Val's solution using a decorator avoids this pitfall.
Upvotes: 0
Reputation: 17817
You can define interface normally:
interface MyInterface {
Name:string;
}
but you can't just do
class MyClass implements MyInterface {
static Name:string; // typescript won't care about this field
Name:string; // and demand this one instead
}
To express that a class should follow this interface for its static properties you need a bit of trickery:
var MyClass: MyInterface;
MyClass = class {
static Name:string; // if the class doesn't have that field it won't compile
}
You can even keep the name of the class, TypeScript (2.0) won't mind:
var MyClass: MyInterface;
MyClass = class MyClass {
static Name:string; // if the class doesn't have that field it won't compile
}
If you want to inherit from many interfaces statically you'll have to merge them first into a new one:
interface NameInterface {
Name:string;
}
interface AddressInterface {
Address:string;
}
interface NameAndAddressInterface extends NameInterface, AddressInterface { }
var MyClass: NameAndAddressInterface;
MyClass = class MyClass {
static Name:string; // if the class doesn't have that static field code won't compile
static Address:string; // if the class doesn't have that static field code won't compile
}
Or if you don't want to name merged interface you can do:
interface NameInterface {
Name:string;
}
interface AddressInterface {
Address:string;
}
var MyClass: NameInterface & AddressInterface;
MyClass = class MyClass {
static Name:string; // if the class doesn't have that static field code won't compile
static Address:string; // if the class doesn't have that static field code won't compile
}
Working example
Upvotes: 25
Reputation: 38046
Another option not mentioned here is defining variable with a type representing static interface and assigning to it class expression:
interface MyType {
instanceMethod(): void;
}
interface MyTypeStatic {
new(): MyType;
staticMethod(): void;
}
// ok
const MyTypeClass: MyTypeStatic = class MyTypeClass {
public static staticMethod() { }
instanceMethod() { }
}
// error: 'instanceMethod' is missing
const MyTypeClass1: MyTypeStatic = class MyTypeClass {
public static staticMethod() { }
}
// error: 'staticMethod' is missing
const MyTypeClass2: MyTypeStatic = class MyTypeClass {
instanceMethod() { }
}
The effect is same as in answer with decorators, but without overhead of decorators
Relevant suggestion/discussion on GitHub
Upvotes: 7
Reputation: 6400
Though static keyword not supported in interface in Typescript
but we can achieve it by creating a function interface
that has static member.
In my following code I have created a function interface Factory
that has two static members serialNumber
and printSerial.
// factory is a function interface
interface Factory<T> {
(name: string, age: number): T;
//staic property
serialNumber: number;
//static method
printSrial: () => void;
}
class Dog {
constructor(public name: string, public age: number) { }
}
const dogFactory: Factory<Dog> = (name, age) => {
return new Dog(name, age);
}
// initialising static members
dogFactory.serialNumber = 1234;
dogFactory.printSrial = () => console.log(dogFactory.serialNumber);
//instance of Dog that DogFactory creates
const myDog = dogFactory("spike", 3);
//static property that returns 1234
console.log(dogFactory.serialNumber)
//static method that prints the serial 1234
dogFactory.printSrial();
Upvotes: 1
Reputation:
Yes, it is possible. Here is the solution
export interface Foo {
test(): void;
}
export namespace Foo {
export function statMethod(): void {
console.log(2);
}
}
Upvotes: 6
Reputation: 729
I found a way to do this (without decorators) for my specific use case.
The important part that checks for static members is IObjectClass
and using cls: IObjectClass<T>
in the createObject
method:
//------------------------
// Library
//------------------------
interface IObject {
id: number;
}
interface IObjectClass<T> {
new(): T;
table_name: string;
}
function createObject<T extends IObject>(cls: IObjectClass<T>, data:Partial<T>):T {
let obj:T = (<any>Object).assign({},
data,
{
id: 1,
table_name: cls.table_name,
}
)
return obj;
}
//------------------------
// Implementation
//------------------------
export class User implements IObject {
static table_name: string = 'user';
id: number;
name: string;
}
//------------------------
// Application
//------------------------
let user = createObject(User, {name: 'Jimmy'});
console.log(user.name);
Upvotes: 2
Reputation: 31769
You can merge interface with namespace using the same name:
interface myInterface { }
namespace myInterface {
Name:string;
}
But this interface is only useful to know that its have property Name
. You can not implement it.
Upvotes: 3
Reputation: 251222
You can't define a static property on an interface in TypeScript.
Say you wanted to change the Date
object, rather than trying to add to the definitions of Date
, you could wrap it, or simply create your rich date class to do the stuff that Date
doesn't do.
class RichDate {
public static MinValue = new Date();
}
Because Date is an interface in TypeScript, you can't extend it with a class using the extends
keyword, which is a bit of a shame as this would be a good solution if date was a class.
If you want to extend the Date object to provide a MinValue
property on the prototype, you can:
interface Date {
MinValue: Date;
}
Date.prototype.MinValue = new Date(0);
Called using:
var x = new Date();
console.log(x.MinValue);
And if you want to make it available without an instance, you also can... but it is a bit fussy.
interface DateStatic extends Date {
MinValue: Date;
}
Date['MinValue'] = new Date(0);
Called using:
var x: DateStatic = <any>Date; // We aren't using an instance
console.log(x.MinValue);
Upvotes: 70
Reputation: 1168
If you're looking to define a static class (ie. all methods/properties are static), you can do something like this:
interface MyStaticClassInterface {
foo():string;
}
var myStaticClass:MyStaticClassInterface = {
foo() {
return 'bar';
}
};
In this case, the static "class" is really just a plain-ol'-js-object, which implements all the methods of MyStaticClassInterface
Upvotes: 3
Reputation: 3520
@duncan's solution above specifying new()
for the static type works also with interfaces:
interface MyType {
instanceMethod();
}
interface MyTypeStatic {
new():MyType;
staticMethod();
}
Upvotes: 8
Reputation: 532
Static properties are usually placed on the (global) constructor for the object, whereas the "interface" keyword applies to instances of the object.
The previous answer given is of course correct if you are writing the class in TypeScript. It may help others to know that if you are describing an object that is already implemented elsewhere, then the global constructor including static properties can be declared like this:
declare var myInterface : {
new(): Interface;
Name:string;
}
Upvotes: 17