-
Section 1: Getting Started
-
Section 2: Basic Types
-
Section 3: Functions and Objects
-
Section 4: Advanced Types
-
Section 5: Object-Oriented TypeScript
-
Section 6: Working with Modules
-
Section 7: Tooling and Practice
-
Section 8: Type-Level Programming
-
Section 9: TypeScript with Backends
-
Section 10: Testing Typed Code
-
Section 11: Data Validation with Types
-
Section 12: Practical Projects
-
Section 13: Compiler Internals
-
Section 14: Configuration Deep Dive
-
Section 15: Enums, Symbols, and Special Types
-
Section 16: Working with Async Code
-
Section 17: TypeScript and the DOM
-
Section 18: Advanced Generics Practice
-
Section 19: Working with Third-Party Types
-
Section 20: Monorepo and Large-Scale Practices
-
Section 21: Common Pitfalls and Best Practices
-
Section 22: Interview Practice
-
Section 23: Handbook: Narrowing In Depth
-
Section 24: Handbook: Object Types In Depth
-
Section 25: Handbook: Classes In Depth
-
Section 26: Handbook: Modules In Depth
-
Section 27: Handbook: Declaration Files In Depth
-
Section 28: JSX and Namespaces
-
Section 29: Compiler Configuration Reference
-
Section 30: More Practice Exercises
-
Section 31: Handbook: Everyday Types Deep Dive
-
Section 32: Utility Types Full Reference
-
Section 33: Decorators Reference
-
Section 34: Mixins and Advanced OOP Patterns
-
Section 35: Iterators and Generators Typing
-
Section 36: More Type-Level Programming Practice
-
Section 37: TypeScript Ecosystem Tools
-
Section 38: TypeScript with Testing Frameworks
-
Section 39: TypeScript for Library Authors
-
Section 40: More Interview Practice
-
Section 41: Handbook: Functions In Depth
-
Section 42: Handbook: Type Manipulation Deep Dive
-
Section 43: More Real-World Patterns
-
Section 44: TypeScript Release Notes Highlights
-
Section 45: Final Practice and Review
44: Parameter Properties in Constructors
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
healthin 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
skushould bereadonlyandpublic. - The
quantityshould bepublic. - The
internalLocationCodeshould beprivate. - 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
There are no comments for now.