Initializing a class
To initialize a class, we first need to define a class, then its members, and then we can create an object for that class using the new
keyword.
class Point {
x: number,
y: number
}
let pt = new Point();
pt.x = 0
pt.y = 0
We can also initialize a class with default values, or without type annotations, but then, the member of the class will be implicit any.
class Point {
x = 0,
y = 0
}
let pt = new Point();
console.log(pt.x, pt.y)
We can specify strict property initialization within the typescript configuration file.
// ---strictPropertyInitialization
class GoodGreeter {
name: string;
constructor() {
this.name = "hello";
}
}
Definite Assignment Assertion Operator
If you plan to initialize a certain property inside a class in another way that is not a constructor, then make sure to use the !
operator to make sure Typescript understand that this is going to be assigned later.
class OKGreeter {
// Not initialized, but no error
name!: string;
}
Readonly Properties
If we want only a class to contain a readonly property, we can use the readonly
keyword to make it read only. We also can pass the initial value in the Object Instantiation and then make it readonly.
class Greeter {
readonly name: string = "world";
constructor(otherName?: string) {
if (otherName !== undefined) {
this.name = otherName;
}
}
err() {
this.name = "not ok";
}
}
let g = new Greeter("Pedro");
g.name // Pedro
g.name = "Juan" // Error, cannot assign value to readonly property.
Constructor Overloads
Two functions can have the same name if the number and/or type of arguments passed is different. This is called a function overload.
We can pass multiple constructor functions, only if the functions parameters are different from one another.
In languages like C++, you can define multiple implementations of the same function, just the return type is different.
But in the case of typescript, we can define the overloads, but only one implementation.
class Point {
// Overloads
constructor(x: number, y: string);
constructor(s: string);
constructor(xs: any, y?: any) {
// TBD
}
}
Extending a class
As programmers, we need to look for ways to re-use our code so we can save up some space and make easier the development process.
Sometimes, we want objects to behave a certain way, so we have classes, that serves us as a way to create objects from a template.
But sometimes, we want those templates in certain objects to do more than the things those classes are defined to do, we want to extend the functionality from those objects.
In this case, we say that we want to extend
the functionality of a class.
class Person {
readonly name: string,
readonly age: number
constructor(name: string, age: number){
this.name = name;
this.age = age;
}
}
class Employee extends Person{
jobTitle: string,
constructor(name:string, age:number, job:string){
super(name, age);
this.jobTitle = job
}
}
The super() method of a class.
When we are extendidng a class, we need to call the constructor of the parent object to access its properties. We do this with the super() method.
super
is an object that can access functions on the parent object.
The super() method, calls the constructor of the parent object.
There is a rule to remember: We need to call super() before accessing any reference to this
.
Extends vs Implements in OOP
Implements: Check if a certain subclass is using implementing correctly another class.
interface Pingable {
ping(): void;
}
class Sonar implements Pingable {
ping() {
console.log("ping!");
}
}
class Ball implements Pingable {
pong() { // Class 'Ball' incorrectly implements interface 'Pingable'. Property 'ping' is missing in type 'Ball' but required in type 'Pingable'.
console.log("pong!");
}
}
Implements
Implements is only to check that certain subclass is implementing the parent class correctly, is not going to change any types inside it.
interface Checkable {
check(name: string): boolean;
}
class NameChecker implements Checkable {
check(s) { // Parameter 's' implicitly has an 'any' type.
// Notice no error here
return s.toLowercse() === "ok";
}
}
Classes can also implement multiple interfaces.
Implementing a class with optional properties will not create that property in the derived subclass
interface A {
x: number;
y?: number; // This is optional, not required to check in implementation.
}
class C implements A {
x = 0;
}
const c = new C();
c.y = 10;
// Property 'y' does not exist on type 'C'.
Extends
Whereas implements is only to check if a certain class is implementing
the properties and functions correctly, extends will pass all of the properties and methods to the derived subclass. And can be used to define additional members.
class Animal { // Base Class
move() {
console.log("Moving along!");
}
}
class Dog extends Animal { // Contains access to move()
woof(times: number) {
for (let i = 0; i < times; i++) {
console.log("woof!");
}
}
}
const d = new Dog();
// Base class method
d.move();
// Derived class method
d.woof(3);
Methods in Typescript Classes
Methods are functions defined inside classes, and properties are variables that contains some sort of data.
class Person {
name:string // Property
constructor(name:string){
this.name = name;
}
greet(): void { // Method
console.log(`Hello! My name is ${this.name}`)
}
}
class Point {
x = 10; // properties
y = 10;
scale(n: number): void { // methods
this.x *= n;
this.y *= n;
}
}
To access the properties of an object, is mandatory to use the this
keyword, this will modify the context where the property is being called to use the class scope, instead of any global scope.
Getters and Setters in Typescript
Getters and Setters are special methods inside a class that allows a class to protect the data from manipulation in an outside context that is not the class definition.
Getters and Setters are known also as classes accessors
.
class C {
_length = 0;
get length() { // We use the `get` keyword to specify that this is going to be a member that when is accessed, is going to return the length of the property.
return this._length;
}
set length(value) { // Here we can call .length() to set the property value.
this._length = value;
}
}
By convention, the methods that are private inside a class
There are special rules when using getters and setters in Typescript:
- If get exists but no set, the property is automatically readonly
- If the type of the setter parameter is not specified, it is inferred from the return type of the getter
- Getters and setters must have the same Member Visibility
Note: Is also possible to use get and set with multiple types
class Thing {
_size = 0;
get size(): number {
return this._size;
}
set size(value: string | number | boolean) {
let num = Number(value);
// Don't allow NaN, Infinity, etc
if (!Number.isFinite(num)) {
this._size = 0;
return;
}
this._size = num;
}
}
Overriding methods in extended classes
class Base {
greet() {
console.log("Hello, world!");
}
}
class Derived extends Base {
greet(name?: string) {
if (name === undefined) {
super.greet();
} else {
console.log(`Hello, ${name.toUpperCase()}`);
}
}
}
const d = new Derived();
d.greet();
d.greet("reader");
In the previous example, if the name is not provided in the derived greet() method, It will make use of the super.greet() (parent) method.
We have 4 types of member visibility.
Public: By default, all of the members of a class, are public by default, is even not required to write the keyword
public
, but you might do it for style/readability reasons.Protected: These members of a class, are only accesible within the class or derived subclasses.
This will mean that you can't access them outside the class definition.
class Person {
public name: string,
protected bloodType: string,
constructor(name: string, bloodType: string){
this.name = name
}
}
class Employee extends Person {
public job: string,
constructor(name: string, bloodType: string, job: string){
super(name, bloodType);
this.job = job;
}
}
let emp = new Employee("Bob","O-","Software Engineer");
emp.job // "Software Engineer"
emp.bloodType // Property 'bloodType' is protected and only accessible within class 'Greeter' and its subclasses.
If a protected member in a base class is not protected in a derived subclass, then this member will proceed to be a public member.
class Base {
protected m = 10;
}
class Derived extends Base {
// No modifier, so default is 'public'
m = 15;
}
const d = new Derived();
console.log(d.m); // OK
- Private: Same deal as protected, but in this case, it doesn't allow the other derived subclasses to access these members.
There is a way to make a member hard private
, with the #
operator:
class Dog {
#barkAmount = 0;
personality = "happy";
constructor() {}
}
- Static: Any member marked as static can be accessed inside or outside the class definition, and the feature is that you can access this methods without having to instantiate the class object.
class MyClass {
static x = 0;
static printX() {
console.log(MyClass.x);
}
}
console.log(MyClass.x);
MyClass.printX();
Stay Tunned for Part 2!
Top comments (0)