TypeScript
Completed
-
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
153: Parameter Properties Revisited
I've spent a lot of time reviewing PRs from developers moving from Java or C# to TypeScript, and there is one specific pattern that almost always pops up when they start working with classes. They'll write a constructor, give the arguments types, and then act genuinely confused when those values aren't available in other methods of the class.
Thinking Type Annotations Create Class Members
Here is the classic mistake. You'll see something like this in a codebase:
class UserSession {
constructor(userId: string, token: string) {
// The developer thinks they've "defined" the properties here
}
isValid() {
// Error: Property 'token' does not exist on type 'UserSession'
return this.token !== null;
}
}
The misconception here is that by adding : string to the constructor argument, you've somehow told TypeScript to create a member variable for the class. You haven't. In this snippet, userId and token are just local variables. They exist only for the duration of the constructor's execution. Once the object is instantiated, those values are gone unless you manually assign them to this.
Now, you could fix this the long way by declaring the properties at the top of the class and doing the boring this.x = x dance. But that's exactly why parameter properties exist.
Using Access Modifiers as Declarations
To turn a constructor argument into a full-fledged class property, you have to prepend it with an access modifier: public, private, protected, or readonly. When you do this, TypeScript does two things behind the scenes: it declares the member on the class, and it automatically assigns the incoming argument to that member.
Let's rewrite that UserSession class using this shorthand:
class UserSession {
// By adding 'private', we've declared the property and assigned it in one go
constructor(private userId: string, public token: string) {}
isValid() {
// Now this works perfectly!
return this.token !== null;
}
}
I personally love this feature because it removes a massive amount of boilerplate. However, a word of caution: don't overdo it if your constructor is taking ten different arguments. While it's concise, a constructor with ten private parameters can become a readability nightmare. In those cases, I usually recommend passing a single "options" object instead.
One last detail: readonly is a powerful addition here. If you know the userId should never change after the session is created, use readonly. It gives you the assignment shorthand while ensuring that no other method in your class accidentally overwrites the ID.
public: Accessible from anywhere.private: Accessible only within the class.protected: Accessible within the class and its subclasses.readonly: Cannot be modified after initialization.
📋 Practical Task
Refactoring the InventoryManager for Conciseness
You have been handed a legacy InventoryManager class that is far too verbose. Currently, it manually declares every property and assigns them in the constructor. Your task is to refactor this class to use Parameter Properties to reduce the boilerplate while maintaining the same visibility levels.
class InventoryManager {
public storeName: string;
private warehouseId: number;
protected regionCode: string;
readonly apiKey: string;
constructor(storeName: string, warehouseId: number, regionCode: string, apiKey: string) {
this.storeName = storeName;
this.warehouseId = warehouseId;
this.regionCode = regionCode;
this.apiKey = apiKey;
}
getDetails() {
return `${this.storeName} (ID: ${this.warehouseId})`;
}
}
Requirements:
- Remove the explicit property declarations at the top of the class.
- Remove the manual assignments inside the constructor body.
- Use access modifiers within the constructor parameters to achieve the exact same functionality.
- The resulting constructor body should be empty.
There are no comments for now.