Skip to main content

TS Ep 18: Parameter Properties — Concise Class Initializations

Rachmat Hidayat
Author
Rachmat Hidayat
Learn & sharing insights on TypeScript, Go, Kubernetes, DevOps, DevSecOps, SRE, Platform Engineering, AI/ML Engineering, and MLOps.
typescript - This article is part of a series.
Part 18: This Article
In traditional JavaScript and OOP languages, creating a simple class requires triplicating property names: declaring the field, listing it as a constructor parameter, and performing this.field = field assignments. TypeScript Parameter Properties compress all three steps into a single line.

1. The Boilerplate Problem
#

Consider a standard OOP class modeling an HTTP API Client in TypeScript without parameter properties:

// ❌ The Verbose Way (Triplicated Boilerplate)
class TraditionalApiClient {
  // 1. Declare class fields
  public readonly baseUrl: string;
  private timeoutMs: number;
  protected isDebug: boolean;

  // 2. Specify constructor parameters AND perform assignment
  constructor(baseUrl: string, timeoutMs: number, isDebug: boolean) {
    // 3. Assign to 'this'
    this.baseUrl = baseUrl;
    this.timeoutMs = timeoutMs;
    this.isDebug = isDebug;
  }
}

Notice how baseUrl, timeoutMs, and isDebug are written 3 times each! In large enterprise codebases with dependency injection (e.g., Angular or NestJS services containing 8 dependencies), constructor boilerplate quickly bloats files.


2. The Solution: Parameter Properties Syntax
#

By adding an explicit access modifier (public, private, protected), readonly, or override directly in front of a constructor argument, TypeScript automatically creates the class field and assigns it for you.

// 🟢 The Concise Way (Parameter Properties)
class ConciseApiClient {
  // TypeScript automatically declares fields AND assigns this.baseUrl = baseUrl, etc.
  constructor(
    public readonly baseUrl: string,
    private timeoutMs: number,
    protected isDebug: boolean
  ) {
    // Constructor body can be completely empty!
  }

  public getTimeout(): number {
    return this.timeoutMs;
  }
}

const client = new ConciseApiClient("https://api.acme.com", 3000, false);
console.log(client.baseUrl);   // "https://api.acme.com"
console.log(client.getTimeout()); // 3000

3. How Parameter Properties Compile to JavaScript
#

Parameter properties are a pure TypeScript syntactic convenience. When compiled to JavaScript, tsc emits standard ES6 class property assignments:

// Compiled JavaScript Output (dist/ConciseApiClient.js)
class ConciseApiClient {
    constructor(baseUrl, timeoutMs, isDebug) {
        this.baseUrl = baseUrl;
        this.timeoutMs = timeoutMs;
        this.isDebug = isDebug;
    }
    getTimeout() {
        return this.timeoutMs;
    }
}

4. Valid Parameter Property Modifiers
#

To convert a constructor argument into a parameter property, you must prefix it with at least one of the following modifiers:

  • public
  • private
  • protected
  • readonly
  • public readonly
  • private readonly
  • protected readonly
  • override (when extending base classes)
class ProductRecord {
  constructor(
    public id: string,                      // Public instance property
    private secretHash: string,             // Private instance property
    public readonly createdAt = new Date(), // Public Readonly with Default Value!
  ) {}
}
Warning

Omitting the Modifier: If you omit the modifier (e.g., constructor(id: string)), TypeScript treats id as a standard local constructor argument only—it will NOT create this.id on the class instance!


5. Parameter Properties in Inheritance (super())
#

When extending a base class, parameter properties in the subclass are assigned after super() finishes executing.

class BaseService {
  constructor(public readonly serviceName: string) {
    console.log(`[BaseService] Initialized: ${serviceName}`);
  }
}

class UserService extends BaseService {
  constructor(
    serviceName: string,
    private userStoreUrl: string // Subclass Parameter Property
  ) {
    // 1. MUST call super() first!
    super(serviceName);
    
    // 2. TypeScript automatically assigns 'this.userStoreUrl = userStoreUrl' right after super()
  }

  public printStore() {
    console.log(`[UserService] Store URL: ${this.userStoreUrl}`);
  }
}

const userSvc = new UserService("AuthService", "https://db.internal/users");
userSvc.printStore();

6. Common Gotcha: Duplicate Identifier Errors
#

A frequent beginner mistake is declaring the property in the class body AND adding an access modifier in the constructor:

class BadDuplicateClass {
  // ❌ Declaration in class body
  name: string; 

  // ❌ Compiler Error: Duplicate identifier 'name'.
  // Property 'name' is already declared in class 'BadDuplicateClass'.
  constructor(public name: string) {} 
}

The Rule:
#

  • If you use Parameter Properties (public name: string in constructor), do NOT declare name: string at the top of the class.

Summary & Next Steps
#

In this episode:

  • We learned how Parameter Properties compress field declaration, parameter typing, and property assignment into constructor signatures.
  • We listed valid modifiers: public, private, protected, readonly, and override.
  • We traced compilation behavior into standard this.key = key assignments.
  • We analyzed inheritance behavior with super() calls and avoided duplicate identifier errors.

In Episode 19: Getters and Setters, we will explore how to encapsulate property access control using TypeScript get and set accessors!

typescript - This article is part of a series.
Part 18: This Article