What are Classes
Object oriented programming is where you work with “real life entities” in your code. Objects are things you work with in the code, and are instances of classes (meaning based on classes). Classes are the blueprints and start with an uppercase character.
Creating a First Class
class Department {
// define fields here
name: string
constructor(n: string) {
// store the value
this.name = n;
}
}
const accounting = new Department("Accounting")
Compiling to JavaScript
What we see after compilation, in the “app.js” file, is missing the “type assignment” and some other things. If you change the “target” from “es6” to “es5” we see the older “constructor function” syntax.
Constructor Functions and the “this” Keyword
Can add “methods” to classes as well.
class Department {
name: string
constructor(n: string) {
this.name = n;
}
// this is a method
describe() {
console.log('Deaprtment: ' + this.name)
}
}
const accounting = new Department("Accounting")
The “this” keyword can be tricky. Typically “this” points to the thing that is calling it.
Can add “this” as a parameter to a method.
class Department {
name: string
constructor(n: string) {
this.name = n;
}
// this is a method
describe(this: Department) {
console.log('Deaprtment: ' + this.name)
}
}
const accounting = new Department("Accounting")
Public and Private Access Modifiers
When building more complex applications, you want to make sure there is only one clear path, one uniform way, to accessing (to add or to update) properties or methods on a “class.”
This is especially important if on a team and you want one uniform way of doing something.
The “private” keyword can be added before the property name, making it only accessible from inside of that class.
The “public” keyword means properties and methods are accessible outside of the class. This is the default so you don’t need to add that.
JavaScript doesn’t know “public” and “private” keywords.
Shorthand Initialization
Prevent duplicate code (at the top and then again in the constructor) by just adding an access modifier keyword in front of parameter in the constructor function. In this case you do have to add the “public” keyword.
class Department {
// no longer needed, defining the type in the parameter
// private id: string
// private name: string
private employees: string[] = []
constructor (private id: string, public name: string) {
// now we don't need to define here
// this.id = id
// this.name = n
}
}
“readonly” Properties
Another modifier that means a field shouldn’t change after initialization (good for ids). Fails if you try to write to the property. Does not exist in JS like “public/private.” Extra type safety and make intentions clear. Will be used quite often.
Inheritance
A good example of inheritance is having different departments where all departments share some base characteristics but each department also has their own unique characteristics.
Can only inherit from one class, cannot inherit from multiple classes.
Need to add “super” keyword, and execute it like a function, whenever you have your own constructor in one class that is inheriting from another class.
Add the “super” keyword in the inheriting class and execute it like a function to call the constructor of the base class. Call “super” first before doing anything with “this” keyword.
class ITDepartment extends Department {
admins: string[]
constructor(id: string, admins: string []) {
super(id, "IT")
this.admins = admins
}
}
Overriding Properties and the “protected” Modifier
The “protected” keyword is like the “private” access modifier, but grants access to properties and methods if using inheritance. Can access and override properties or methods. Can be helpful to override properties or methods of the base class. Also only available in TS.
Getters and Setters
Getter is a property where you execute a function, or a method, when you retrieve a value and that allows you as a developer, to add more complex logic. Uses the “get” keyword and any name of your choice with parentheses and curly braces. A “getter” method has to return something. Access like a normal property and not with parentheses like you would access a method. Behind the scenes it executes.
Setter is added almost the same way as “get,” but instead use the “set” keyword and name of your choice with parentheses and curly braces. Pass in the value want to store. Add some manipulation before storing. Also access like a property and not with parenthesis like you would normally access a method.
These features of classes exist in JS and TS. Can be great for encapsulating logic. Define like methods but access like properties.
Static Properties and Methods
Allows you to add properties and methods to classes, that are not accessed on instance of the class but accessed directly on the class.
Often used for utility functions that you want to group or map to a class logically or a global constant that you want to store in a class. An example of this is the built-in Math constructor, (“Math.round(),” “Math.random(),” etc). Don’t want to instantiate a class just to call a utility method.
Add the “static” keyword when defining your function. Example add a “createEmployee()” method where we can call directly on the class by using “Department.createEmployee().”
We can also do this for properties and not just methods.
One thing to keep in mind is that you can’t access static properties and methods from “non-static” parts without using the class name (Accessing from inside of the class, say from inside of the constructor, “this.property” would not work and would instead have to be “Department.property”).
Exists in TS and JS (es6 or later).
Abstract Classes
Force developers working with a certain class to add and override a certain method when using inheritance.
We use this when you want to ensure a certain method is available on all classes based on some base class and you know the exact implementation will depend on the specific version.
Want to force it to exist but the inheriting class will need to provide its own implementation.
Have an empty method in the base class and force all classes based on the class to add and override the method by using the “abstract” keyword before the method.
describe() {
}
//becomes
abstract describe(this.Department): void
Use this when you want classes to share common property or method but you don’t want to have to provide concrete or base value in the base class but instead the inheriting class has to do it.
Classes marked with the “abstract” keyword cannot be instantiated themselves. It’s only purposes is to be there so it can be inherited from.
Singletons & Private Constructors
A pattern in object-oriented programming called the “Singleton” pattern where we ensure there is only exactly one instance of a certain class. Can’t create multiple objects only want exactly one object, for example only want there to be only one accounting department.
Add the “private” keyword in front of the constructor and now we cannot instantiate the class with the “new” keyword. It is now only accessible form inside of the class. We can get inside the class with “static” methods, as a “static” method can be called on the class itself so you don’t instantiate it for that.
Can put “private” keyword in front of the “static” keyword. An example is to put “private” in front of constructor function. Now use “static” method to instantiate it.
class AccountingDepartment extends Department {
private static instance: AccountingDepartment
private constructor(id: string, private reports: string[]) {
super(id, "Accounting")
this.lastReport = reports[0]
}
static getInstance() {
if (AccountingDepartment.instance) {
return this.instance
} else {
this.instance = new AcountingDepartment("d2", [])
return this.instance
}
}
}
const accounting = AccountingDepartment.getInstance()
We won’t use this approach all the time but we should know about this pattern.
Classes – A Summary
Important to understand classes in JS and TS.
A First Interface
We use “interfaces” to describe the structure of an object. This is powerful feature to describe your classes in a very clear manner. Unlike class, this will not be used as a blueprint just as a custom type so to speak. Have property names and types of values stored in the “interface” but we don’t define concrete values.
interface Person {
name: string
age: number
greet(phrase: string): void
}
We can do the same for methods, providing a name, structure and return type. We can now use this to “type check” an object. We can now use our “interface” to define a “type.”
let user1: Person // will instantiate later
// it is now later
user1 = {
name: "Max",
age: 30,
greet(phrase: string) {
console.log(phrase + " " + this.name)
}
}
user1.greet("Hi there - I am")
“Interfaces” allow you to define the structure of an object and only exists in TS and not JS.
Using Interfaces with Classes
Why do we even need “interfaces” can’t we just use a “custom type?”
“Interfaces” can only be used to describe the structure of an object, whereas “custom types” can define other things, like “union types.” A “custom type” may be more flexible but an “interface” is clearer because you can only define objects so “interfaces” are more common than “custom types.”
Can implement an “interface” within a class. “Interface” can be used as a sort of contract a class can implement and that a class can adhere to.
We can use the “implements” keyword to inherit from multiple classes by simply separating them using commas.
interface Greetable {
name: string
greet(phrase:string): void
}
class Person implements Greetable {
name: string
constructor(n: string) {
this.name = n
}
greet(phrase: string) {
console.log(phrase + " " + this.name)
}
}
let user1: Greetable
user1 = new Person("Max")
user1.greet('Hit there - I am')
console.log(user1)
“Interfaces” are often used to share functionality amongst different classes not regarding there concrete implementation or values but the structure.
What is the difference between “interfaces” and “abstract classes?”
An “interface” has no implementation details at all whereas “abstract classes” can be a mixture of “you have to override this part” or “you have a concrete implementation part.”
Why Interfaces?
Want to ensure existence and force certain structure. May have code that relies on that structure. Powerful and flexible code. Example: Class has to have a “greet method” but you can implement greet however you like.
Readonly Interface Properties
Inside of an interface you can add or implement the “readonly” modifier. Cannot add “public” or “private” keywords.
Can also do this on “custom type.”
Extending Interfaces
Can implement “inheritance” in “interfaces” with the “implements” keyword and you can “inherit” from multiple “interfaces.”
interface Named {
readonly name: string
}
interface Greetable {
greet(phrase:string): void
}
class Person implements Greetable, Named {
name: string
constructor(n: string) {
this.name = n
}
greet(phrase: string) {
console.log(phrase + " " + this.name)
}
}
Can also have one “interface” use “inheritance” with another “interface” with the “extends” keyword, (can have an “interface” that “extends” multiple “interfaces, merging multiple interfaces into one, by separating with comma). Remember that with classes this is not possible, as you can only inherit from one other class and not from multiple classes.
interface Named {
readonly name: string
}
interface Greetable extends Named {
greet(phrase:string): void
}
class Person implements Greetable {
name: string
constructor(n: string) {
this.name = n
}
greet(phrase: string) {
console.log(phrase + " " + this.name)
}
}
We would use this approach of splitting “interfaces” when on some objects you need both and other objects you just need one.
“Extending Interfaces” is a pure TS feature.
Interfaces as Function Types
Interfaces can also be used to define the structure of a function. Functions are technically objects so this is an exception to the rule that “interfaces only describe the structure of objects.”
Use colon instead of an arrow to create an anonymous function.
interface AddFn {
(a: number, c:number): number
}
This is an alternative to “custom type” but using the “custom type” to define the structure of a function is a bit more common and has a bit more concise syntax.
Although using a “custom type” to define a function is the more popular approach, this is nice syntax to be aware of.
Optional Parameters and Properties
Going back to “interfaces” for objects, you can also define optional properties by adding a question mark after the property name.
interface Named {
readonly name: string
outputName?: string
}
Can also mark methods as optional.
myMethod?() { ... }
In a class you can also have an optional property (properties above the constructor function).
class person implements Named {
name?: string
age =30
constructor(n?:string) {
if (n) {
this.name = n
}
}
}
Can have optional parameters in the constructor function as well either by providing a default or fallback value or by adding a question mark (which makes the default value be “undefined”).
class person implements Named {
name?: string
age =30
constructor(n?:string) {
if (n) {
this.name = n
}
greet(phrase: string) {
if (this.name) {
console.log(phrase + " " + this.name)
} else {
console.log("Hi.") // no reference to the name since it is undefined because it was called without a name because name is optional
}
}
}
user1.greet("Hi there - I am")
Can also have optional parameters in methods.
Compiling Interfaces to JavaScript
For interfaces there are no translations to JS as it’s a pure TS feature for the dev environment to help you write better code, more clearly-structured code, and adhering to your rules. Interfaces are dumped in compilation.