Open In App

Cross Referencing With Extended Classes In TypeScript

Last Updated : 16 Aug, 2024
Comments
Improve
Suggest changes
Like Article
Like
Report

TypeScript has features like inheritance, allowing you to extend classes. When dealing with more complex applications, you might encounter cases where classes need to reference each other or even work in a cross-referenced manner. This is common in systems where different classes are interdependent, such as a relationship between different entities or modules.

Basics of Class Extension

In TypeScript deriving from a base class involves simple class extension where a derived class inherit properties, and methods of its parent.

Example: In this example we have Dog extending Animal so that the former inherits name property and speak function from Animal, besides this the Dog adds fetch function of its own, when myDog.speak() is called then “Rex barks” will be displayed because the method has been overridden, the fetch method is unique to the Dog class and outputs “Rex is fetching.”

JavaScript
class Animal {
    constructor(public name: string) { }

    speak(): void {
        console.log(`${this.name} makes a sound.`);
    }
}

class Dog extends Animal {
    constructor(name: string, public breed: string) {
        super(name);
    }

    speak(): void {
        console.log(`${this.name} barks.`);
    }

    fetch(): void {
        console.log(`${this.name} is fetching.`);
    }
}

const myDog = new Dog("Rex", "Labrador");
myDog.speak(); // Rex barks.
myDog.fetch(); // Rex is fetching.

Output

Rex barks.
Rex is fetching.

Cross-Referencing Inherited Members

In TypeScript there are mechanism to ensure that types of base class and derived class match as well as they can be used correctly in cross-referencing.

1. Accessing Base Class Properties and Methods

You can directly access properties and methods of base class using derived class, the type system of TypeScript guarantees that these members have correct types.

Example: In this example, Cat is a class and it extends Animal, it inherits from Animal its name property and speak method, in Cat, makeSound invokes a speak inherited from Animal, if myCat.makeSound() is called, it prints “Whiskers makes a sound”, which means that base class method was successfully accessed.

JavaScript
class Animal {
    constructor(public name: string) { }

    speak(): void {
        console.log(`${this.name} makes a sound.`);
    }
}

class Cat extends Animal {
    constructor(name: string) {
        super(name);
    }

    makeSound(): void {
        this.speak(); // Accessing base class method
    }
}

const myCat = new Cat("Whiskers");
myCat.makeSound(); // Whiskers makes a sound.

Output

Whiskers makes a sound.

2. Overriding Methods

TypeScript ensures that when you override methods in any derived classes it checks if their signatures are compatible with those of base classes ones, the keyword override should be used to specify that a method is overriding another one belonging to the base object.

Example: In this example Dog class overrides speak method from the Animal class, the override keyword ensures that method signatures match and that the base method is being replaced, when myDog.speak() is called, it outputs "Dog barks," demonstrating the overridden behavior.

JavaScript
class Animal {
    speak(): void {
        console.log("Animal sound.");
    }
}

class Dog extends Animal {
    override speak(): void {
        console.log("Dog barks.");
    }
}

const myDog = new Dog();
myDog.speak(); // Dog barks.

Output

Dog barks.

3. Type Compatibility with Polymorphism

Type system of TypeScript facilitate polymorphism whereby base class type can refer to derived class instances and this guarantees that methods and properties are used correctly across various class types.

Example: In this example makeAnimalSpeak is a function that accepts an Animal type and calls its speak method, both Dog and Cat extend Animal and provide their specific implementations of speak, when makeAnimalSpeak(myDog) is called, it outputs "Rex barks," and when makeAnimalSpeak(myCat) is called, it outputs "Whiskers meows" showcasing polymorphism and correct method invocation based on instance type.

JavaScript
class Animal {
    constructor(public name: string) { }

    speak(): void {
        console.log(`${this.name} makes a sound.`);
    }
}

class Dog extends Animal {
    constructor(name: string, public breed: string) {
        super(name);
    }

    speak(): void {
        console.log(`${this.name} barks.`);
    }

    fetch(): void {
        console.log(`${this.name} is fetching.`);
    }
}

class Cat extends Animal {
    constructor(name: string) {
        super(name);
    }

    speak(): void {
        console.log(`${this.name} meows.`);
    }
}

function makeAnimalSpeak(animal: Animal): void {
    animal.speak();
}

const myDog = new Dog("Rex", "Labrador");
const myCat = new Cat("Whiskers");

makeAnimalSpeak(myDog); // Rex barks.
makeAnimalSpeak(myCat); // Whiskers meows.

Output

Rex barks.
Whiskers meows.

Advanced Cross-Referencing Techniques

1. Abstract Classes and Methods

Abstract classes define methods that must be implemented by derived classes, this ensures derived classes provide particular implementations of abstract methods.

Example: In this example Shape class is abstract and defines an abstract method getArea that must be implemented by derived classes, Rectangle class extends Shape and provides an implementation for getArea, when myRectangle.printArea() is called, it outputs "The area is 200," showing that abstract method has been correctly implemented.

JavaScript
abstract class Shape {
    abstract getArea(): number;

    printArea(): void {
        console.log(`The area is ${this.getArea()}`);
    }
}

class Rectangle extends Shape {
    constructor(private width: number, private height: number) {
        super();
    }

    getArea(): number {
        return this.width * this.height;
    }
}

const myRectangle = new Rectangle(10, 20);
myRectangle.printArea(); // The area is 200

Output

The area is 200

2. Generics with Inheritance

Generics can be used in conjunction with inheritance to create flexible and reusable class structures, TypeScript generics are very influential in this language because they help you to manufacture components, functions and classes that can be used with different types of data, these generics make it possible for us to design type safe class hierarchies that can be adjusted even when combined with inheritance.

Example: In this example Repository class is a generic class that can store items of any type T, UserRepository class extends Repository with a specific type User, when a User instance is added to userRepository, it is correctly stored and retrieved by demonstrating how generics and inheritance can work together to create flexible data structures.

JavaScript
class Repository<T> {
    private items: T[] = [];

    add(item: T): void {
        this.items.push(item);
    }

    get(index: number): T | undefined {
        return this.items[index];
    }
}

class User {
    constructor(public name: string) { }
}

class UserRepository extends Repository<User> { }

const userRepository = new UserRepository();
userRepository.add(new User("GeeksForGeeks"));
console.log(userRepository.get(0)?.name);

Output

GeeksForGeeks

Next Article

Similar Reads