Menu

TypeScript Class: Fields, Methods, Getters and Implements

TypeScript classes are JavaScript classes with typed fields, methods and constructors. Learn how field declarations and strictPropertyInitialization work, how to type this, getters and setters, static members, implements, and how a class doubles as a type.

This page includes runnable editors - edit, run, and see output instantly.

A TypeScript class is a JavaScript class with types: each field is declared with its type in the class body, and methods and the constructor get typed parameters and return values.

The compiled output is an ordinary JavaScript class with the types removed, so everything from JavaScript classes still applies. What TypeScript adds happens at compile time: the last call is rejected before the program runs.

Declaring Fields

A field needs a declaration in the class body before the constructor can assign it. Assigning this.owner without declaring owner is error TS2339 (property does not exist). A field with an initializer gets its type inferred from the value, just like a variable.

DeclarationMeaning
name: string;Must be assigned in the constructor
count = 0;Initialized, type inferred as number
label?: string;Optional, type is string | undefined
readonly id: number;Assigned once, then read only
data!: string[];Definite assignment: trust me, it gets set
static total = 0;Belongs to the class, not to instances

With strict on, strictPropertyInitialization checks that every non-optional field gets a value. This class forgets two:

The compiler reports both:

index.ts(3,5): error TS2564: Property 'name' has no initializer and is not definitely assigned in the constructor.
index.ts(4,5): error TS2564: Property 'age' has no initializer and is not definitely assigned in the constructor.

The check exists because without it new Profile().name.toUpperCase() would type-check and then crash with undefined. Fix each field one of four ways: give it an initializer, assign it in the constructor, mark it optional with ?, or write name!: string when a framework or an init() method sets it later. The last one turns the check off for that field, so use it sparingly.

Methods and this

Methods are typed like functions. Inside a method, this has the type of the instance. A method can return this to allow chaining, and the return type follows subclasses automatically.

class QueryBuilder {
    private parts: string[] = [];

    where(condition: string): this {
        this.parts.push(condition);
        return this;
    }

    build(): string {
        return this.parts.join(" AND ");
    }
}

const sql = new QueryBuilder().where("age > 18").where("active = 1").build();
console.log(sql); // age > 18 AND active = 1

A method loses its this when you pull it off the object, a classic JavaScript bug. TypeScript does not catch that by default, but it does if the method declares a this parameter. That parameter is erased from the output and only guards the call site:

The arrow field works because each instance gets its own function that closes over this. The cost is one function per instance instead of one shared method on the prototype.

Getters and Setters

get and set accessors look like properties from the outside. A getter with no setter is read only: assigning to it is a compile error (TS2540).

The compiler rejects the assignment, and if the check is bypassed the runtime throws a TypeError too, because the compiled file runs in strict mode (the compiler emits "use strict" at the top). The RangeError in the setter is a runtime check you wrote yourself. The type number only says the value is a number; it knows nothing about ranges.

Static Members

static fields and methods belong to the class itself. You reach them through the class name, not through an instance. A static { } block runs once when the class is defined.

Static members cannot use the class's type parameters: in class Box<T>, a static empty: T is error TS2302, because there is only one static field, shared by every Box<string>, Box<number> and so on.

Implementing an Interface

implements asks the compiler to check that the class has everything an interface requires. A missing member is error TS2420: Class 'X' incorrectly implements interface 'Y'.

A class can implement several interfaces: class Doc implements Printable, Serializable. Two things implements does not do. It does not type the class's method parameters for you: check(s) { ... } inside a class that implements check(s: string): boolean is still error TS7006 (Parameter 's' implicitly has an 'any' type), so annotate them. And it leaves no trace at runtime, so obj instanceof Shape is not possible.

The private radius in the constructor is a parameter property: it declares and assigns the field in one step. See constructors.

A Class Is Also a Type

A class declaration creates two things with one name: a value (the constructor function you call with new) and a type (the shape of an instance). Because TypeScript compares types by structure, any object with the same public members fits the type, even one not made by the class.

The plain object passes the type check but is not a Point at runtime: ({ x: 6, y: 8 }) instanceof Point is false. A class with a private or #private member stops this: only instances of that class (or its subclasses) are assignable to it.

Common Mistakes

  • Declaring a field and never assigning it. TS2564 is telling you the field would be undefined. Initialize it rather than silencing it with !.
  • Passing a method as a callback. button.onclick = obj.handle loses this. Use an arrow function field or obj.handle.bind(obj).
  • Expecting implements to add code or types. It only checks. Parameter types still need annotations.
  • Using instanceof with an interface. Interfaces do not exist at runtime; check a class or use a type guard.
  • Assuming types validate data. A number field accepts any number at compile time and anything at all from untyped data at runtime.

Frequently Asked Questions

How do you create a class in TypeScript?

Declare each field with its type in the class body, then write the constructor and methods as in JavaScript: class User { name: string; constructor(name: string) { this.name = name; } greet(): string { return "Hi, " + this.name; } }. Create an instance with new User("Ada").

What does "has no initializer and is not definitely assigned in the constructor" mean?

It is error TS2564 from strictPropertyInitialization (part of strict). A field typed as string would start as undefined because nothing sets it. Fix it with an initializer (name = ""), an assignment in the constructor, an optional field (name?: string), or, when something outside the constructor sets it, a definite assignment assertion (name!: string).

What is the difference between implements and extends in TypeScript?

extends inherits code from a parent class: its fields and methods exist on the child at runtime. implements only asks the compiler to check that the class has the shape of an interface. It adds nothing to the class and disappears from the compiled JavaScript.

Can a class be used as a type in TypeScript?

Yes. A class name is both a value (the constructor) and a type (the shape of its instances). let u: User accepts any object with the same public members, because TypeScript compares types structurally. typeof User is the type of the constructor itself.

Are TypeScript classes different from JavaScript classes at runtime?

No. TypeScript compiles a class to a normal JavaScript class. Type annotations, implements clauses and modifiers like private are erased; only JavaScript features such as #private fields, static blocks and getters exist at runtime.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED