Hands-On ASP.NET Core 3.1 : Learn MVC, Razor Pages, Web API, Entity Framework Core, and Blazor. Private online coaching for software developers. Click here to know more.

Use inheritance, abstract classes, and polymorphism in TypeScript

Object oriented programming languages such as C# allow you to inherit functionality from base class. TypeScript also offers inheritance capabilities so that your classes can inherit from other classes. TypeScript doesn't support multiple inheritance (although your class can implement multiple interfaces). You can also create abstract classes - classes that can't be instantiated on their own; they must be inherited in other classes. In this article you will learn all these aspects of TypeScript inheritance.

Let's get started.

I am going to assume that you have worked though the examples discussed in the previous articles of this article and video series. If that's not the case, I suggest you read earlier parts of this series to learn the other basics of TypeScript we have been discussing so far.

Add a new TypeScript file called Inheritance.ts and also an HTML file named Inheritance.html

Suppose you are building a contact management system and you want to create two classes - PersonalContact and ProfessionalContact in your system. The PersonalContact class is a general purpose class that represents a personal contact such as a friend or a family member. The ProfessionalContact represents a business contact such as a colleague or a financial consultant. These two contacts share a few common things such as first name and last name. So, we will create a class named Contact that wraps this common functionality. Then we inherit the Contact class in two derived classes namely PersonalContact and ProfessionalContact.

The Contact class is shown below:

class Contact {

    protected contactID: number;
    protected firstName: string;
    protected lastName: string;

    constructor(contactID: number,
firstName: string, 
lastName: string) {
        this.contactID = contactID;
        this.firstName = firstName;
        this.lastName = lastName;
    }

    getFullName(): string {
        return this.firstName + " " +
 this.lastName;
    }
}

The Contact class declares three properties - contactID, firstName, and lastName. Notice that they are marked with protected keyword. Earlier in this series you were introduced to public and private keywords. The protected access modifier indicates that the member will be accessible in the class it is declared (Contact) and the sub-classes (PersonalContact and ProfessionalContact).

The Contact class has a constructor that accepts contactID, firstName, and lastName values. Inside, the values are stored in the respective properties.

The getFullName() function returns the full name of the person by concatenating firstName and lastName properties.

The PersonalContact and ProfessipnalContact classes require two enumerations that contain a list of possible contact types:

enum PersonalContactType {
    Friend = "Friend",
    FamilyMember = "Family Member",
    Acquaintance = "Acquaintance"
}


enum ProfessionalContactType {
    Financial = "Financial Services",
    Medical = "Medical Services",
    Consultant = "General Consultation",
    Other = "Other"
}

You are already familiar with string enums from the previous part of this article series. So, I am not going into details of PersonalContactType and ProfessionalContactType enums.

The Contact class is inherited by PersonalContact and ProfessionalContact classes. The PersonalContact class is shown below:

class PersonalContact extends Contact {

    private contactType: PersonalContactType;
    private email: string;
    private phone: string;
    private birthDate: Date;

    constructor(firstName: string, 
lastName: string, 
contactType: PersonalContactType, 
email:string, 
phone:string,
birthDate: Date) {
        super(contactID, firstName, lastName);
        this.contactID = contactID;
        this.contactType = contactType;
        this.email = email;
        this.phone = phone;
        this.birthDate = birthDate;
    }

    getDetails():string {
        let details: string;
        details = `#${this.contactID} 
${super.getFullName()} 
${this.contactType} 
${this.email} 
${this.phone}
${this.birthDate}`;
        return details;
    }


    getFullName(): string {
        return `${this.firstName} 
${this.lastName}, 
${this.birthDate}`;
    }
}

The PersonalContact class uses extends keyword to inherit from Contact class. Since PersonalContact is inheriting from the Contact class, contactID, firstName, and lastName properties are available to it. Additionally, PersonalContact declares contactType, email, phone, and birthDate properties.

Then we create the constructor for PersonalContact that accepts various properties as parameters. Inside, the code calls the base class constructor and passes contactID, firstName and lastNam to it. This is done using super keyword that represents the base class of the derived class. Then we set other properties to their respective values.

The PersonalContact class has a public method called getDetails() that returns all the property values of the object to the caller as a string. Notice how the code uses super keyword to invoke the base class getFullName() method.

You can also override a base  class method in the derived class. In this case, PersonalContact redefines getFullName method to include birthDate in the returned string. Due to this overriding, calling getFullName() on the PersonalContact object will invoke getFullName() as defined in the PersonalContact. In the absence of  overriding, it would have called the base class getFullName() method.

The other derived class - ProfessionalContact - is shown below:

class ProfessionalContact extends Contact {

    private contactType: ProfessionalContactType;
    private company: string;
    private emails: string[];
    private phones: string[];
    private notes: string;

    constructor(contactID: number,
        firstName: string,
        lastName: string,
        contactType: ProfessionalContactType,
        company: string,
        emails: string[],
        phones: string[],
        notes:string
    ) {
        super(contactID, firstName, lastName);
        this.contactType = contactType;
        this.company = company;
        this.emails = emails;
        this.phones = phones;
        this.notes = notes;
    }

    getDetails(): string {
        let details: string;
        details = `#${this.contactID} 
                    ${super.getFullName()} 
                    ${this.contactType} 
                    ${this.company}`;
        return details;
    }


