The circumstance where an object’s type is decided by its behavior, like methods and attributes, rather than its class is known as “duck typing”.
The usage of interfaces in TypeScript makes duck typing possible. Where interface means the set of methods and characteristics an object must have to fall under that type are described.
For example, if an interface defines the function, any object with the method called “myFunc()” may be treated as belonging to a specific kind, regardless of its class.
Duck typing emphasizes assessing an object’s suitability for a task by considering its methods and attributes instead of its actual type. An interface explains the set of properties and methods an object must have to be considered “duck typed” for a particular purpose.
Benefits of Duck Typing
One of duck typing’s main benefits is making code more flexible and reusable. The code works with any object with the required methods and properties rather than just particular types of objects and may be used in various situations without requiring modification. Duck typing also improves code reuse by enabling the interchangeable usage of objects of diverse kinds within a single codebase.
Exmaples of Duck Typing is TypeScript
Here is an example of how to use duck typing in TypeScript
Define an interface that represents the behaviour you want an object to have. For example
interfaceDuck{quack():void;}
Create a class that implements the interface. For example
classMallardDuckimplementsDuck{quack():void{
console.log("Quack!");}}</code></pre>
Create an instance of the class and use it as the type defined by the interface.
let duck: Duck =newMallardDuck();
duck.quack();// Output: "Quack!"
Create another class that also implements the interface
classRubberDuckimplementsDuck{quack():void{
console.log("Squeak!");}}</code></pre>
Use the new class instance as the same type defined by the interface.
let duck: Duck =newRubberDuck();
duck.quack();// Output: "Squeak!"
As you can see, both MallardDuck and RubberDuck classes implement the Duck interface, and the duck variable can be assigned to instances of both classes. The type is determined by the behaviour (methods and properties) defined in the interface rather than the class.
It's also important to note that in typescript, you can use the typeof keyword to check the type of object in runtime and if the object has the expected method or property.
Example
In this example, the Bird and Plane classes implement the Flyable interface, which requires a fly() method. Both "duck types" can be used interchangeably in the goFly() function. The function doesn't care about the actual type of the object passed to it as long as it has a fly() method that can be called.
console.log("Bird is flying");}}classPlaneimplementsFlyable{fly():void{
console.log("Plane is flying");}}functiongoFly(flyable: Flyable){
flyable.fly();}let bird =newBird();let plane =newPlane();goFly(bird);// Prints "Bird is flying"goFly(plane);// Prints "Plane is flying"
On compiling, it will generate the following JavaScript code
var Bird =/** @class */(function(){functionBird(){}
Bird.prototype.fly=function(){console.log("Bird is flying");};return Bird;}());var Plane =/** @class */(function(){functionPlane(){}
Plane.prototype.fly=function(){console.log("Plane is flying");};return Plane;}());functiongoFly(flyable){
flyable.fly();}var bird =newBird();var plane =newPlane();goFly(bird);// Prints "Bird is flying"goFly(plane);// Prints "Plane is flying"
Output
The above code will produce the following output
Bird is flying
Plane is flying
Example
Overall, duck typing is a powerful programming concept that allows for greater flexibility and reusability in TypeScript code by allowing objects of different types to be used interchangeably as long as they have the same methods and properties. In this example, the Driveable interface, Car and Truck classes show the same thing.
console.log("Car is driving");}}classTruckimplementsDriveable{drive():void{
console.log("Truck is driving");}}functiongoDrive(driveable: Driveable){
driveable.drive();}let car =newCar();let truck =newTruck();goDrive(car);// Prints "Car is driving"goDrive(truck);// Prints "Truck is driving"
On compiling, it will generate the following JavaScript code
var Car =/** @class */(function(){functionCar(){}
Car.prototype.drive=function(){console.log("Car is driving");};return Car;}());var Truck =/** @class */(function(){functionTruck(){}
Truck.prototype.drive=function(){console.log("Truck is driving");};return Truck;}());functiongoDrive(driveable){
driveable.drive();}var car =newCar();var truck =newTruck();goDrive(car);// Prints "Car is driving"goDrive(truck);// Prints "Truck is driving"</code></pre>
Output
The above code will produce the following output
Car is driving
Truck is driving
The main idea behind duck typing is that the code should be written to work with any object with the methods and properties it needs, rather than being written to work with specific objects. This can make the code more flexible and reusable, allowing you to use different types of objects interchangeably without changing the code.
Accessors in TypeScript provides a way to access and set the value of the class members using the getter and setter methods. They control how class members are accessed to read or write their values.
Accessors are useful for achieving encapsulation in TypeScript, which restricts access to class members only to the member functions of the class, preventing unauthorized access via third parties.
TypeScript supports the followings to access and change class members:
getters
setters
Getters in TypeScript
Getters are used to access the values of class members and manage how these values are accessed outside of the class. They can be created using the ‘get’ keyword.
Syntax
You can follow the syntax below to use getters in TypeScript.
classclass_name{// Define private variable here.// Defining the gettergetgetter_name(): return_type {// Return variable value}}let val = class_name.getter_name;
In the above syntax, the method_name method is a static method, which can take multiple parameters and return a value.
To access the value using the getter, we can use the class name followed by a dot followed by the getter method name.
Example
In the example below, we have defined the Person class, which contains the ‘Name’ private variable. It also contains the constructor() method which initializes the value of the ‘Name’ variable.
// Defining the Person classclassPerson{// Defining the private fieldprivate Name:string;// Defining the constructorconstructor(Name:string){this.Name = Name;}// Defining the gettergetSName():string{returnthis.Name;}}// Creating an instance of the Person classconst person =newPerson("John");console.log(person.SName);// Outputs: John
On compiling, it will generate the following JavaScript code.
// Defining the Person classclassPerson{// Defining the constructorconstructor(Name){this.Name = Name;}// Defining the gettergetSName(){returnthis.Name;}}// Creating an instance of the Person classconst person =newPerson("John");console.log(person.SName);// Outputs: John
Output
John
Example
In the code below, the Temperature class contains the ‘celsius’ private variable. The constructor() method initializes the value of the ‘celsius’ variable.
// Define a class Temperature with a property Celsius of type number.classTemperature{private celsius:number;// Define a constructor that initializes the Celsius property.constructor(celsius:number){this.celsius = celsius;}// Define a getter fahrenheit that returns the temperature in Fahrenheit.getfahrenheit():number{return(this.celsius *9/5)+32;}}// Create an instance of the Temperature class with a temperature of 25 degrees Celsius.const temp =newTemperature(25);console.log("The Fahrenheit value is: "+ temp.fahrenheit);// Outputs: 77
On compiling, it will generate the following JavaScript code.
// Define a class Temperature with a property Celsius of type number.classTemperature{// Define a constructor that initializes the Celsius property.constructor(celsius){this.celsius = celsius;}// Define a getter fahrenheit that returns the temperature in Fahrenheit.getfahrenheit(){return(this.celsius *9/5)+32;}}// Create an instance of the Temperature class with a temperature of 25 degrees Celsius.const temp =newTemperature(25);console.log("The Fahrenheit value is: "+ temp.fahrenheit);// Outputs: 77
Output
The Fahrenheit value is: 77
Setters in TypeScript
In TypeScript, setters are used to set the value of class members without accessing them outside of the class. They use the ‘set’ keyword to define the setter method.
Syntax
You can follow the syntax below to use setters in TypeScript.
classclass_name{// Define private variable here.// Defining the settersetsetter_name(val: type){// Set variable value}}
class_name.setter_name = val;
In the above syntax, we have used the ‘set’ keyword followed by ‘setter_name’ to define a setter. It takes only a single value as a parameter and uses it inside the setter method to change the value of any private class variables.
To use the setter method, you need to use the class name followed by a dot followed by the setter method name and assign a value to it.
Example
In the code below, we have defined the TextContainer class, which contains the ‘_content’ private variable to store the text.
// Define a class with a private property and a getter/setter methodclassTextContainer{// Define a private propertyprivate _content:string='';// Setter methodsetcontent(value:string){this._content = value.trim().toLowerCase();}// Getter methodgetcontent():string{returnthis._content;}}// Create an instance of the class and set the contentconst text =newTextContainer();
text.content =" Hello, WORLD! ";console.log(text.content);// Outputs: hello, world!
On compiling, it will generate the following JavaScript code.
// Define a class with a private property and a getter/setter methodclassTextContainer{constructor(){// Define a private propertythis._content ='';}// Setter methodsetcontent(value){this._content = value.trim().toLowerCase();}// Getter methodgetcontent(){returnthis._content;}}// Create an instance of the class and set the contentconst text =newTextContainer();
text.content =" Hello, WORLD! ";console.log(text.content);// Outputs: hello, world!
Output
hello, world!
It is very important to use accessors to achieve encapsulation in TypeScript. You can also create multiple getter and setter methods in a single class.
The abstract classes are used to achieve abstraction in TypeScript. The abstract class contains only method declaration but not implementation. We need to implement all abstract methods of the abstract class into the inherited class.
The abstraction is a way to hide the lower-level code implementation from users and some developers. Furthermore, it is used to show only the required information about the method rather than showing the whole complex implementation of methods.
Creating Abstract Classes
We can use the abstract keyword to define the abstract classes or methods. The abstract classes can contain the normal and abstract types of methods. In the abstract class, we need to implement the functional or normal method and only need to declare the abstract method.
We can inherit the abstract class using any other class, but we need to implement all abstract methods of the abstract class into the inherited class. If we dont want to implement the abstract method into the inherited class, we need to make an inherited class to abstract using the abstract keyword.
Also, we cant create the object of the abstract class, but we can create the object of the inherited class and use the abstract class methods. The limitation of the abstract class is that we cant implement multiple inheritances using the multiple abstract classes.
Syntax
You can follow the syntax below to create and inherit the abstract class to other classes.
abstract class sample {
// define variables inside the abstract class,
// declare the abstract methods or non-abstract method inside the abstract class
abstract demo(string): void;
}
// extend sample class and implement all abstract methods of sample to demo class
class test extends sample {
demo(name: string): void {
// code for the demo method
}
}
Example 1
In the example below, we have defined the abstract class containing the abstract methods. In the inherited test class, we have implemented the abstract methods of the sample class. Next, we created the object of the test class with 3 arguments and used that called the demo() and save() methods.
abstract classsample{// define variables inside the abstract class,
property1: string;constructor(property1: string, property2: number){this.property1 = property1;}// declare the abstract methods
abstract demo():void;// defining the non-abstract methodssave():void{
console.log("The save method of the abstract class is executed.");}}// extend sample class and implement all abstract methods of sample to demo classclasstestextendssample{
property2: number;constructor(property1: string, property2: number){super(property1);this.property2 = property2;}demo():void{// code for the demo method
console.log("The value of the property 3 is "+this.propert2);}}let test_obj =newtest("TutorialsPont",9999);
test_obj.demo();
test_obj.save();
We have hidden the implementation of the save() method from the inherited class test in the above example. We allow developers to implement the demo() method as they want but hide the other class information, such as property1, property2, and implementation of the save() method.
Now, users understand correctly the motive to use the abstract class and how we can use it to hide the information and can reveal only the required information.
On compiling, the above code will generate the following JavaScript code
var __extends =(this&&this.__extends)||(function(){varextendStatics=function(d, b){
extendStatics = Object.setPrototypeOf ||({ __proto__:[]}instanceofArray&&function(d, b){ d.__proto__ = b;})||function(d, b){for(var p in b)if(b.hasOwnProperty(p)) d[p]= b[p];};returnextendStatics(d, b);};returnfunction(d, b){extendStatics(d, b);function__(){this.constructor = d;}
d.prototype = b ===null? Object.create(b):(__.prototype = b.prototype,new__());};})();var sample =/** @class */(function(){functionsample(property1, property2){this.property1 = property1;}// defining the non-abstract methods
sample.prototype.save=function(){
console.log("The save method of the abstract class is executed.");};return sample;}());// extend sample class and implement all abstract methods of sample to demo classvar test =/** @class */(function(_super){__extends(test, _super);functiontest(property1, property2){var _this =_super.call(this, property1)||this;
_this.property2 = property2;return _this;}
test.prototype.demo=function(){// code for the demo method
console.log("The value of the property 3 is "+this.propert2);};return test;}(sample));var test_obj =newtest("TutorialsPont",9999);
test_obj.demo();
test_obj.save();
Output
It will produce the following output
The value of the property 3 is undefined
The save method of the abstract class is executed.
Example 2
In the example below, the class1 is the abstract class, which contains the declaration of the abstract method name method1. The class2 only contains the definition of method2(). It extended class1 but didnt implement the abstract method named method1().
After that, we defined class3 and inherited it via class2. Also, we have defined the method1 of class inside class3. At last, we created the object of class3 and invoked the method1() and method2().
// define the abstract class1 containing the abstract method1
abstract classclass1{
abstract method1():void;}// Need to create class2 to abstract as we inherited class1 but doesn't defined abstract method1()
abstract classclass2extendsclass1{method2():void{
console.log("Inside the method 2 of class2.");}}// defining the class3 inherited by the class2classclass3extendsclass2{// Implementation of the method1 of the abstract class1method1():void{
console.log("Implemented the abstract method name method1 of class1 inside the class3");}}// Crating the object of the class3var object =newclass3();// Invoking the method1 of class1 which is declared in the abstract class1
object.method1();// Invoking the method2 of class2
object.method2();
The above example shows us that if we inherit the abstract class by any class and dont want to implement the abstract method into the inherited class, we need to make the inherited class abstract.
On compiling, above code will generate the following JavaScript code
var __extends =(this&&this.__extends)||(function(){varextendStatics=function(d, b){
extendStatics = Object.setPrototypeOf ||({ __proto__:[]}instanceofArray&&function(d, b){ d.__proto__ = b;})||function(d, b){for(var p in b)if(b.hasOwnProperty(p)) d[p]= b[p];};returnextendStatics(d, b);};returnfunction(d, b){extendStatics(d, b);function__(){this.constructor = d;}
d.prototype = b ===null? Object.create(b):(__.prototype = b.prototype,new__());};})();// define the abstract class1 containing the abstract method1var class1 =/** @class */(function(){functionclass1(){}return class1;}());// Need to create class2 to abstract as we inherited class1 but doesn't defined abstract method1()var class2 =/** @class */(function(_super){__extends(class2, _super);functionclass2(){return _super !==null&&_super.apply(this, arguments)||this;}
class2.prototype.method2=function(){
console.log("Inside the method 2 of class2.");};return class2;}(class1));// defining the class3 inherited by the class2var class3 =/** @class */(function(_super){__extends(class3, _super);functionclass3(){return _super !==null&&_super.apply(this, arguments)||this;}// Implementation of the method1 of the abstract class1
class3.prototype.method1=function(){
console.log("Implemented the abstract method name method1 of class1 inside the class3");};return class3;}(class2));// Crating the object of the class3var object =newclass3();// Invoking the method1 of class1 which is declared in the abstract class1
object.method1();// Invoking the method2 of class2
object.method2();
Output
It will will produce the following output
Implemented the abstract method name method1 of class1 inside the class3
Inside the method 2 of class2.
In TypeScript, static properties belong to the class itself, instead of the instances of the class. The static methods and properties can be accessed without using the instance of the classes. This means you do not need to create an object of the class to use these methods and properties.
There are two types of properties and methods in TypeScript. One is instance properties and methods and another one is static properties and methods. Here, you will learn about static properties and methods.
Static properties and methods are useful to create utility functions and constants that don’t change across different instances. For example, if you are creating the circle class, and want to define the ‘PI’ property inside the class, you can make it static as it is a constant.
Static Properties
We can use the ‘static’ keyword before the property name to define static properties.
Syntax
You can follow the syntax below to define the static properties in TypeScript classes.
In the above syntax, we have used the ‘static’ keyword before the property name to define the static property.
To access the static property, we can use the class name followed by a dot followed by the static property name as shown below.
className.property_name;
Example
In the code below, we have created the ‘circle’ class. The class contains the ‘pi’ static property, which has a constant value.
classCircle{static pi:number=3.14159;// Static property to hold the value of Pi}console.log("The value of the PI is: "+ Circle.pi);
On compiling, it will generate the following JavaScript code.
var Circle =/** @class */(function(){functionCircle(){}
Circle.pi =3.14159;// Static property to hold the value of Pireturn Circle;}());console.log("The value of the PI is: "+ Circle.pi);</code></pre>
Output
The output of the above generated JavaScript code is as follows −
The value of the PI is: 3.14159
Example
In the code below, we have defined the 'student' class. It contains the static property named 'studentCount' to store the total number of students.
classStudent{static studentCount:number=0;// Static variable to store the count of studentsconstructor(public name:string,public age:number){this.name = name;this.age = age;
Student.studentCount++;// Incrementing the count of students}// Method to display the student detailsdisplay(){console.log(Name: ${this.name}, Age: ${this.age});}}// Creating objects of Student classlet student1 =newStudent('John',20);let student2 =newStudent('Doe',21);// Accessing static variableconsole.log("Total number of registered students is: "+ Student.studentCount);// Output: 2</code></pre>
On compiling, it will generate the following JavaScript code.
Student.studentCount++;// Incrementing the count of students}// Method to display the student detailsdisplay(){console.log(Name: ${this.name}, Age: ${this.age});}}
Student.studentCount =0;// Static variable to store the count of students// Creating objects of Student classlet student1 =newStudent('John',20);let student2 =newStudent('Doe',21);// Accessing static variableconsole.log("Total number of registered students is: "+ Student.studentCount);// Output: 2
Output
The output of the above generated JavaScript code is as follows −
Total number of registered students is: 2
Static Methods
You can use the 'static' keyword before the method name to declare the static method.
Syntax
You can follow the syntax below to define the static methods in TypeScript classes.
classClass_name{staticmethod_name(params){// Code to be executed}}
In the above syntax, the method_name method is a static method, which can take multiple parameters and return a value.
You can call the static method by accessing it using the class name as shown in the code below.
Class_name.method_name();
Example
In the code below, the 'square' class contains the 'area()' static method, which gets the value of the square side as a parameter and returns the area of the square.
classSquare{// Define a static methodstaticarea(side:number){return side * side;// return the area of the square}}// call the static methodlet area = Square.area(5);console.log(Area of the square: ${area});// Output: Area of the square: 25
On compiling, it will generate the following JavaScript code.
classSquare{// Define a static methodstaticarea(side){return side * side;// return the area of the square}}// call the static methodlet area = Square.area(5);console.log(Area of the square: ${area});// Output: Area of the square: 25
Output
The output of the above generated JavaScript code is as follows −
Area of the square: 25
Example
The below example is very similar to the second example covered in this lesson. It has a private static member named 'studentCount' to store the total number of students.
classStudent{privatestatic studentCount:number=0;// Static variable to store the count of studentsconstructor(public name:string,public age:number){this.name = name;this.age = age;
Student.studentCount++;// Incrementing the count of students}// Method to get the count of studentsstaticgetStudentCount(){return Student.studentCount;}}// Creating objects of Student classlet student1 =newStudent('John',20);let student2 =newStudent('Doe',21);// Accessing static variableconsole.log("Total number of registered students is: "+ Student.getStudentCount());// Output: 2</code></pre>
On compiling, it will generate the following JavaScript code.
Student.studentCount++;// Incrementing the count of students}// Method to get the count of studentsstaticgetStudentCount(){return Student.studentCount;}}
Student.studentCount =0;// Static variable to store the count of students// Creating objects of Student classlet student1 =newStudent('John',20);let student2 =newStudent('Doe',21);// Accessing static variableconsole.log("Total number of registered students is: "+ Student.getStudentCount());// Output: 2
Output
The output of the above generated JavaScript code is as follows −
Total number of registered students is: 2
In real-time development, static properties can be used to store values like application version, particular settings, etc. as they remain constant. In short, you can use static properties to store constant values. Furthermore, you can use static methods when the code of the method is not dependent on any instance property.
Inheritance in TypeScript is a concept that allows us to reuse the properties and methods of a single class with other classes. So, it increases the code readability by allowing the reuse of the codes of different classes.
TypeScript is an object-oriented programming language, and it supports all features of the OOP. Object-oriented programming has four main pillars and inheritance is one of them.
TypeScript mainly supports two types of inheritance: single-class inheritance and multilevel inheritance. We will explore each of them in this chapter.
Single Class Inheritance
In single-class inheritance, one class inherits the properties of another class. The class that inherits the properties of the other class is called the base class or child class. The class whose properties are inherited is called a parent or superclass.
You can use the ‘extend’ keyword to achieve inheritance in TypeScript.
Syntax
You can follow the syntax below to use the single-class inheritance.
classChildextendsParent{// Define properties and methods for child class}
In the above syntax, we have used the ‘extends’ keyword between the name of the child and the parent class.
Example
In the code below, we have defined the ‘Vehicle’ class which contains the ‘getType()’ method, returning the vehicle type.
classVehicle{getType(){return"Vehicle";}}// Define a class Car that extends VehicleclassCarextendsVehicle{
carName:string="Innova";getCarName(){returnthis.carName;}}// Create an object of Car classlet car =newCar();console.log(car.getType());// Output: Vehicleconsole.log(car.getCarName());// Output: Innova</code></pre>
On compiling, it will generate the following JavaScript code.
classVehicle{getType(){return"Vehicle";}}// Define a class Car that extends VehicleclassCarextendsVehicle{constructor(){super(...arguments);this.carName ="Innova";}getCarName(){returnthis.carName;}}// Create an object of Car classlet car =newCar();console.log(car.getType());// Output: Vehicleconsole.log(car.getCarName());// Output: Innova
Output
Vehicle
Innova
Super Keyword
The super keyword is used to call the constructor of the Parent class or access and call the method of the Parent class.
Syntax
You can follow the syntax below to use the super keyword to call the constructor and methods of the Parent class in the Child class.
classChildextendsParent{constructor(){super();// To call the constructor of the parent class}super.method_name();// To call method of the parent class}
In the above syntax, we have used the 'super()' inside the constructor of the child class to call the constructor of the parent class.
In 'super.method_name()', method_name is the name of the method of the parent class.
Example
In the code below, we have defined the Person class which contains the 'name' property, and initialized it inside the constructor() method. The display() method prints the value of the name property.
classPerson{
name:string;constructor(name:string){this.name = name;}display():void{console.log(this.name);}}// Employee class is inheriting the Person classclassEmployeeextendsPerson{
empCode:number;constructor(name:string, code:number){super(name);this.empCode = code;}show():void{super.display();}}// Creating the object of Employee classlet emp =newEmployee("Sam",123);
emp.show();// Sam
On compiling, it will generate the following JavaScript code.
classPerson{constructor(name){this.name = name;}display(){console.log(this.name);}}// Employee class is inheriting the Person classclassEmployeeextendsPerson{constructor(name, code){super(name);this.empCode = code;}show(){super.display();}}// Creating the object of Employee classlet emp =newEmployee("Sam",123);
emp.show();// Sam
Output
The above code example will produce the following result -
Sam
Method Overriding
The method overriding concept allows you to override the code of the particular method of the parent class inside the child class. This way, you can have a method with the same name in the parent and child classes, having different functionalities.
Example
In the code below, the Animal class has a move() method which works for any animal. After that, we have extended the Dog class with the Animal class. The Dog class has its own move() method, and that's how we do method overriding.
classAnimal{move(){console.log("Animal is moving");}}// Dog class is inheriting Animal classclassDogextendsAnimal{// Method overridingmove(){console.log("Dog is moving");}}let dog =newDog();
dog.move();// Output: Dog is moving
On compiling, it will generate the same JavaScript code.
Output
The above code example will produce the following result -
Dog is moving
Multilevel Inheritance
Multilevel inheritance allows you to inherit the properties of the parent class, where the parent class also inherits the other class.
Example
In the code below, the Parrot class inherits the properties and methods of the Bird class, and the Bird class inherits the properties and methods of the Animal class. However, you may have more levels of multilevel inheritance in real-time development.
classAnimal{// Move methodmove(){console.log('This animal moves');}}// Bird class extends Animal classclassBirdextendsAnimal{// Bird can flyfly(){console.log('This bird flies');}}// Parrot class inherits the Bird classclassParrotextendsBird{// Parrot can speakspeak(){console.log('The parrot speaks');}}// Creating an instance of the Parrot classletP1=newParrot();P1.speak();
On compiling, it will generate the same JavaScript code.
Output
The above code example will produce the following result -
The parrot speaks
Multiple Inheritance, Hierarchical Inheritance, and Hybrid Inheritance are also supported by some object-oriented programming languages but not supported by TypeScript. You may use the interface or prototype-based inheritance to achieve multiple inheritance.
TypeScript provides us with readonly keyword to make a property in class, type or interface as read-only. The readonly properties can be accessed outside the class but their values can’t be modified.
By using the readonly properties, you can ensure that no one can modify the property outside the object, but they can read the value of the property.
Syntax
To define readonly properties in TypeScript, we prefix the property name with the readonly keyword.
readonly propName: type;
Where,
In the above syntax, ‘readonly’ is a keyword.
The ‘propName’ is a property name of the readonly property.
The ‘type’ is a property type of the readonly property.
The encapsulating entity may be a class or an interface in TypeScript.
You can replace propertyName with the desired name for the readonly property, and type with the appropriate data type.
The readonly Properties with Interface
Interface is used to define the prototype of the object. We can define the readonly properties in the interface. Let’s understand it via the example below.
Example
In the code below, we have defined the Car interface containing the model, year, and fuel properties. Here, the fuel property is readonly, which can be initialized only while creating the object but can’t be changed after creating the object.
After that, we have created the object of the Car type and initialized all properties.
You can try to change the value of the fuel property of the car object, and observe the error.
// Defining the interface for the carinterfaceCar{
model:string;
year:number;readonly fuel:string;}// Defining the car objectlet car: Car ={
model:'BMW',
year:2019,
fuel:'petrol'}console.log(car.model);console.log(car.year);console.log(car.fuel);// Error: Cannot assign to 'fuel' because it is a read-only property.// car.fuel = 'diesel';</code></pre>
On compiling, it will generate the following JavaScript code.
// Defining the car objectlet car ={
model:'BMW',
year:2019,
fuel:'petrol'};console.log(car.model);console.log(car.year);console.log(car.fuel);// Error: Cannot assign to 'fuel' because it is a read-only property.// car.fuel = 'diesel';</code></pre>
Output
The output of the above example code is as follows −
BMW
2019
petrol
The readonly Properties with Classes
The class can also contain readonly properties similar to the interfaces. The value of the readonly properties can be initialized in the constructor() method while creating the object. You can't change the value of the readonly properties of the class using the class instance.
Example
In the code below, we have defined the car class which contains the model and price properties. It also contains the 'type' readonly property.
In the constructor() method, we initialize the values of all properties including the 'type' readonly property.
The display() method prints the values of all properties.
After that, we have created the object of the car class. Now, you can try to change the value of the 'type' property of the car object, it will throw an error as it is readonly.
// Defining the class for carclassCar{// Properties
model:string;
price:number;readonly type:string='Car';// Constructorconstructor(model:string, price:number, type:string){this.model = model;this.price = price;this.type = type;}// Methoddisplay():void{console.log(Model: ${this.model}, Price: ${this.price}, Type: ${this.type});}}// Creating object of Car classlet car =newCar('BMW',1000000,'Sedan');
car.display();// Try to change the value of readonly property// car.type = 'SUV'; // Error: Cannot assign to 'type' because it is a read-only property.
On compiling, it will generate the following JavaScript code.
// Defining the class for carclassCar{// Constructorconstructor(model, price, type){this.type ='Car';this.model = model;this.price = price;this.type = type;}// Methoddisplay(){console.log(Model: ${this.model}, Price: ${this.price}, Type: ${this.type});}}// Creating object of Car classlet car =newCar('BMW',1000000,'Sedan');
car.display();// Try to change the value of readonly property// car.type = 'SUV'; // Error: Cannot assign to 'type' because it is a read-only property.
Output
The output of the above code example is as follows −
Model: BMW, Price: 1000000, Type: Sedan
The readonly Properties with Type Aliases
Type aliases are used to define a type for the object. It can also contain readonly properties similar to the interface.
Example
In the code below, 'Point' is a type alias that contains 'x' and 'y' properties and both are readonly.
After that, we defined the P1 point of type 'Point' and initialized the values of 'X' and 'Y' properties while defining it.
Next, try to change the value of any property of the 'P1' point. It will throw an error as both properties of P1 are readonly.
// Readonly Properties with type aliastypePoint={readonly x:number;readonly y:number;}let p1: Point ={ x:10, y:20};// p1.x = 5; // Errorconsole.log(p1.x, p1.y);
On compiling, it will generate the following JavaScript code.
The 'const' keyword is similar to the 'readonly' keyword but there are some minor differences.
The 'const' keyword is used to declare the constant variables, whereas the 'readonly' keyword is used to declare the readonly properties of objects.
You need to assign value to the variable declared using the 'const' keyword while declaring it, but for 'readonly' properties, you can assign values while creating the object.
The value of 'const' variables can't be changed after declaring it, and the value of 'readonly' properties can't be changed outside the object or class.
When to Use Readonly
Data Integrity: When you want to ensure that certain properties of an object don't change after their initial assignment.
Functional Programming: It helps in programming paradigms where immutability is a cornerstone.
API Contracts: When creating objects that are exposed to external users and you need to guarantee that the internal state won't be altered unexpectedly.
The concept access modifiers are used to implement encapsulation or data hiding in TypeScript. The access modifiers define the visibility class members outside the defining class. The class members are properties and functions. The access modifiers are also known as access specifiers.
TypeScript supports three types of access modifiers public, private and protected. These modifiers are the keywords that are used to declare a class member as public, private or protected.
A public class member is accessible from anywhere in the code. A private member is only accessible within the class that defines it. And a protected member is accessible from within the class and derived classes.
Let’s understand each of the three access modifiers discussed above in details.
Public Access Modifier
A public access modifier in TypeScript defines a public class member. By default, a member of class is public. So it’s optional to use public keyword to declare a member public. The public members are accessible from anywhere within and outside the class that defines the member.
Example
In the example below, we have created a class named Person. The class Person has two members, one a public property name and other a public method greet(). As these members are declared as public, these can be accessed from anywhere in the program.
We also created an instance of the Person class as person. We access the name property of the person object and assign a value to it. Finally, we called the greet function to display the greeting with new name.
classPerson{public name:string="";publicgreet():void{console.log(Hello, my name is ${this.name}!);}}const person =newPerson();
person.name ="John";
person.greet();
On compiling, it will generate the following JavaScript code.
classPerson{constructor(){this.name ="";}greet(){console.log(Hello, my name is ${this.name}!);}}const person =newPerson();
person.name ="John";
person.greet();
The output of the above example code is as follows
Hello, my name is John!
Private Access Modifiers
A private access modifier restricts the access of the class member (property or method) to the class where it is declared. When you add the private modifier to the property or method, you can access that property or method within the same class only.
A private access modifier in TypeScript defines a private class member. Private members are accessible from within the class that defines them.
Example
In this example, we have created a BankAccount class with a private property balance, which can only be accessed and modified within the class. Additionally, we have a private method calculateInterest(), that calculates the interest based on the balance.
You can observe in the output that attempting to access the private members will result in a TypeError.
classBankAccount{private balance:number;constructor(initialBalance:number){this.balance = initialBalance;}privatecalculateInterest():number{const interestRate =0.05;returnthis.balance * interestRate;}}// Creating an instance of the BankAccount classconst account =newBankAccount(1000);// Attempting to access private membersconsole.log(account.balance);console.log(account.calculateInterest());
On compiling, it will generate the following JavaScript code.
classBankAccount{constructor(initialBalance){this.balance = initialBalance;}calculateInterest(){const interestRate =0.05;returnthis.balance * interestRate;}}// Creating an instance of the BankAccount classconst account =newBankAccount(1000);// Attempting to access private membersconsole.log(account.balance);console.log(account.calculateInterest());
And it will give the following error
Property 'balance' is private and only accessible within class 'BankAccount'.
Property 'calculateInterest' is private and only accessible within class 'BankAccount'.
The output of the above example code is as follows
1000
50
Protected Access Modifiers
The protected access modifier is used to define a protected class member (property or method). A protected data member is accessible only within the class that defines it or any class that extends it.
Example
In this example, we create a base class Animal with a protected property name, which can be accessed and modified within the class and its derived classes. We also have a protected method makeSound(), that simply logs a message.
We then create a derived class Dog that extends the Animal class. The Dog class adds a public method bark(), which utilizes the name property inherited from the base class to output a bark message.
Finally, we create an instance of the Dog class named dog with the name “Buddy” and call the bark() method.
The output will show the dog’s name followed by the bark message.
classAnimal{protected name:string;constructor(name:string){this.name = name;}protectedmakeSound():void{console.log("The animal makes a sound");}}classDogextendsAnimal{publicbark():void{console.log(${this.name} barks!);}}// Creating an instance of the Dog classconst dog =newDog("Buddy");
dog.bark();
On compiling, it will generate the following JavaScript code.
classAnimal{constructor(name){this.name = name;}makeSound(){console.log("The animal makes a sound");}}classDogextendsAnimal{bark(){console.log(${this.name} barks!);}}// Creating an instance of the Dog classconst dog =newDog("Buddy");
dog.bark();
The output of the above example code is as follows
An object in TypeScript is an instance which contains set of key value pairs. The key value pairs are also referred as object properties. The values can be scalar values or functions or even array of other objects. If a property’s value is a function, that property is known as method.
Syntax
The syntax to create an object in TypeScript is given below
var object_name ={
key1:"value1",//scalar value
key2:"value",key3:function(){//functions },
key4:["content1","content2"]//collection };
As shown above, an object can contain scalar values, functions and structures like arrays and tuples.
Type Annotations
In TypeScript, we should annotate the object properties as follows
let person:{name:string, age:number}={
name:"Tom Hanks",
age:35,}
In the above example we annotated the properties name and age of the person object.
We can also use interface to create type for object properties. Look the below example
The object literal notation is the easiest way to create an object in TypeScript. We use curly braces ({}) to create object. Each property of the object is separated by a comma. Each property is written as property name (key) followed by colon (:) followed by property value.
Example
In the example below, we have created an object named person containing two properties. The first property is firstname: “Tom” and the second property is lastname: “Hanks”. We access the property values and print them in console.
var person:{ firstName:string, lastName:string}={
firstName:"Tom",
lastName:"Hanks"};//access the object values console.log(person.firstName)console.log(person.lastName)
On compiling, it will generate the following code in JavaScript.
var person ={
firstName:"Tom",
lastName:"Hanks"};//access the object values console.log(person.firstName);console.log(person.lastName);</code></pre>
Output
Tom
Hanks
TypeScript Type Template
Let's say you created an object literal in JavaScript as
var person ={
firstname:"Tom",
lastname:"Hanks"};
In case you want to add some value to an object, JavaScript allows you to make the necessary modification. Suppose we need to add a function to the person object later this is the way you can do this.
person.sayHello=function(){return"hello";}
If you use the same code in Typescript the compiler gives an error. This is because in Typescript, concrete objects should have a type template. Objects in Typescript must be an instance of a particular type.
You can solve this by using a method template in declaration.
Example: Typescript Type template
var person ={
firstName:"Tom",
lastName:"Hanks",sayHello:function(){}//Type template }
person.sayHello=function(){console.log("hello "+person.firstName)}
person.sayHello()
On compiling, it will generate the same code in JavaScript.
Output
The above generated JavaScript code will produce the following output -
hello Tom
Objects as Function Parameters
Objects can also be passed as parameters to function.
Example: Objects as function parameters
var person ={
firstname:"Tom",
lastname:"Hanks"};varinvokeperson=function(obj:{ firstname:string, lastname :string}){console.log("first name :"+obj.firstname)console.log("last name :"+obj.lastname)}invokeperson(person)
The example declares an object literal. The function expression is invoked passing person object.
On compiling, it will generate following JavaScript code.
//Generated by typescript 1.8.10var person ={
firstname:"Tom",
lastname:"Hanks"};varinvokeperson=function(obj){console.log("first name :"+ obj.firstname);console.log("last name :"+ obj.lastname);};invokeperson(person);
Output
first name :Tom
last name :Hanks
Anonymous Objects
You can create and pass an anonymous object on the fly.
Example: Anonymous Object
varinvokeperson=function(obj:{ firstname:string, lastname :string}){console.log("first name :"+obj.firstname)console.log("last name :"+obj.lastname)}invokeperson({firstname:"Sachin",lastname:"Tendulkar"});
On compiling, it will generate following JavaScript code.
//Generated by typescript 1.8.10varinvokeperson=function(obj){console.log("first name :"+ obj.firstname);console.log("last name :"+ obj.lastname);};invokeperson({ firstname:"Sachin", lastname:"Tendulkar"});
Output
first name :Sachin
last name :Tendulkar
Duck-typing
In duck-typing, two objects are considered to be of the same type if both share the same set of properties. Duck-typing verifies the presence of certain properties in the objects, rather than their actual type, to check their suitability. The concept is generally explained by the following phrase
When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck.
The TypeScript compiler implements the duck-typing system that allows object creation on the fly while keeping type safety. The following example shows how we can pass objects that dont explicitly implement an interface but contain all of the required members to a function.
Example
interfaceIPoint{
x:number
y:number}functionaddPoints(p1:IPoint,p2:IPoint):IPoint {var x = p1.x + p2.x
var y = p1.y + p2.y
return{x:x, y:y}}//Valid var newPoint =addPoints({x:3,y:4},{x:5,y:1})//Error var newPoint2 =addPoints({x:1},{x:4,y:3})
TypeScript is object oriented JavaScript. TypeScript supports object-oriented programming features like classes, interfaces, etc. A class in terms of OOP is a blueprint for creating objects. A class encapsulates data for the object. Typescript gives built in support for this concept called class. JavaScript ES5 or earlier didnt support classes. Typescript gets this feature from ES6.
Creating classes
Use the class keyword to declare a class in TypeScript. The syntax for the same is given below −
Syntax
class class_name {
//class scope
}
The class keyword is followed by the class name. The rules for identifiers must be considered while naming a class.
A class definition can include the following −
Fields − A field is any variable declared in a class. Fields represent data pertaining to objects
Constructors − Responsible for allocating memory for the objects of the class
Functions − Functions represent actions an object can take. They are also at times referred to as methods
These components put together are termed as the data members of the class.
Consider a class Person in typescript.
classPerson{}
On compiling, it will generate following JavaScript code.
//Generated by typescript 1.8.10var Person =(function(){functionPerson(){}return Person;}());
Example: Declaring a class
classCar{//field
engine:string;//constructor constructor(engine:string){this.engine = engine
}//function disp():void{console.log("Engine is : "+this.engine)}}
The example declares a class Car. The class has a field named engine. The var keyword is not used while declaring a field. The example above declares a constructor for the class.
A constructor is a special function of the class that is responsible for initializing the variables of the class. TypeScript defines a constructor using the constructor keyword. A constructor is a function and hence can be parameterized.
The this keyword refers to the current instance of the class. Here, the parameter name and the name of the classs field are the same. Hence to avoid ambiguity, the classs field is prefixed with the this keyword.
disp() is a simple function definition. Note that the function keyword is not used here.
On compiling, it will generate following JavaScript code.
//Generated by typescript 1.8.10var Car =(function(){//constructorfunctionCar(engine){this.engine = engine;}//function
Car.prototype.disp=function(){console.log("Engine is : "+this.engine);};return Car;}());
Creating Instance objects
To create an instance of the class, use the new keyword followed by the class name. The syntax for the same is given below −
Syntax
var object_name = new class_name([ arguments ])
The new keyword is responsible for instantiation.
The right-hand side of the expression invokes the constructor. The constructor should be passed values if it is parameterized.
Example: Instantiating a class
var obj =newCar("Engine 1")
Accessing Attributes and Functions
A classs attributes and functions can be accessed through the object. Use the . dot notation (called as the period) to access the data members of a class.
//accessing an attribute
obj.field_name
//accessing a function
obj.function_name()
Example: Putting them together
classCar{//field
engine:string;//constructor constructor(engine:string){this.engine = engine
}//function disp():void{console.log("Function displays Engine is : "+this.engine)}}//create an object var obj =newCar("XXSY1")//access the field console.log("Reading attribute value Engine as : "+obj.engine)//access the function
obj.disp()
On compiling, it will generate following JavaScript code.
//Generated by typescript 1.8.10var Car =(function(){//constructorfunctionCar(engine){this.engine = engine;}//function
Car.prototype.disp=function(){console.log("Function displays Engine is : "+this.engine);};return Car;}());//create an objectvar obj =newCar("XXSY1");//access the fieldconsole.log("Reading attribute value Engine as : "+ obj.engine);//access the function
obj.disp();
The output of the above code is as follows −
Reading attribute value Engine as : XXSY1
Function displays Engine is : XXSY1
Class Inheritance
TypeScript supports the concept of Inheritance. Inheritance is the ability of a program to create new classes from an existing class. The class that is extended to create newer classes is called the parent class/super class. The newly created classes are called the child/sub classes.
A class inherits from another class using the extends keyword. Child classes inherit all properties and methods except private members and constructors from the parent class.
Syntax
class child_class_name extends parent_class_name
However, TypeScript doesnt support multiple inheritance.
Example: Class Inheritance
classShape{
Area:numberconstructor(a:number){this.Area = a
}}classCircleextendsShape{disp():void{console.log("Area of the circle: "+this.Area)}}var obj =newCircle(223);
obj.disp()
On compiling, it will generate following JavaScript code.
//Generated by typescript 1.8.10var __extends =(this&&this.__extends)||function(d, b){for(var p in b)if(b.hasOwnProperty(p)) d[p]= b[p];function__(){this.constructor = d;}
d.prototype = b ===null? Object.create(b):(__.prototype = b.prototype,new__());};var Shape =(function(){functionShape(a){this.Area = a;}return Shape;}());var Circle =(function(_super){__extends(Circle, _super);functionCircle(){_super.apply(this, arguments);}
Circle.prototype.disp=function(){console.log("Area of the circle: "+this.Area);};return Circle;}(Shape));var obj =newCircle(223);
obj.disp();
The output of the above code is as follows −
Area of the Circle: 223
The above example declares a class Shape. The class is extended by the Circle class. Since there is an inheritance relationship between the classes, the child class i.e. the class Car gets an implicit access to its parent class attribute i.e. area.
Inheritance can be classified as −
Single − Every class can at the most extend from one parent class
Multiple − A class can inherit from multiple classes. TypeScript doesnt support multiple inheritance.
Multi-level − The following example shows how multi-level inheritance works.
Example
classRoot{
str:string;}classChildextendsRoot{}classLeafextendsChild{}//indirectly inherits from Root by virtue of inheritance var obj =newLeaf();
obj.str ="hello"console.log(obj.str)
The class Leaf derives the attributes from Root and Child classes by virtue of multi-level inheritance.
On compiling, it will generate following JavaScript code.
//Generated by typescript 1.8.10var __extends =(this&&this.__extends)||function(d, b){for(var p in b)if(b.hasOwnProperty(p)) d[p]= b[p];function__(){this.constructor = d;}
d.prototype = b ===null? Object.create(b):(__.prototype = b.prototype,new__());};var Root =(function(){functionRoot(){}return Root;}());var Child =(function(_super){__extends(Child, _super);functionChild(){_super.apply(this, arguments);}return Child;}(Root));var Leaf =(function(_super){__extends(Leaf, _super);functionLeaf(){_super.apply(this, arguments);}return Leaf;}(Child));var obj =newLeaf();
obj.str ="hello";console.log(obj.str);
Its output is as follows −
Output
hello
TypeScript Class inheritance and Method Overriding
Method Overriding is a mechanism by which the child class redefines the superclasss method. The following example illustrates the same −
classPrinterClass{doPrint():void{console.log("doPrint() from Parent called")}}classStringPrinterextendsPrinterClass{doPrint():void{super.doPrint()console.log("doPrint() is printing a string")}}var obj =newStringPrinter()
obj.doPrint()
The super keyword is used to refer to the immediate parent of a class. The keyword can be used to refer to the super class version of a variable, property or method. Line 13 invokes the super class version of the doWork() function.
On compiling, it will generate following JavaScript code.
//Generated by typescript 1.8.10var __extends =(this&&this.__extends)||function(d, b){for(var p in b)if(b.hasOwnProperty(p)) d[p]= b[p];function__(){this.constructor = d;}
d.prototype = b ===null? Object.create(b):(__.prototype = b.prototype,new__());};var PrinterClass =(function(){functionPrinterClass(){}
PrinterClass.prototype.doPrint=function(){console.log("doPrint() from Parent called");};return PrinterClass;}());var StringPrinter =(function(_super){__extends(StringPrinter, _super);functionStringPrinter(){_super.apply(this, arguments);}
StringPrinter.prototype.doPrint=function(){
_super.prototype.doPrint.call(this);console.log("doPrint() is printing a string");};return StringPrinter;}(PrinterClass));var obj =newStringPrinter();
obj.doPrint();
The output of the above code is as follows −
doPrint() from Parent called
doPrint() is printing a string
The static Keyword
The static keyword can be applied to the data members of a class. A static variable retains its values till the program finishes execution. Static members are referenced by the class name.
Example
classStaticMem{static num:number;staticdisp():void{console.log("The value of num is"+ StaticMem.num)}}
StaticMem.num =12// initialize the static variable
StaticMem.disp()// invoke the static method
On compiling, it will generate following JavaScript code.
//Generated by typescript 1.8.10var StaticMem =(function(){functionStaticMem(){}
StaticMem.disp=function(){console.log("The value of num is"+ StaticMem.num);};return StaticMem;}());
StaticMem.num =12;// initialize the static variable
StaticMem.disp();// invoke the static method
The output of the above code is as follows −
The value of num is 12
The instanceof operator
The instanceof operator returns true if the object belongs to the specified type.
Example
classPerson{}var obj =newPerson()var isPerson = obj instanceofPerson;console.log(" obj is an instance of Person "+ isPerson);
On compiling, it will generate following JavaScript code.
//Generated by typescript 1.8.10var Person =(function(){functionPerson(){}return Person;}());var obj =newPerson();var isPerson = obj instanceofPerson;console.log(" obj is an instance of Person "+ isPerson);
The output of the above code is as follows −
obj is an instance of Person True
Data Hiding
A class can control the visibility of its data members to members of other classes. This capability is termed as Data Hiding or Encapsulation.
Object Orientation uses the concept of access modifiers or access specifiers to implement the concept of Encapsulation. The access specifiers/modifiers define the visibility of a classs data members outside its defining class.
The access modifiers supported by TypeScript are −
S.No.
Access Specifier & Description
1.
publicA public data member has universal accessibility. Data members in a class are public by default.
2.
privatePrivate data members are accessible only within the class that defines these members. If an external class member tries to access a private member, the compiler throws an error.
3.
protectedA protected data member is accessible by the members within the same class as that of the former and also by the members of the child classes.
Example
Let us now take an example to see how data hiding works −
classEncapsulate{
str:string="hello"private str2:string="world"}var obj =newEncapsulate()console.log(obj.str)//accessible console.log(obj.str2)//compilation Error as str2 is private
The class has two string attributes, str1 and str2, which are public and private members respectively. The class is instantiated. The example returns a compile time error, as the private attribute str2 is accessed outside the class that declares it.