it is bad idea to use constructor functions in TS, use classes instead
usually TS compiler assign type any to objects generated by constructor functions which creates a gap in the type system.
you can use type casting to cast these objects to the correct type, but it is still a bad idea to use constructor functions in TS.
you can still give the same name to variable and type, but it is not recommended, this is possible since TS compiler tracks types and variables separately.
TS classes can look different from the standard JS classes, but the the compiler will generate fully standard JS classes that depends on JS constructor functions and prototype inheritance at runtime.
TS access modifiers keywords:
public: property accessible from anywhere
private: property accessible only from inside the class
protected: property accessible from inside the class and from its subclasses
access modifiers work only during development, at runtime the properties still on objects and can be accessible
compiler option strictPropertyInitialization tells compiler to raise an error if a class defines a property without initializing it and assigning it a value. the initialization may happen in the constructor or in the class declaration.
to ensure that a property is actually private, you can use:
the private keyword and JS getters/setters
JS setters/getters and JS private properties syntax(aka. property name starts with #).
classPersonT{private_name:string;// use of private keyword#age:number;// use of JS private properties syntaxgetname():string{// getterreturnthis._name;}setname(value:string){// setterthis._name=value;}getage():number{// getterreturnthis.#age;}setage(value:number){// setterthis.#age=value;}}
although using JS syntax for private properties may guarantee that the property is private during runtime, it is not recommended. stick to use the private keyword.
readonly properties are properties that can only be set once (during instantiation, aka. in the constructor).
// readonly propertiesclassPersonT2{readonlyid:number;name:string;constructor(id:number,name:string){this.id=id;this.name=name;}}constperson2=newPersonT2(1,"John");person2.name="Mike";// Fineperson2.id=2;// Error: Cannot assign to 'id' because it is a read-only property.
The readonly keyword must come after the access control keyword if one has been used. eg. public readonly | private readonly.
Abstract classes cannot be instantiated directly and are used to describe common functionality that must be implemented by subclasses
any abstract methods must be implemented in the subclasses
abstractclassPersonT3{constructor(privatereadonlyid:string,publicname:string,publicage:number){}abstractsayHello():void;}classEmployeeT3extendsPersonT3{constructor(id:string,name:string,age:number){super(id,name,age);}sayHello(){console.log(`I'm an employee`,`Hello, my name is ${this.name} and I am ${this.age} years old`);}}classCustomerT3extendsPersonT3{constructor(id:string,name:string,age:number){super(id,name,age);}sayHello(){console.log(`I'm a customer`,`Hello, my name is ${this.name} and I am ${this.age} years old`);}}
Objects instantiated from classes derived from an abstract class can be used through the abstract class type. eg. objects of type CustomerT3 and EmployeeT3 can be stored in an array of PersonT3
type guarding against classes can work properly using instanceof, but it is important to now that this returns true for both parent and child classes.
Interfaces are used to describe the shape of an object, which a class that implements the interface must conform to.
interfaces and types are very similar, but interface word comes from OOP world, it is recommended to use type unless the type you are defining is shaping a class then use interface and implement keywords.
A class can implement more than one interface, meaning it must define the methods and properties defined by all of them.
Interfaces can be defined in multiple interface declarations, which are merged by the compiler to form a single interface.
interfaces define public properties and methods only, private properties and methods can not appear in interfaces.
a single class can implement multiple interfaces, but it can only inherit one class.
interfacePersonT4{// id: string; // interfaces define public properties and methods only, id is private and cannot be in the interfacename:string;age:number;}interfacePersonT4{// compiler automatically merges previous definition with this one resulting in a single definition of PersonT4// that contains all the properties and methods of both interfacessayHello():void;}interfaceCustomerDetailedT4{getPastOrderIds():string[];computeTotalOrderValue():number;}classEmployeeT4implementsPersonT4{constructor(privatereadonlyid:string,publicname:string,publicage:number){}sayHello(){console.log(`I'm an employee`,`Hello, my name is ${this.name} and I am ${this.age} years old`);}}// CustomerT4 implements PersonT4 and CustomerDetailedT4classCustomerT4implementsPersonT4,CustomerDetailedT4{constructor(privatereadonlyid:string,publicname:string,publicage:number){}sayHello(){console.log(`I'm a customer`,`Hello, my name is ${this.name} and I am ${this.age} years old`);}getPastOrderIds():string[]{return["1","2"];}computeTotalOrderValue():number{return100;}}
Interfaces can be extended, and the result is a new interface that contains all the properties and methods of the original interfaces.
interfacePersonT5{name:string;age:number;}interfaceCustomerT5extendsPersonT5{getPastOrderIds():string[];computeTotalOrderValue():number;}// instead of using multiple implements, we can extend these interfaces into a single interface then implement it.// class CustomerT5 implements CustomerT5 = class CustomerT4 implements PersonT4, CustomerDetailedT4classCustomerT5implementsCustomerT5{constructor(privatereadonlyid:string,publicname:string,publicage:number){}getPastOrderIds():string[]{return["1","2"];}computeTotalOrderValue():number{return100;}}
you can use classes to implement types as if it implementing an interface
typePersonT={};interfacePersonI{}classPerson1implementsPersonT{}// worksclassPerson2implementsPersonI{}// works
interfaces can also extend types
typePersonT={name:string;age:number;};// PersonT is a type, not an interface, and yet CustomerT5 extends PersonT fine !!interfaceCustomerTextendsPersonT{getPastOrderIds():string[];computeTotalOrderValue():number;}
There is no JavaScript equivalent to interfaces, and no details of interfaces are included in the JavaScript code generated by the TypeScript compiler.
instanceof keyword cannot be used to narrow interface types,
index signature allows to dynamically assign/access properties of an object.
index name would be a variable as in [index: string]: TypeT
// index signaturesinterfaceProductI{name:string;price:number;}classSportsProductIimplementsProductI{constructor(publicname:string,publicprice:number){}}classProductRepository{randomThing:string="this will issues";constructor(...initialProducts:[string,ProductI][]){initialProducts.forEach(([id,product])=>this.add(id,product));}add(id:string,product:ProductI){this[id]=product;// index signature, properties are dynamically added to the object}get(id):ProductI[]{returnthis[id];// index signature, properties are dynamically accessed from the object}}constproductRepository=newProductRepository(["1",newSportsProductI("Football",100)]);constp1=productRepository.get("1");constp2=productRepository.get("2");console.log({p1,p2});// { p1: SportsProductI { name: 'Football', price: 100 }, p2: undefined }// -------------------------productRepository.add("3",newSportsProductI("Basketball",200));// dynamically adds a property to the object with the given idconstp3=productRepository.get("3");console.log({p3});// { p3: SportsProductI { name: 'Basketball', price: 200 } }// ------------------------- // index signature is not a good idea in classesconstpRandomThing=productRepository.get("randomThing");// this will issuesconsole.log({pRandomThing});//{ pRandomThing: 'this will issues' }// ProductRepository.get can give you any instance variable defined on its object, and that may not be of ProductI type.// better to avoid in classes, but it is useful in lateral objects.