    getFullName(): string {
        return `${this.firstName} 
${this.lastName}, ${this.company}`;
    }

}

This class is similar to the PersonalContact class but has a different set of additional properties - emails, phones, company and notes.

Now that you have two derived classes ready let's use them by calling their getDetails() method.

Add a function DoWork() in the Inheritance.ts file and write the following code in it:

function DoWork() {
    let contact: PersonalContact;
    contact = new PersonalContact(1, 
"Nancy", "Davolio", 
PersonalContactType.Acquaintance, 
["nancy@localhost"], ["0123456789"], new Date(1960, 10, 3));
    document.getElementById("msg").innerHTML 
= contact.getDetails();
}

The DoWork() function declares a variable of PeesonalContact type and creates an object of PersonalContact. All the property values are passed in the constructor while instantiating the object. Then getDetails() method of PersonalContact is called and the result is displayed in msg <div> element.

The DoWork() method is called as soon as the Inheritance.html page is loaded in the browser:

<body onload="DoWork()">
    <div id="msg"></div>
    <script src="/TypeScript/Output/Inheritance.js">
    </script>
</body>

The following figure shows a sample run of this code:

Now change the DoWork() code as follows:

function DoWork() {
    let contact: ProfessionalContact;
    contact = new ProfessionalContact(2, 
"Andrew", "Fuller", 
ProfessionalContactType.Consultant, 
"Northwind Traders", "andrew@localhost", 
"0123456789","Expert in international marketing");
    document.getElementById("msg").innerHTML = contact.getDetails();
}

And the outcome is like this:

TypeScript also supports polymorphism via inheritance. Let's illustrate that too using a simple fragment of code.

Modify DoWork() as shown below:

function DoWork() {
    let contact: Contact;
    let result: string;

    contact = new PersonalContact(1, "Nancy", 
"Davolio", PersonalContactType.Acquaintance, 
"nancy@localhost", "0123456789", new Date(1960,10,3));
    result = contact.getFullName();

    contact = new ProfessionalContact(2, 
"Andrew", "Fuller", ProfessionalContactType.Consultant, 
"Northwind Traders", "andrew@localhost", 
"0123456789", "Expert in international marketing");
    result += "<br />" + contact.getFullName();

    document.getElementById("msg").innerHTML = result;

}

This time the type of contact variable is base type Contact; it's not PersonalContact or ProfessionalContact.

Firstly, contact is assigned a new object of type PersonalContact. Although the type of contact variable is Contact you are able to store an object of PersonalContact in it because PersonalContact inherits from Contact.

Then getFullname() method is called. Recollect that Contact has its own implementation of getFullName() that returns firstName and lastName to the caller. And PersonalContact also has its own implementation of getFullName() that returns firstName, lastName, and birthDate. So, when you call getFullName() on the contact object which implementation will be invoked? As you might have guessed, although contact's type is Contact, here getFullName() of PersonalContact is called because contact holds an object of type PersonalContact.

Then the code assigns an object of ProfessionalContact to the contact variable. This assignment is valid because ProfessionalContact also inherits from Contact. The code then invokes the getFullName() method. This time getFullName() of ProfessionalContact gets executed.

If you output the result in the msg <div>, the following will be displayed:

In our example discussed above, we don't need to instantiate Contact class directly. We need to instantiate either PersonalContact or ProfessionalContact. So, in this case we can make the Contact class abstract. An abstract class can contain properties and methods like any other class but it can't be instantiated. To use an abstract class you need to inherit from it and the derived class thus created can be instantiated. An abstract class can also have abstract methods. An abstract method must be implemented by the derived classes.

The modified Contact class is shown below:

abstract class Contact {
    protected contactID: number;
    protected firstName: string;
    protected lastName: string;

    constructor(contactID:number,
firstName: string, 
lastName: string) {
        this.contactID = contactID;
        this.firstName = firstName;
        this.lastName = lastName;
    }

    getFullName(): string {
        return this.firstName + " " + 
this.lastName;
    }

    abstract getDetails(): string;
}

As you can see, the Contact class is now marked with abstract keyword. Properties and methods of the Contact class are quite similar to the previous case but this time getDetails() abstract method has been added to the class. An abstract method doesn't have an implementation. The derived classes provide the respective implementation. In our example PersonalContact and ProfessionalContact classes contain getDetails() implementation.

You can also watch the companion video here.

That's it for now! Keep coding!!


Bipin Joshi is an independent software consultant, trainer, author, yoga mentor, and meditation teacher. He has been programming, meditating, and teaching for 24+ years. He conducts instructor-led online training courses in ASP.NET family of technologies for individuals and small groups. He is a published author and has authored or co-authored books for Apress and Wrox press. Having embraced the Yoga way of life he also teaches Ajapa Yoga to interested individuals. To know more about him click here.

Get connected : Facebook  Twitter  LinkedIn  YouTube

Posted On : 04 May 2020


Tags : ASP.NET ASP.NET Core MVC .NET Framework JavaScript Visual Studio


Subscribe to our newsletter

Get monthly email updates about new articles, tutorials, code samples, and how-tos getting added to our knowledge base.

  

Receive Weekly Updates