TypeScript abstract classes and access modifiers
Use private, protected, readonly, and abstract to describe what subclasses and outside code may do.
On this page
TypeScript classes and interfaces.
- Use private, protected, readonly, and abstract to describe what subclasses and outside code may do.
- Predict the sample output and explain the state changes.
- Complete the practice task and check the boundary cases.
The concept
TypeScript adds access modifiers to classes. A private member is visible only inside the class, a protected member is also visible to subclasses, and public is the default. readonly forbids assignment after construction. These modifiers are checked by the compiler and erased at run time. For hard privacy at run time use JavaScript's #private fields.
An abstract class cannot be instantiated and may declare abstract members that subclasses must implement. Code can depend on the abstract type, for example Shape, and work with any subclass. A class can also implement an interface, which lists the members it promises to provide, without sharing any code. Fields are declared explicitly and assigned in the constructor.
Code example
interface Describable {
describe(): string;
}
abstract class Shape implements Describable {
readonly name: string;
private static count = 0;
constructor(name: string) {
this.name = name;
Shape.count++;
}
abstract area(): number;
describe(): string {
return this.name + " with area " + this.area().toFixed(2);
}
static total(): number {
return Shape.count;
}
}
class Circle extends Shape {
private radius: number;
constructor(radius: number) {
super("circle");
this.radius = radius;
}
area(): number {
return Math.PI * this.radius ** 2;
}
}
class Rectangle extends Shape {
private width: number;
private height: number;
constructor(width: number, height: number) {
super("rectangle");
this.width = width;
this.height = height;
}
area(): number {
return this.width * this.height;
}
}
const shapes: Shape[] = [new Circle(1.5), new Rectangle(2, 3.5)];
for (const shape of shapes) {
console.log(shape.describe());
}
console.log("shapes created: " + Shape.total());circle with area 7.07 rectangle with area 7.00 shapes created: 2
Follow the code
Open the walkthrough to inspect the example one step at a time.
Show guided walkthrough
interface Describable { describe(): string;} abstract class Shape implements Describable { readonly name: string; private static count = 0; constructor(name: string) { this.name = name; Shape.count++; } abstract area(): number; describe(): string { return this.name + " with area " + this.area().toFixed(2); } static total(): number { return Shape.count; }} class Circle extends Shape { private radius: number; constructor(radius: number) { super("circle"); this.radius = radius; } area(): number { return Math.PI * this.radius ** 2; }} class Rectangle extends Shape { private width: number; private height: number; constructor(width: number, height: number) { super("rectangle"); this.width = width; this.height = height; } area(): number { return this.width * this.height; }} const shapes: Shape[] = [new Circle(1.5), new Rectangle(2, 3.5)];for (const shape of shapes) { console.log(shape.describe());}console.log("shapes created: " + Shape.total());Follow the example
Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.
View the complete dry-run table
| Step / line | What happens | State after step | Output so far |
|---|---|---|---|
| Shape cannot be instantiated. It promises the Describable interface. | — | — | |
| Every concrete shape must implement area. | — | — | |
| describe is written once and calls the subclass's area. | — | — | |
| The array has the abstract type but holds the two subclasses. | — | — | |
| Each shape reports its own area: π·1.5² and 2·3.5. | — | circle with area 7.07 rectangle with area 7.00 | |
| The static counter lives on the class and is private, with a public accessor. | — | circle with area 7.07 rectangle with area 7.00 shapes created: 2 |
Behind the scenes
How TypeScript processes this code
TypeScript checks types and emits JavaScript. Type annotations are removed before execution.
Cost and common mistakes
describe and area are O(1).
- private and protected exist only in the compiler. At run time anyone can read the property. Use #private for real privacy.
- Parameter properties (constructor(private radius: number)) are shorter, but this example writes the fields out so that it also runs in type-stripping environments.
- An interface has no code and no run-time presence. A class that only needs a contract should implement an interface rather than extend a class.
Try it yourself
Why make Shape abstract instead of a normal class?
Reveal answer and reasoning
A plain Shape has no meaningful area. Abstract forbids creating one and forces each subclass to define its area.