Skip to Content
Course content

44: Parameter Properties in Constructors

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Imagine you're hiring a new employee. In a traditional, bureaucratic office, the process looks like this: the new hire hands you their ID, you take that ID, walk over to a filing cabinet, open a drawer, and manually type the ID number into a folder. You've essentially taken a piece of information just to move it two feet to the left. It's redundant, tedious, and honestly, a waste of your time.

In TypeScript, we do this "filing cabinet" dance every time we write a constructor. We define a property, we pass that same property as an argument, and then we manually assign the argument to the property using this.x = x. Parameter properties are TypeScript's way of saying, "Just staple the ID to the folder and be done with it."

The Boilerplate Tax

Before we look at the shortcut, let's look at the "long way" that most of us are used to. I've seen this pattern in thousands of pull requests, and it's the definition of repetitive:

class GameCharacter {
    public name: string;
    private health: number;

    constructor(name: string, health: number) {
        this.name = name;
        this.health = health;
    }
}

Notice how we had to write name and health three times each? Once for the declaration, once for the constructor argument, and once for the assignment. It's not a bug, but it's a "boilerplate tax" that makes your classes feel longer and more cluttered than they actually are.

Cutting Out the Middleman

TypeScript allows you to collapse those three steps into one. By adding an accessibility modifier (like public, private, protected, or readonly) directly to the constructor parameter, you're telling TypeScript: "Create this property on the class and assign the incoming value to it automatically."

Here is that exact same GameCharacter class, but written the way a seasoned TS dev would do it:

class GameCharacter {
    constructor(
        public name: string, 
        private health: number
    ) {
        // The body is empty because TS handled the assignment for us!
    }
}

That's it. By adding public and private inside the parentheses, TypeScript implicitly creates the member variables and handles the this.name = name logic behind the scenes. I personally love this because it lets the reader see exactly what the class's state is just by glancing at the constructor.

Choosing Your Modifier

You might wonder if you're limited in how you define these. You aren't. You have the full suite of access modifiers at your disposal:

  • public: The property is accessible from anywhere. This is the default if you don't specify, but you must specify a modifier to trigger the parameter property shorthand.
  • private: The property is only accessible within the class. Great for internal state like health in our example.
  • protected: The property is accessible within the class and its subclasses.
  • readonly: The property can be read, but not changed after initialization. This is incredibly useful for configuration objects or IDs that should never be mutated.

For example, if your character has a unique ID that should never change, you'd just use public readonly id: string in the constructor. It’s clean, concise, and tells the next developer exactly what the intent is.




πŸ“‹ Practical Task

Refactoring the WarehouseInventory Class

You've inherited a codebase from a developer who loves writing boilerplate. You have a WarehouseInventory class that tracks items, but it's unnecessarily verbose. Your task is to refactor this class to use Parameter Properties.

Requirements:

  • The sku should be readonly and public.
  • The quantity should be public.
  • The internalLocationCode should be private.
  • The constructor body should be completely empty after your refactor.
// REFACTOR THIS CODE
class WarehouseInventory {
    public readonly sku: string;
    public quantity: number;
    private internalLocationCode: string;

    constructor(sku: string, quantity: number, internalLocationCode: string) {
        this.sku = sku;
        this.quantity = quantity;
        this.internalLocationCode = internalLocationCode;
    }
}

const item = new WarehouseInventory("TS-123", 50, "Aisle-4-Bin-12");
console.log(item.sku); // Should work
console.log(item.quantity); // Should work
// console.log(item.internalLocationCode); // Should throw a TS error
Rating
0 0

There are no comments for now.

to be the first to leave a comment.