public static const in TypeScript
If you did want something that behaved more like a static constant value in modern browsers (in that it can't be changed by other code), you could add a get
only accessor to the Library
class (this will only work for ES5+ browsers and NodeJS):
export class Library {
public static get BOOK_SHELF_NONE():string { return "None"; }
public static get BOOK_SHELF_FULL():string { return "Full"; }
}
var x = Library.BOOK_SHELF_NONE;
console.log(x);
Library.BOOK_SHELF_NONE = "Not Full";
x = Library.BOOK_SHELF_NONE;
console.log(x);
If you run it, you'll see how the attempt to set the BOOK_SHELF_NONE
property to a new value doesn't work.
2.0
In TypeScript 2.0, you can use readonly
to achieve very similar results:
export class Library {
public static readonly BOOK_SHELF_NONE = "None";
public static readonly BOOK_SHELF_FULL = "Full";
}
The syntax is a bit simpler and more obvious. However, the compiler prevents changes rather than the run time (unlike in the first example, where the change would not be allowed at all as demonstrated).
You can do it using namespaces, like this:
export namespace Library {
export const BOOK_SHELF_NONE: string = 'NONE';
}
Then you can import it from anywhere else:
import {Library} from './Library';
console.log(Library.BOOK_SHELF_NONE);
If you need a class there as well include it inside the namespace: export class Book {...}
Here's what's this TS snippet compiled into (via TS Playground):
define(["require", "exports"], function(require, exports) {
var Library = (function () {
function Library() {
}
Library.BOOK_SHELF_NONE = "None";
Library.BOOK_SHELF_FULL = "Full";
return Library;
})();
exports.Library = Library;
});
As you see, both properties defined as public static
are simply attached to the exported function (as its properties); therefore they should be accessible as long as you properly access the function itself.
Meanwhile this can be solved through a decorator in combination with Object.freeze
or Object.defineProperty
, I'm using this, it's a little bit prettier than using tons of getters. You can copy/paste this directly TS Playground to see it in action. - There are two options
Make individual fields "final"
The following decorator converts both, annotated static and non-static fields to "getter-only-properties".
Note: If an instance-variable with no initial value is annotated @final
, then the first assigned value (no matter when) will be the final one.
// example
class MyClass {
@final
public finalProp: string = "You shall not change me!";
@final
public static FINAL_FIELD: number = 75;
public static NON_FINAL: string = "I am not final."
}
var myInstance: MyClass = new MyClass();
myInstance.finalProp = "Was I changed?";
MyClass.FINAL_FIELD = 123;
MyClass.NON_FINAL = "I was changed.";
console.log(myInstance.finalProp); // => You shall not change me!
console.log(MyClass.FINAL_FIELD); // => 75
console.log(MyClass.NON_FINAL); // => I was changed.
The Decorator: Make sure you include this in your code!
/**
* Turns static and non-static fields into getter-only, and therefor renders them "final".
* To use simply annotate the static or non-static field with: @final
*/
function final(target: any, propertyKey: string) {
const value: any = target[propertyKey];
// if it currently has no value, then wait for the first setter-call
// usually the case with non-static fields
if (!value) {
Object.defineProperty(target, propertyKey, {
set: function (value: any) {
Object.defineProperty(this, propertyKey, {
get: function () {
return value;
},
enumerable: true,
configurable: false
});
},
enumerable: true,
configurable: true
});
} else { // else, set it immediatly
Object.defineProperty(target, propertyKey, {
get: function () {
return value;
},
enumerable: true
});
}
}
As an alternative to the decorator above, there would also be a strict version of this, which would even throw an Error when someone tried to assign some value to the field with "use strict";
being set. (This is only the static part though)
/**
* Turns static fields into getter-only, and therefor renders them "final".
* Also throws an error in strict mode if the value is tried to be touched.
* To use simply annotate the static field with: @strictFinal
*/
function strictFinal(target: any, propertyKey: string) {
Object.defineProperty(target, propertyKey, {
value: target[propertyKey],
writable: false,
enumerable: true
});
}
Make every static field "final"
Possible Downside: This will only work for ALL statics of that class or for none, but cannot be applied to specific statics.
/**
* Freezes the annotated class, making every static 'final'.
* Usage:
* @StaticsFinal
* class MyClass {
* public static SOME_STATIC: string = "SOME_STATIC";
* //...
* }
*/
function StaticsFinal(target: any) {
Object.freeze(target);
}
// Usage here
@StaticsFinal
class FreezeMe {
public static FROZEN_STATIC: string = "I am frozen";
}
class EditMyStuff {
public static NON_FROZEN_STATIC: string = "I am frozen";
}
// Test here
FreezeMe.FROZEN_STATIC = "I am not frozen.";
EditMyStuff.NON_FROZEN_STATIC = "I am not frozen.";
console.log(FreezeMe.FROZEN_STATIC); // => "I am frozen."
console.log(EditMyStuff.NON_FROZEN_STATIC); // => "I am not frozen."
Thank you WiredPrairie!
Just to expand on your answer a bit, here is a complete example of defining a constants class.
// CYConstants.ts
class CYConstants {
public static get NOT_FOUND(): number { return -1; }
public static get EMPTY_STRING(): string { return ""; }
}
export = CYConstants;
To use
// main.ts
import CYConstants = require("./CYConstants");
console.log(CYConstants.NOT_FOUND); // Prints -1
console.log(CYConstants.EMPTY_STRING); // Prints "" (Nothing!)