DEV Community

Pedro Uzcátegui
Pedro Uzcátegui

Posted on

Typescript Classes Pt.1

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

We can specify strict property initialization within the typescript configuration file.


// ---strictPropertyInitialization
class GoodGreeter {
  name: string;

  constructor() {
    this.name = "hello";
  }
}
Enter fullscreen mode Exit fullscreen mode
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;
}
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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
  }
}
Enter fullscreen mode Exit fullscreen mode

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
    }
}
Enter fullscreen mode Exit fullscreen mode

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!");
  }
}
Enter fullscreen mode Exit fullscreen mode
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";
  }
}
Enter fullscreen mode Exit fullscreen mode

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'.
Enter fullscreen mode Exit fullscreen mode
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);
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode
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");
Enter fullscreen mode Exit fullscreen mode

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.

  1. 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.

  2. 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.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
  1. 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() {}
}
Enter fullscreen mode Exit fullscreen mode
  1. 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();
Enter fullscreen mode Exit fullscreen mode

Stay Tunned for Part 2!

Top comments (0)