The Hidden Dangers of Access Modifiers in Typescript
The problem with relying on access modifiers in Typescript and how to use them more effectively.
I’ve had the opportunity to work with various programming languages, but the one I constantly reach for is Typescript. The language offers a range of features that make it a joy to work with. However, one aspect of Typescript has been the bane of my existence for quite some time: Classes.
If you’re familiar with object-oriented programming (OOP), classes in Typescript seem like a straightforward way to organize code. They provide a clear structure for defining objects, with constructors, properties, and methods all neatly organized in one place. Additionally, Typescript classes allow you to specify access modifiers such as public and private which can help ensure your code is encapsulated and secure. Or is it?
Consider the following example:
class Foo {
public helloPublic() {
console.log("Hello Public");
}
private helloPrivate() {
console.log("Hello Private");
}
}
const foo = new Foo();
foo.helloPublic();
foo.helloPrivate(); // Compiler error accessing a private propertyAttempting to call foo.helloPrivate() will result in an error at compile time. Typescript is smart enough to know that you have indicated that the property is private and should not be accessible outside the class. This seems like the correct behavior, so what do I mean when I say that Typescript classes lie?
While it may seem like marking a property as private will prevent it from being accessed outside of the class, in reality, there are many ways that other code can still interact with it. Casting the object to a different type or disabling the compiler on that line are some simple ways to do so.
// Both of these will not result in a compiler error
// even though we're accessing the private property
// @ts-ignore
foo.helloPrivate();
(foo as any).helloPrivate();But how is this possible? A property marked as private should not be accessible from outside the class, so why does Typescript allow this behavior? This is because JavaScript classes don’t have a concept of access modifiers, and every property on the class is treated as public by default. When compiling the Typescript to JavaScript, all of our access modifiers disappear.
With all of this in mind, rather than using private, I instead often rely on the protected access modifier. Even though it is still a lie, it provides some escape hatches that make it easy to test these methods in the class without going through the public interface.
class Foo {
protected protectedAddition(a: number, b: number) {
return a + b;
}
}
// "Test" class that makes the protected property public
class FooTest extends Foo {
public protectedAddition(a: number, b: number) {
return super.protectedAddition(a, b);
}
}
describe("Foo", () => {
describe("protectedAddition", () => {
test("should add two numbers", () => {
const foo = new FooTest();
expect(foo.protectedAddition(2, 2)).toBe(4);
});
});
});I prefer to avoid Typescript classes entirely in favor of record types, but if it can’t be helped, you might as well choose to use protected rather than private properties.

