Author: saqibkhan

  • Modules

    A module is designed with the idea to organize code written in TypeScript. Modules are broadly divided into −

    • Internal Modules
    • External Modules

    Internal Module

    Internal modules came in earlier version of Typescript. This was used to logically group classes, interfaces, functions into one unit and can be exported in another module. This logical grouping is named namespace in latest version of TypeScript. So internal modules are obsolete instead we can use namespace. Internal modules are still supported, but its recommended to use namespace over internal modules.

    Internal Module Syntax (Old)

    module TutorialPoint { 
       export function add(x, y) {  
    
      console.log(x+y); 
    } }

    Namespace Syntax (New)

    namespace TutorialPoint { 
       export function add(x, y) { console.log(x + y);} 
    }
    

    JavaScript generated in both cases are same

    var TutorialPoint;(function(TutorialPoint){functionadd(x, y){console.log(x + y);} 
       TutorialPoint.add = add;})(TutorialPoint ||(TutorialPoint ={}));

    External Module

    External modules in TypeScript exists to specify and load dependencies between multiple external js files. If there is only one js file used, then external modules are not relevant. Traditionally dependency management between JavaScript files was done using browser script tags (<script></script>). But thats not extendable, as its very linear while loading modules. That means instead of loading files one after other there is no asynchronous option to load modules. When you are programming js for the server for example NodeJs you dont even have script tags.

    There are two scenarios for loading dependents js files from a single main JavaScript file.

    • Client Side – RequireJs
    • Server Side – NodeJs

    Selecting a Module Loader

    To support loading external JavaScript files, we need a module loader. This will be another js library. For browser the most common library used is RequieJS. This is an implementation of AMD (Asynchronous Module Definition) specification. Instead of loading files one after the other, AMD can load them all separately, even when they are dependent on each other.

    Defining External Module

    When defining external module in TypeScript targeting CommonJS or AMD, each file is considered as a module. So its optional to use internal module with in external module.

    If you are migrating TypeScript from AMD to CommonJs module systems, then there is no additional work needed. The only thing you need to change is just the compiler flag Unlike in JavaScript there is an overhead in migrating from CommonJs to AMD or vice versa.

    The syntax for declaring an external module is using keyword export and import.

    Syntax

    //FileName : SomeInterface.ts 
    export interface SomeInterface { 
       //code declarations 
    }
    

    To use the declared module in another file, an import keyword is used as given below. The file name is only specified no extension used.

    import someInterfaceRef = require(./SomeInterface);
    

    Example

    Lets understand this using an example.

    // IShape.ts exportinterfaceIShape{draw();}// Circle.ts import shape =require("./IShape");exportclassCircleimplementsshape.IShape {publicdraw(){console.log("Cirlce is drawn (external module)");}}// Triangle.ts import shape =require("./IShape");exportclassTriangleimplementsshape.IShape {publicdraw(){console.log("Triangle is drawn (external module)");}}// TestShape.ts import shape =require("./IShape");import circle =require("./Circle");import triangle =require("./Triangle");functiondrawAllShapes(shapeToDraw: shape.IShape){
       shapeToDraw.draw();}drawAllShapes(newcircle.Circle());drawAllShapes(newtriangle.Triangle());

    The command to compile the main TypeScript file for AMD systems is −

    tsc --module amd TestShape.ts
    

    On compiling, it will generate following JavaScript code for AMD.

    File:IShape.js

    //Generated by typescript 1.8.10define(["require","exports"],function(require, exports){});

    File:Circle.js

    //Generated by typescript 1.8.10define(["require","exports"],function(require, exports){var Circle =(function(){functionCircle(){}
    
      Circle.prototype.draw=function(){console.log("Cirlce is drawn (external module)");};return Circle;})();
    exports.Circle = Circle;});

    File:Triangle.js

    //Generated by typescript 1.8.10define(["require","exports"],function(require, exports){var Triangle =(function(){functionTriangle(){}
    
      Triangle.prototype.draw=function(){console.log("Triangle is drawn (external module)");};return Triangle;})();
    exports.Triangle = Triangle;});

    File:TestShape.js

    //Generated by typescript 1.8.10define(["require","exports","./Circle","./Triangle"],function(require, exports, circle, triangle){functiondrawAllShapes(shapeToDraw){
    
      shapeToDraw.draw();}drawAllShapes(newcircle.Circle());drawAllShapes(newtriangle.Triangle());});</code></pre>

    The command to compile the main TypeScript file for Commonjs systems is

    tsc --module commonjs TestShape.ts
    

    On compiling, it will generate following JavaScript code for Commonjs.

    File:Circle.js

    //Generated by typescript 1.8.10var Circle =(function(){functionCircle(){}
       Circle.prototype.draw=function(){console.log("Cirlce is drawn");};return Circle;})();
    
    exports.Circle = Circle;

    File:Triangle.js

    //Generated by typescript 1.8.10var Triangle =(function(){functionTriangle(){}
       Triangle.prototype.draw=function(){console.log("Triangle is drawn (external module)");};return Triangle;})();
    exports.Triangle = Triangle;

    File:TestShape.js

    //Generated by typescript 1.8.10var circle =require("./Circle");var triangle =require("./Triangle");functiondrawAllShapes(shapeToDraw){
       shapeToDraw.draw();}drawAllShapes(newcircle.Circle());drawAllShapes(newtriangle.Triangle());

    Output

    Cirlce is drawn (external module)
    Triangle is drawn (external module)
    
  • Namespaces

    A namespace is a way to logically group related code. This is inbuilt into TypeScript unlike in JavaScript where variables declarations go into a global scope and if multiple JavaScript files are used within same project there will be possibility of overwriting or misconstruing the same variables, which will lead to the global namespace pollution problem in JavaScript.

    Defining a Namespace

    A namespace definition begins with the keyword namespace followed by the namespace name as follows −

    namespace SomeNameSpaceName {exportinterfaceISomeInterfaceName{}exportclassSomeClassName{}}

    The classes or interfaces which should be accessed outside the namespace should be marked with keyword export.

    To access the class or interface in another namespace, the syntax will be namespaceName.className

    SomeNameSpaceName.SomeClassName;
    

    If the first namespace is in separate TypeScript file, then it should be referenced using triple slash reference syntax.

    /// <reference path = "SomeFileName.ts" />
    

    The following program demonstrates use of namespaces −

    FileName :IShape.ts 
    ----------namespace Drawing {exportinterfaceIShape{draw();}}  
    
    FileName :Circle.ts 
    ----------/// <reference path = "IShape.ts" /> namespace Drawing {exportclassCircleimplementsIShape{publicdraw(){console.log("Circle is drawn");}  
    
      
      FileName :Triangle.ts 
      ----------/// &lt;reference path = "IShape.ts" /&gt; namespace Drawing {exportclassTriangleimplementsIShape{publicdraw(){console.log("Triangle is drawn");}} 
         
         FileName : TestShape.ts 
         /// &lt;reference path = "IShape.ts" /&gt;   /// &lt;reference path = "Circle.ts" /&gt; /// &lt;reference path = "Triangle.ts" /&gt;  functiondrawAllShapes(shape:Drawing.IShape){ 
            shape.draw();}drawAllShapes(newDrawing.Circle());drawAllShapes(newDrawing.Triangle());}}}</code></pre>

    The above code can be compiled and executed using the following command −

    tsc --out app.js TestShape.ts  
    
    node app.js
    

    On compiling, it will generate following JavaScript code(app.js).

    //Generated by typescript 1.8.10/// <reference path = "IShape.ts" />var Drawing;(function(Drawing){var Circle =(function(){functionCircle(){}
    
      Circle.prototype.draw=function(){console.log("Cirlce is drawn");};return Circle;}());
    Drawing.Circle = Circle;})(Drawing ||(Drawing ={}));/// <reference path = "IShape.ts" />var Drawing;(function(Drawing){var Triangle =(function(){functionTriangle(){}
      Triangle.prototype.draw=function(){console.log("Triangle is drawn");};return Triangle;}());
    Drawing.Triangle = Triangle;})(Drawing ||(Drawing ={}));/// <reference path = "IShape.ts" />/// <reference path = "Circle.ts" />/// <reference path = "Triangle.ts" />functiondrawAllShapes(shape){ shape.draw();}drawAllShapes(newDrawing.Circle());drawAllShapes(newDrawing.Triangle());

    When the above code is compiled and executed, it produces the following result −

    Circle is drawn 
    Triangle is drawn
    

    Nested Namespaces

    You can define one namespace inside another namespace as follows −

    namespace namespace_name1 {exportnamespace namespace_name2 {exportclassclass_name{}}}

    You can access members of nested namespace by using the dot (.) operator as follows −

    FileName : Invoice.ts  
    namespace tutorialPoint {exportnamespace invoiceApp {exportclassInvoice{publiccalculateDiscount(price:number){return price *.40;}}}} 
    FileName: InvoiceTest.ts 
    
    /// <reference path = "Invoice.ts" />var invoice =newtutorialPoint.invoiceApp.Invoice();console.log(invoice.calculateDiscount(500));

    The above code can be compiled and executed using the following command −

    tsc --out app.js InvoiceTest.ts 
    node app.js
    

    On compiling, it will generate following JavaScript code(app.js).

    //Generated by typescript 1.8.10var tutorialPoint;(function(tutorialPoint){var invoiceApp;(function(invoiceApp){var Invoice =(function(){functionInvoice(){}
    
         Invoice.prototype.calculateDiscount=function(price){return price *.40;};return Invoice;}());
      invoiceApp.Invoice = Invoice;})(invoiceApp = tutorialPoint.invoiceApp ||(tutorialPoint.invoiceApp ={}));})(tutorialPoint ||(tutorialPoint ={}));/// &lt;reference path = "Invoice.ts" /&gt;var invoice =newtutorialPoint.invoiceApp.Invoice();console.log(invoice.calculateDiscount(500));</code></pre>

    When the above code is compiled and executed, it produces the following result −

    200
    
  • Triple-Slash Directives

    Triple-slash directives are similar to single-line comments which contain a single XML tag. They are used to provide instructions to the TypeScript compiler about processing the TypeScript file. Triple-slash directives start with three forward slashes (///) and are mainly placed at the top of the code file.

    Triple-slash directives are mostly used in TypeScript. However, they can also be used with JavaScript projects that use TypeScript.

    In TypeScript, you can use triple-slash directives for two purposes.

    Reference Directives

    Reference directives are mainly used to tell the compiler to include another TypeScript file in the compilation process. They specify the dependencies between multiple files. Furthermore, they are also used to declare dependency packages and include libraries in the TypeScript file.

    Syntax

    /// <reference path="file_path" />

    In the above syntax, we have used three forward slashes first to define the triple-slash directives. After that, we used the XML tag <reference /> and added a path attribute to it. The path attribute takes the file path as a value.

    Similarly, you can also use other XML tags with three forward slashes.

    Types of Triple-Slash Reference Directives

    Here are the most commonly used triple-slash reference directives in a JavaScript/TypeScript environment:

    • ///<reference path=”…” /> − It is used to add a reference of one TypeScript file in another file.
    • ///<reference types=”…” /> − It is used to declare a dependency on a package.
    • ///<reference lib=”…” /> − It is used to include a library file that is part of the compilation context.

    Example: Referencing File Paths

    The main purpose of the triple-slash directives is to reference other files in particular TypeScript files.

    Filename: MathFunctions.ts

    In the below code, we have defined the add() function that takes two numbers as a parameter and returns the sum of them. We also export that function using the export keyword to use it in a different file.

    // This file will be referenced in app.tsexportfunctionadd(a:number, b:number):number{return a + b;}

    Filename: app.ts

    In this file, we have imported the add() function from the mathFunctions file. We have also used the reference directive in this file to tell the file path to the compiler.

    /// <reference path="mathFunctions.ts" />import{ add }from'./mathFunctions';console.log('Addition of 10 and 20 is:',add(10,20));

    Output

    Addition of 10 and 20 is 30
    

    Example: Referencing Type Definition

    The triple-slash directives can also be used to specify the reference type definition for the external module. You can use the <reference type=”module_type” /> directive to specify the type of the module.

    App.ts

    In the code below, we have imported the fs module. Before that, we have used the triple-slash directive to specify the type of the module.

    Next, we used the readFileSync() method of the fs module to read the file content.

    /// <reference types="node" />import*as fs from'fs';const content = fs.readFileSync('sample.txt','utf8');console.log('File content:', content);

    Sample.txt

    The below file contains plain text.

    Hello, this is a sample text file.
    

    Output

    File content: Hello, this is a sample text file.
    

    Example: Including Libraries

    When you want to use a specific library during the TypeScript compilation, you can use the ///<reference lib=”lib_name” /> triple-slash directive to specify it.

    In the code below, we have used the triple-slash directive to specify the es2015.array library.

    /// <reference lib="es2015.array" /> const str =["Hello","1","World","2"];const includesTwo = str.includes("2");console.log(includesTwo);

    Output

    true
    

    Module System Directives

    Module system directives are used to specify how modules are loaded in the TypeScript file. They are used to load modules asynchronously.

    There are two types of module system directives in TypeScript:

    • ///<amd-module name=”…” />: It is used to specify the module name for loading.
    • ///<amd-dependency path=”…” />: It is used to specify the path for the dependencies of the AMD module.

    Example

    In the code below, we have used the AMD-module triple-slash directive which specifies to load the MyModule module asynchronously.

    /// <amd-module name="MyModule" />exportfunctiondisplayMessage(){return"Hello from MyModule";}

    Triple-slash directives allow developers to enhance JavaScript projects by providing advanced compilation options, module loading, and integration with various types and libraries.

  • Generic Classes

    Generic Classes

    TypeScript Generic classes allow you to create a class that can work over a variety of data types rather than a single one. It increases the scalability and reusability of the code. Let’s understand how generic classes work in TypeScript.

    Syntax

    You can follow the syntax below to use the generic classes in TypeScript.

    classclass_name<T, U>{// Class body}let obj1 =newclass_name<data_type_1, data_type_2>();
    • In the above syntax, ‘class’ is a keyword to define a class.
    • ‘class_name’ is a valid identifier, representing the class name.
    • ‘<T, U>’ are type parameters specified in the angular bracket. You can specify as many as you want.
    • While defining the object of the class, you need to pass data types as an argument after the class name in the angular bracket.

    Example

    In the code below, we have defined the generic class named ‘Box’ which takes type parameter T.

    In the class, we have defined the ‘val’ variable of type T, and the constructor function which initializes the value of the ‘val’ variable.

    After that, we defined the getters and setters named get() and set(), respectively to get the value of the ‘val’ variable.

    Next, we have defined the ‘box1’ and ‘box2’ objects of the Box class which take number and string data type as a type parameter argument, respectively.

    // generic classclassBox<T>{// member variable
    
    val:T;// constructor with valueconstructor(value:T){this.val = value;}// Method to get valueget():T{returnthis.val;}// Method to set valueset(value:T):void{this.val = value;}}// create object of Box classlet box1 =newBox&lt;number&gt;(10);console.log(box1.get());// 10let box2 =newBox&lt;string&gt;("Hello");console.log(box2.get());// Hello</code></pre>

    On compiling, it will generate the following JavaScript code:

    // generic classclassBox{// constructor with valueconstructor(value){this.val = value;}// Method to get valueget(){returnthis.val;}// Method to set valueset(value){this.val = value;}}// create object of Box classlet box1 =newBox(10);
    console.log(box1.get());// 10let box2 =newBox("Hello");
    console.log(box2.get());// Hello

    Output

    The output of the above code is as follows

    10
    Hello
    

    Example

    In the TypeScript code below:

    • We have defined the 'Stack' class which takes a type parameter 'T'.
    • In the class, we have defined the private variable 'st' whose type is an array of type T.
    • The constructor function initializes the 'st' array.
    • Push() method takes the element of type 'T' as a parameter and inserts it in the 'st' array.
    • The pop() method removes the last element from the 'st' array and returns it.
    • The peek() method returns the last element from the array.
    • The isEmpty() method returns a boolean value based on whether the array is empty.
    • The size() method returns the size of the 'st' array.
    • Next, we have defined the object of the Stack class with the number data type, performed various operations using the methods of the Stack class.
    // Defining the class stackclassStack<T>{// Defining the private array to store the stack elementsprivate st:T[]=[];// Constructor to initialize the stack with initial contentsconstructor(initialContents?:T[]){if(initialContents){this.st = initialContents;}}// Method to push an element to the stackpush(item:T):void{this.st.push(item);}// Method to pop an element from the stackpop():T|undefined{returnthis.st.pop();}// Method to get the top element of the stackpeek():T|undefined{returnthis.st[this.st.length -1];}// Method to check if the stack is emptyisEmpty():boolean{returnthis.st.length ===0;}// Method to get the size of the stacksize():number{returnthis.st.length;}}// Usage Exampleconst numberStack =newStack<number>();
    numberStack.push(1);
    numberStack.push(2);
    numberStack.push(3);console.log(numberStack.peek());// Outputs: 3console.log(numberStack.pop());// Outputs: 3console.log(numberStack.peek());// Outputs: 2console.log(numberStack.isEmpty());// Outputs: falseconsole.log(numberStack.size());// Outputs: 2

    On compiling, it will generate the following JavaScript code:

    // Defining the class stackclassStack{// Constructor to initialize the stack with initial contentsconstructor(initialContents){// Defining the private array to store the stack elementsthis.st =[];if(initialContents){this.st = initialContents;}}// Method to push an element to the stackpush(item){this.st.push(item);}// Method to pop an element from the stackpop(){returnthis.st.pop();}// Method to get the top element of the stackpeek(){returnthis.st[this.st.length -1];}// Method to check if the stack is emptyisEmpty(){returnthis.st.length ===0;}// Method to get the size of the stacksize(){returnthis.st.length;}}// Usage Exampleconst numberStack =newStack();
    numberStack.push(1);
    numberStack.push(2);
    numberStack.push(3);
    console.log(numberStack.peek());// Outputs: 3
    console.log(numberStack.pop());// Outputs: 3
    console.log(numberStack.peek());// Outputs: 2
    console.log(numberStack.isEmpty());// Outputs: false
    console.log(numberStack.size());// Outputs: 2

    Output

    The output of the above code is as follows

    3
    3
    2
    false
    2
    

    Implementing Generic Interface with Generic Classes

    Generic classes can also implement the generic interfaces. So, developers can use a single generic interface to implement multiple generic classes, allowing them to reuse the code.

    Syntax

    You can follow the syntax below to implement the generic interface with generic classes.

    classclass_name<T>implementsinterface_name<T>{// Class body}
    • In the above syntax, 'class class_name<T>' defines the generic class.
    • 'implements' is a keyword to implement the interface with the class.
    • 'interface_name<T>' is a generic interface.

    Example

    In the example below:

    • We have defined the generic interface named 'dataBase', which defines the findById() and save() method.
    • Next, we have defined the generic class named 'memorydataBase', and implemented it with the 'dataBase' interface.
    • In the class, we have defined the 'items' map which stores the numeric value as a key, the value of type 'T'.
    • Next, we have implemented the findById() method, which accesses the value by key from the map and returns it.
    • The save() method stores the key-value pair in the 'items' map.
    • In the end, we created the object of the 'MemorydataBase' class and performed various operations using this method.
    // Defining a generic interfaceinterfacedataBase<T>{findById(id:number):T|undefined;save(item:T):void;}// Defining a class that implements the generic interfaceclassMemorydataBase<T>implementsdataBase<T>{// Defining a private property that is a map of itemsprivate items =newMap<number, T>();// Implementing the findById methodfindById(id:number):T|undefined{returnthis.items.get(id);}// Implementing the save methodsave(item:T):void{const id =this.items.size +1;this.items.set(id, item);}}// Creating an instance of the MemorydataBase classconst repo =newMemorydataBase<string>();
    repo.save("Hello");console.log(repo.findById(1));// Outputs: Hello

    On compiling, it will generate the following JavaScript code:

    // Defining a class that implements the generic interfaceclassMemorydataBase{constructor(){// Defining a private property that is a map of itemsthis.items =newMap();}// Implementing the findById methodfindById(id){returnthis.items.get(id);}// Implementing the save methodsave(item){const id =this.items.size +1;this.items.set(id, item);}}// Creating an instance of the MemorydataBase classconst repo =newMemorydataBase();
    repo.save("Hello");
    console.log(repo.findById(1));// Outputs: Hello

    Output

    The output of the above code is as follows

    Hello

    You may use the 'extends' keyword to use the various constraints with the generic classes. It's always a good idea to use generic parameters, constraints, interfaces, and classes in your code to make it scalable and reusable.

  • Generic Interfaces

    Generic Interfaces

    In TypeScript, the generic interface is similar to the interface but it is defined with one or more type parameters. These type parameters allow the interface to be reused with different data types while still enforcing type safety and consistency.

    An interface in TypeScript is a way to define the structure of the object or class. It specifies the expected format which objects or classes should have, allowing to ensure consistency and type safety in the code.

    Syntax

    You can follow the syntax below to define the generic interfaces.

    interfaceIGeneric<T>{
    
    value1:T;
    value2:T;}</code></pre>

    In the above syntax, 'IGeneric' is a typed interface, which accepts the custom data type. In the interface, we have used the type 'T' as a value of the value1 and value2 properties.

    Example

    In the code below, we have created the 'IGeneric' interface with the custom type 'T'. It has value1 and value2 properties of type 'T'.

    Next, we have defined the 'obj' object of the IGeneric interface type and used the number data type as a generic type with the interface. After that, we print the value of the 'value1' property of the object in the output.

    // Generic interfaceinterfaceIGeneric<T>{
    
    value1:T;
    value2:T;}// Object of generic interfacelet obj: IGeneric&lt;number&gt;={
    value1:10,
    value2:20};console.log(obj.value1);</code></pre>

    On compiling, it will generate the following JavaScript code:

    // Object of generic interfacelet obj ={
    
    value1:10,
    value2:20};
    console.log(obj.value1);

    Output

    The output of the above example code is as follows

    10

    Example

    In this code, we use the 'T' and 'U' both data types with the 'IGeneric' interface. The value1 property is of type 'T' and the value2 property is of type 'U' in the interface.

    Next, we have defined the 'obj' object of interface type with number and string custom data types.

    // Generic interfaceinterfaceIGeneric<T, U>{
    
    value1:T;
    value2:U;}// Define object with a generic interfacelet obj: IGeneric&lt;number,string&gt;={
    value1:10,
    value2:"Hello"};console.log(obj.value2);</code></pre>

    On compiling, it will generate the following JavaScript code:

    // Define object with a generic interfacelet obj ={
    
    value1:10,
    value2:"Hello"};
    console.log(obj.value2);

    Output

    The output of the above example code is as follows

    Hello

    Example

    The below example is very similar to the previous one, but the 'IGeneric' interface contains the merge() method which takes the parameters of type 'U' and returns the value of type 'U'.

    While defining the 'obj' object, we used the number and string data types with the interface. It means the merge() method will take two string parameters, and return a string value.

    In the output, the code prints the concatenated string, which we have passed as parameters of the merge() method.

    // Generic interfaceinterfaceIGeneric<T, U>{
    
    value1:T;// method that returns the value of type Umerge:(a:U, b:U)=&gt;U;}// Define object with a generic interfacelet obj: IGeneric&lt;number,string&gt;={
    value1:10,merge:(a, b)=&gt; a + b
    };console.log(obj.merge("Hello","world!"));// Hello world!

    On compiling, it will generate the following JavaScript code:

    // Define object with a generic interfacelet obj ={
    
    value1:10,merge:(a, b)=&gt; a + b
    }; console.log(obj.merge("Hello","world!"));// Hello world!

    Output

    Helloworld!

    Generic Interface as a Function Type

    Developers can also use the generic interface as a function type. This enables you to use the same function interface across diverse types and scenarios, ensuring type safety without sacrificing flexibility.

    Example

    In the code below, 'func_interface' accepts the generic types T and U. It defines the structure for the function, which takes the parameter of type 'T' and returns the value of type 'U'.

    Next, we have defined the function expression which returns the string length and stored it in the 'stringToLength' variable. The type of the function expression is defined using the generic interface with string and number data types.

    Similarly, we have defined another function that converts a number to a string, and its type is func_interface with string and number data type.

    Next, we invoke the functions and print their output in the console.

    // Define a generic interface for a functioninterfacefunc_interface<T, U>{(input:T):U;}// A specific function that matches the func_interface interfaceconst stringToLength: func_interface<string,number>=(input)=>{return input.length;};// Using the functionconst result =stringToLength("Hello, TypeScript!");// returns 18console.log(result);// Another function that matches the func_interface interfaceconst numberToString: func_interface<number,string>=(input)=>{returnNumber: ${input};};// Using the second functionconst output =numberToString(123);// returns "Number: 123"console.log(output);

    On compiling, it will generate the following JavaScript code:

    // A specific function that matches the func_interface interfaceconststringToLength=(input)=>{return input.length;};// Using the functionconst result =stringToLength("Hello, TypeScript!");// returns 18
    console.log(result);// Another function that matches the func_interface interfaceconstnumberToString=(input)=>{returnNumber: ${input};};// Using the second functionconst output =numberToString(123);// returns "Number: 123"
    console.log(output);

    Output

    18
    Number: 123
    

    In short, generic interfaces allow developers to reuse the interfaces with multiple data types. Generic interfaces can be used as an object or function type, allowing to reuse the single function or object with different structures.

  • Generic Constraints

    In TypeScript, generic constraints allow you to specify limitations on the types that can be used with a generic type parameter. This adds an extra layer of type safety by ensuring the generic code only works with compatible data types.

    Problem Examples

    Before going deep into the generic constraints, let’s understand the problem examples where you need to apply generic constraints.

    Example

    In the code below, we have created the merge() generic function, which takes the two objects as parameters, merges them using the spread operator, and returns the merged object.

    After that, we invoke the merge() function by passing two objects as an argument, and it successfully prints the merged object.

    // Generic function to merge two objectsfunctionmerge<T, U>(obj1:T, obj2:U){return{...obj1,...obj2};}// Invoke the functionconst mergedObj =merge({name:'Sam'},{age:30});console.log(mergedObj);// Output: {name: 'Sam', age: 30}  

    On compiling, it will generate the following JavaScript code.

    // Generic function to merge two objectsfunctionmerge(obj1, obj2){return Object.assign(Object.assign({}, obj1), obj2);}// Invoke the functionconst mergedObj =merge({ name:'Sam'},{ age:30});
    console.log(mergedObj);// Output: {name: 'Sam', age: 30}  

    Output

    The output of the above example code is as follows

    {name: 'Sam', age: 30}
    

    The merge() function has generic parameters. So, it can take the value of any data type as an argument including an object. What if you pass the boolean value as a second argument? Let’s look at the example below.

    Example

    The below example code is very similar to the previous one. We have just changed the second argument of the merge() function to a Boolean value while calling it.

    // Generic function to merge two objectsfunctionmerge<T, U>(obj1:T, obj2:U){return{...obj1,...obj2};}// Invoke the functionconst mergedObj =merge({name:'Sam'},true);console.log(mergedObj);// Output: {name: 'Sam'}  

    On compiling, it will generate the following JavaScript code.

    // Generic function to merge two objectsfunctionmerge(obj1, obj2){return Object.assign(Object.assign({}, obj1), obj2);}// Invoke the functionconst mergedObj =merge({ name:'Sam'},true);
    console.log(mergedObj);// Output: {name: 'Sam'}  

    Output

    The output of the above example code is as follows

    {name: 'Sam'}
    

    The above code prints the first object only in the output because the second argument was a Boolean value but not an object. To solve the problem found in the above example code, developers can use generic constraints.

    How Generic Constraints Work in TypeScript?

    Generic constraints allow us to limit the generic parameters to accept values of only particular types. i.e. you can narrow down the type of the generic parameters.

    Syntax

    You can follow the syntax below to use the generic constraints with generic parameters.

    functionmerge<T extends object>(obj1:T){// Code to execute}
    • In the above syntax, ‘T’ is a generic type, ‘extends’ is a keyword, and ‘object’ is a data type.
    • Here, ‘T’ accepts only values having an ‘object’ data type.

    Let’s understand more about generic constraints via the example below. Now, if you try to compile the below code, you will get the compilation error as the generic parameter can accept only object argument but we are passing Boolean value.

    // Generic function to merge two objectsfunctionmerge<T extends object, U extends object>(obj1:T, obj2:U){return{...obj1,...obj2 };}// Invoke the functionconst mergedObj =merge({ name:'Sam'},true);console.log(mergedObj);

    On compiling the above TypeScript code, the compiler shows the following error

    Argument of type 'boolean' is not assignable to parameter of type 'object'.
    

    This way, we can limit the generic parameters to accept the values of a particular data type.

    Example (Extending Generic Types with Interfaces)

    Let’s understand the code below with a step-by-step explanation.

    • We have defined the ‘Person’ interface which contains name, age, and email properties.
    • Next, we have defined the ‘Employee’ interface which contains ’empCode’, and ’empDept’ properties.
    • The merge() function contains two generic parameters T of type Person and U of type Employee.
    • In the merge() function, we merge both objects.
    • After that, we have defined two objects of type Person, and Employee, respectively.
    • Next, we invoke the merge() function by passing objects as an argument, and the code runs without any error.
    // Define Person interfaceinterfacePerson{
    
    name:string;
    age:number;
    email:string;}// Define Employee interfaceinterface Employee {
    empCode:number;
    empDept:string;}// Generic function which takes Objects of the Person and Employee interfaces typesfunctionmerge&lt;T extends Person, U extends Employee&gt;(obj1:T, obj2:U){return{...obj1,...obj2 };}// Create two objectsconst person: Person ={ name:'John', age:30, email:'[email protected]'};const employee: Employee ={ empCode:1001, empDept:'IT'};// Invoke the functionconst mergedObj =merge(person, employee);console.log(mergedObj);</code></pre>

    On compiling, it will generate the following JavaScript code.

    // Generic function which takes Objects of the Person and Employee interfaces typesfunctionmerge(obj1, obj2){return Object.assign(Object.assign({}, obj1), obj2);}// Create two objectsconst person ={ name:'John', age:30, email:'[email protected]'};const employee ={ empCode:1001, empDept:'IT'};// Invoke the functionconst mergedObj =merge(person, employee);
    console.log(mergedObj);

    Output

    The output of the above example code is as follows

    {
      name: 'John',
      age: 30,
      email: '[email protected]',
      empCode: 1001,
      empDept: 'IT'
    }
    

    Using Type Parameters in Generic Constraints

    TypeScript also allows you to define a type parameter, which is constrained by another parameter of the same function.

    Let's understand it via the example below.

    Example

    In the code below, type 'U' extends the keys of the object received in the first parameter. So, it will accept the keys of the obj object as an argument to avoid errors in the function body.

    Next, we invoke the getValue() function by passing the 'obj' object as an argument. It prints the key value in the output.

    // Parameter U ensures that the key is a valid key of the object T.functiongetValue<T extends object, U extends keyof T>(obj:T, key:U){return obj[key];}// Define an objectconst obj ={
    
    name:'Sam',
    age:34};// Get the value of the key 'name'const name1 =getValue(obj,'name');console.log(name1);// Sam</code></pre>

    On compiling, it will generate the following JavaScript code.

    // Parameter U ensures that the key is a valid key of the object T.functiongetValue(obj, key){return obj[key];}// Define an objectconst obj ={
    
    name:'Sam',
    age:34};// Get the value of the key 'name'const name1 =getValue(obj,'name');
    console.log(name1);// Sam

    Output

    The output of the above example code is as follows

    Sam
    

    We understood that generic constraints are useful to accept the values of the specific data types as a parameter, instead of taking values of all data types.

  • Generics

    Generics are a powerful feature in TypeScript that allow you to write reusable code that can work with different types of data. They act like placeholders that can be filled with specific data types when you use the generic code. This improves code flexibility and maintainability.

    Problem Examples

    Before going deep into TypeScript generics, let’s understand the problem examples where you need to apply generics.

    Let’s start with the example below, where you want to log the value of the variable passed as a parameter.

    Example

    In the code below, we have defined the printVar() function which takes the number value as a parameter and logs the value in the console. Next, we invoke the function by passing 10 as an argument.

    functionprintVar(val:number){console.log(val);// Prints the value of val}printVar(10);// Invokes the function with a number

    On compiling, it will generate the following JavaScript code.

    functionprintVar(val){
    
    console.log(val);// Prints the value of val}printVar(10);// Invokes the function with a number</code></pre>

    Output

    Its output is as follows

    10
    

    Now, let's suppose you want to extend the use case of the printVar() function to print the value of other types of variables like string, boolean, etc. One way of doing that is as shown in the example below.

    Example

    In the code below, the printVar() function can accept the arguments of number, string, or boolean type.

    functionprintVar(val:number|string|boolean){console.log(val);// Prints the value of val}printVar(true);// Invokes the function with a boolean value

    On compiling, it will generate the following JavaScript code.

    functionprintVar(val){
    
    console.log(val);// Prints the value of val}printVar(true);// Invokes the function with a boolean value</code></pre>

    Output

    The output is as follows

    true
    

    What if you want to print the array or object value? You need to extend the types of the 'val' parameter, and it makes the code complex to read.

    Another way to use the parameters of 'any' data type is as shown in the example below.

    Example

    In the code below, the type of the 'val' parameter is any. So, it can accept any type of value as an argument.

    functionprintVar(val:any){console.log(val);// Prints the value of val}printVar("Hello world!");// Invokes the function with a boolean value

    On compiling, it will generate the following JavaScript code

    functionprintVar(val){
    
    console.log(val);// Prints the value of val}printVar("Hello world!");// Invokes the function with a boolean value</code></pre>

    Output

    Its output is as follows

    Hello world!
    

    The problem with the above code is that you won't have a reference to the data type inside the function. Whether you pass a string, number, boolean, array, etc. as a function argument, you will get the 'any' type of the variable in the function.

    Here, generic functions come into the picture.

    TypeScript Generics

    In TypeScript, generics is a concept that allows to creation of reusable components like functions, classes, interfaces, etc. It creates a function, classes, etc. which can work with multiple data types instead of the single data type. In short, it allows developers to create programs that can work with multiple data types and are scalable in the long term.

    Syntax

    Users can follow the syntax below to use the generic variables with functions in TypeScript.

    functionprintVar<T>(val:T){// execute the code}printVar(val);
    • Developers can use the type variable in the angular bracket(<>) after the function name.
    • After that, you can use the type variable T as a type of the parameters.
    • Here, developers can use any valid identifier instead of 'T'.
    • After that, you can call the function with the value of any data type, and the function automatically captures the data type of the variable.

    Example

    In the example below, the printVar() function is a generic function, which takes the value of any data type as an argument, and prints it.

    After that, we have invoked the function with array, object, and boolean value. In the output, users can observe that it prints the value of different types of variables without any error.

    functionprintVar<T>(val:T){// T is a generic typeconsole.log("data: ", val);}let arr =[1,2,3];let obj ={ name:"John", age:25};printVar(arr);// Val is arrayprintVar(obj);// Val is ObjectprintVar(true);// Val is boolean

    On compiling, it will generate the following JavaScript code.

    functionprintVar(val){
    
    console.log("data: ", val);}let arr =&#91;1,2,3];let obj ={ name:"John", age:25};printVar(arr);// Val is arrayprintVar(obj);// Val is ObjectprintVar(true);// Val is boolean</code></pre>

    Output

    The output of the above example code is as follows

    data:  [ 1, 2, 3 ]
    data:  { name: 'John', age: 25 }
    data:  true
    

    Example

    In this code, we printVar() function is a generic function, which takes the type of the variable value passed as a parameter. While invoking the function, we have passed the value of different data types, and users can observe the type of each variable in the output.

    functionprintVar<T>(val:T){// T is a generic typeconsole.log("data: ",typeof val);}printVar(2);// Val is numberprintVar("Hello");// Val is stringprintVar(true);// Val is boolean

    On compiling, it will generate the following JavaScript code.

    functionprintVar(val){
    
    console.log("data: ",typeof val);}printVar(2);// Val is numberprintVar("Hello");// Val is stringprintVar(true);// Val is boolean</code></pre>

    Output

    The output of the above example code is as follows

    data:  number
    data:  string
    data:  boolean
    

    Example

    In the code below, the concatenate() function takes two parameters of type T and U, respectively. It uses the spread operator to concatenate the value of the 'first' and 'second' parameters.

    Next, we call the function to concatenate two strings and arrays. In the output, we can observe that the concatenate() function executes without any error and prints the final output in the console.

    functionconcatenate<T, U>(first:T, second:U):T&U{return{...first,...second};}// Example usage with stringsconst resultString =concatenate("Hello, ","world!");console.log(resultString);// Output: Hello, world!// Example usage with arraysconst resultArray =concatenate([1,2,3],[4,5,6]);console.log(resultArray);// Output: [1, 2, 3, 4, 5, 6]

    On compiling, it will generate the following JavaScript code.

    functionconcatenate(first, second){return Object.assign(Object.assign({}, first), second);}// Example usage with stringsconst resultString =concatenate("Hello, ","world!");
    console.log(resultString);// Output: Hello, world!// Example usage with arraysconst resultArray =concatenate([1,2,3],[4,5,6]);
    console.log(resultArray);// Output: [1, 2, 3, 4, 5, 6]

    Output

    The output of the above example code is as follows

    {
      '0': 'w',
      '1': 'o',
      '2': 'r',
      '3': 'l',
      '4': 'd',
      '5': '!',
      '6': ' '
    }
    { '0': 4, '1': 5, '2': 6 }
    

    Benefits of Generics

    Here are some benefits of using generics in TypeScript.

    • Type Safety: Generics enforce type consistency, reducing runtime errors by catching mistakes at compile time.
    • Code Reusability: Developers can define a single generic function, class, or interface that works with different data types. It reduces the code duplication.
    • Improved Readability: By using Generics, developers can write cleaner and easy-to-read code.
    • Enhanced Performance: You can increase the performance of the application by avoiding unnecessary type casting and checks via using generics.
  • Template Literal Types

    Template string literals are used to create dynamic strings with variables in JavaScript. Similarly, in TypeScript you can use template literal types to create the dynamic types, which is introduced in the TypeScript 4.1 version. The syntax of the template literal types in TypeScript is very similar to the JavaScript template string literals.

    Syntax

    You can follow the syntax below to use the template literal types in TypeScript.

    typetype_name=some_text ${variable_name}
    • In the above syntax, ‘type_name’ is the name of the type.
    • You need to use the backticks (“) to define template literal types.
    • The ‘some_text’ can be any string value that will remain constant.
    • To add any dynamic value in the type, you need to add the variable name inside the ‘${}’ structure.

    Examples

    Let’s understand the Template Literal Types in details with the help of some examples in TypeScript.

    Example: Basic Use

    In the code below, we have defined the ‘World’ type which contains the World as a value. The ‘Greeting’ type is a template literal type whose value changes based on the value of the ‘World’ variable.

    Next, we created the ‘greeting’ variable of type ‘Greeting’ which contains a Hello World value. If we try to assign another value to it, the TypeScript compiler throws an error.

    typeWorld="world";// Defining a template literal typetype Greeting =Hello, ${World};const greeting: Greeting ="Hello, world";// Correct// const wrongGreeting: Greeting = "Hello, everybody"; // Error: This type is "Hello, world" specifically.console.log(greeting);

    On compiling, it will generate the following JavaScript code:

    const greeting ="Hello, world";// Correct// const wrongGreeting: Greeting = "Hello, everybody"; // Error: This type is "Hello, world" specifically.console.log(greeting);

    The above example code will produce the following output:

    Hello, world
    

    Example: Combining with Union Types

    In this code, the ‘size’ type contains the union of multiple type values. The ‘ResponseMessage’ type is a template literal type whose value changes based on the value of the ‘Size’ type.

    The selectSize() function takes a string of type ‘Size’ as a parameter, and returns the value of type ‘ResponseMessage’.

    Next, we call the function by passing ‘medium’ as a function parameter. If we try to pass any other string value than the ‘small’, ‘medium’, and ‘large’ as a function parameter, it will throw an error.

    // Creating a type using literal typetypeSize="small"|"medium"|"large";// Custom template literal typetype ResponseMessage =${Size} size selected;// Function to select sizefunctionselectSize(size: Size): ResponseMessage {return${size} size selected;}// Call the functionconst response: ResponseMessage =selectSize("medium");// "medium size selected"console.log(response);

    On compiling, it will generate the following JavaScript code:

    // Function to select sizefunctionselectSize(size){return${size} size selected;}// Call the functionconst response =selectSize("medium");// "medium size selected"console.log(response);

    Its output is as follows:

    medium size selected
    

    Example: Conditional String Patterns

    In this example, we have used the generic types with constraints. Here, ‘T extends Status’ means, the value of T can be only from the status. The statusMessage type is a combination of the type ‘T’ and ‘status’ strings.

    The printStatus() function takes the type T as a parameter, which can be any one value from the ‘Status’ type, and returns the value of type ‘statusMessage’.

    Next, we have called the printStatus() function by passing the loading value which is a part of the ‘Status’ type.

    // Definition of the Status typetypeStatus="loading"|"success"|"error";// T can be any of the values in the Status typetype StatusMessage<TextendsStatus>=${T}Status;// Function to return a message based on the statusfunctionprintStatus<T extends Status>(status:T): StatusMessage<T>{return${status} Statusas StatusMessage<T>;}// Call the function with the "loading" statusconst loadingMessage =printStatus("Loading");// "loadingStatus"console.log(loadingMessage);

    On compiling, it will generate the following JavaScript code:

    // Function to return a message based on the statusfunctionprintStatus(status){return${status} Status;}// Call the function with the "loading" statusconst loadingMessage =printStatus("Loading");// "loadingStatus"console.log(loadingMessage);

    Its output is as follows:

    Loading Status
    

    Example: API Route Generation

    The below example demonstrates the real-time practical use of the template literal types.

    Then, We have defined the Method type which can have any value from the REST API method type. The Entity type defines the entity objects.

    The getRoute() method takes the entity and method of type Entity and Method, respectively as a parameter. It returns the string value of type APIRoute after combining the entity and method values.

    // Defining the Method and Entity typestypeMethod="get"|"post"|"delete";typeEntity="user"|"post";// Defining the ApiRoute typetype ApiRoute =/${Entity}/${Method};// Defining the getRoute functionfunctiongetRoute(entity: Entity, method: Method): ApiRoute {return/${entity}/${method}as ApiRoute;}// Using the getRoute functionconst userRoute =getRoute("user","post");// "/user/post"console.log(userRoute);

    On compiling, it will generate the following JavaScript code:

    // Defining the getRoute functionfunctiongetRoute(entity, method){return/${entity}/${method};}// Using the getRoute functionconst userRoute =getRoute("user","post");// "/user/post"console.log(userRoute);

    Its output is as follows:

    /user/post
    

    In TypeScript, the template literal type is mainly used when you need to create dynamic types.

  • Mapped Types

    Mapped types in TypeScript are used to create new types by transforming the properties of existing types. Mapping means creating new elements from the existing ones after making some changes. Similarly, mapped type means creating new types after making changes to the existing types. So, you can reuse the existing types to create new types.

    Built-in Mapped Types

    The built-in mapped type allows you to transform the existing types without manually transforming types.

    Here are the built-in mapped types available in TypeScript.

    • Partial<T>: It creates a new type by making all properties optional of the existing type.
    • Required<T>: It creates a new type by making all properties required of the existing type.
    • Readonly<T>: Makes all properties optional in a new type.
    • Record<K, T>: Creates a type with a set of properties K of type T. Useful for mapping properties of the same type.
    • Pick<T, K>: Creates a new type by picking a set of properties K from T.
    • Omit<T, K>: Creates a new type by omitting a set of properties K from T.
    • Exclude<T, U>: It creates a new type by excluding all properties from T that are assignable to U.
    • Extract<T, U>: It creates a new type by extracting all properties from T that are assignable to U.
    • NonNullable<T>: Constructs a type by excluding null and undefined from T.

    Examples

    Let’s understand some of the commonly used built-in mapped types with help of some program examples in TypeScript.

    Example: Using the Partial Type

    In the code below, we have created the Person type containing the name and age properties. After that, we used the Partial utility type to create a new type from the Person type.

    The PartialPerson type has all the properties of the Person class but all are optional. We have defined the partialPerson object of the PartialPerson type, which contains only the name property as all properties are optional.

    // Creating the Person typetypePerson={
    
    name:string;
    age:number;};// Using the Partial mapped typetype PartialPerson = Partial&lt;Person&gt;;// Creating an object of type PartialPersonconst partialPerson: PartialPerson ={
    name:"John",//   Age is optional};// Outputconsole.log(partialPerson);</code></pre>

    On compiling, it will generate the following JavaScript code.

    // Creating an object of type PartialPersonvar partialPerson ={
    
    name:"John"};// Outputconsole.log(partialPerson);</code></pre>

    Output

    { name: 'John' }
    

    Example: Using the ReadOnly Type

    In the code below, we used the Readonly utility type to create a new type having all properties readonly from the Person type. If we try to change any property of the partialPerson object, it will throw an error as it is readonly.

    // Creating the Person typetypePerson={
    
    name:string;
    age:number;};// Using the ReadOnly mapped typetype PartialPerson = Readonly&lt;Person&gt;;// Creating an object of type PartialPersonconst partialPerson: PartialPerson ={
    name:"John",
    age:30};// Trying to change the name property// partialPerson.name = "Doe"; // Error: Cannot assign to 'name' because it is a read-only property.// Outputconsole.log(partialPerson);</code></pre>

    On compiling, it will generate the following JavaScript code.

    // Creating an object of type PartialPersonconst partialPerson ={
    
    name:"John",
    age:30};// Trying to change the name property// partialPerson.name = "Doe"; // Error: Cannot assign to 'name' because it is a read-only property.// Outputconsole.log(partialPerson);</code></pre>

    Output

    { name: 'John', age: 30 }
    

    Example: Using the Pick Type

    In the code below, we have created the Animal type which contains 3 properties.

    After that, we used the Pick utility type to pick only name and species properties from the Animal type and create a new type named 'AnimalNameAndSpecies'.

    The animalNameAndSpecies object is of type AnimalNameAndSpecies, which contains only name and species properties.

    // Creating the Animal typetypeAnimal={
    
    name:string;
    species:string;
    age:number;};// Using the Pick utility type to create a new typetypeAnimalNameAndSpecies= Pick&lt;Animal,'name'|'species'&gt;;// Creating an object of the AnimalNameAndSpecies typeconst animalNameAndSpecies: AnimalNameAndSpecies ={
    name:'Milo',
    species:'Dog'};console.log(animalNameAndSpecies);</code></pre>

    On compiling, it will generate the following JavaScript code.

    // Creating an object of the AnimalNameAndSpecies typeconst animalNameAndSpecies ={
    
    name:'Milo',
    species:'Dog'};console.log(animalNameAndSpecies);</code></pre>

    Output

    { name: 'Milo', species: 'Dog' }
    

    Creating Custom Mapped Types

    Utility functions have limited functionalities. So, it is important to create the custom mapped types to map types according to your own conventions.

    Syntax

    You can follow the syntax below to create a custom-mapped type.

    typeMyMappedType<T>={[PinkeyofT]: NewType;};
    • 'T' is an existing type from which we need to create a new custom-mapped type.
    • 'P' is a property of the type.
    • The 'keyof' operator gets each key of the type 'T'.
    • NewType is a new type to assign to the type property 'P'.

    Example: Making all Properties Boolean

    In the code below, we created the Booleanify type which takes the type as a generic parameter. Inside the type body, we traverse each key of the type 'T' using the '[P in keyof T]' code, and assign a boolean to it as we are changing the type of all properties to a boolean.

    The boolPerson object has all properties of the boolean type.

    // Defining the Person typetypePerson={
      name:string;
      age:number;};// Creating the custom mapped typetype Booleanify<T>={// Making all properties of Person type to Boolean[PinkeyofT]:boolean;};// Creating an object of Booleanify typeconst boolPerson: Booleanify<Person>={
      name:true,
      age:true,};console.log(boolPerson);

    On compiling, it will generate the following JavaScript code:

    // Creating an object of Booleanify typeconst boolPerson ={
    
    name:true,
    age:true,};console.log(boolPerson);</code></pre>

    Output

    The above example code will produce the following output:

    { name: true, age: true }
    

    Example: Making all Properties Optional

    In this example, we are making all properties of existing types optional without using the built-in utility type Partial.

    Here, the Animal class contains the name and legs properties. In the code [key in keyof Animal]?: Animal[key], question mark (?) makes properties optional, and Animal[key] helps in keeping the property type same.

    Now, In the customAnimal object, all properties are optional.

    // Defining the Animal typetypeAnimal={
    
    name:string;
    legs:number;};// Creating custom type and making all properties optional using mappingtypeCustomAnimal={&#91;key inkeyof Animal]?: Animal&#91;key];};// Creating an object of type CustomAnimallet customAnimal: CustomAnimal ={
    name:"Dog",
    legs:4,};console.log(customAnimal);</code></pre>

    On compiling, it will generate the following JavaScript code:

    // Creating an object of type CustomAnimallet customAnimal ={
    
    name:"Dog",
    legs:4,};console.log(customAnimal);</code></pre>

    Output

    The output of the above example code is as follows:

    { name: 'Dog', legs: 4 }
    

    You can either use the utility functions or create custom mapped types to reuse the existing types to create new types. These custom mapped types improve the readability of the code and help developers to make code maintainable.

  • Conditional Types

    In TypeScript, conditional types allow you to assign a type to the variables based on the conditions. This enables you to define types that dynamically change based on certain conditions. This feature is very useful for large-scale applications, where you need dynamic typing according to the different situations.

    Basic Conditional Types

    We will use the ternary (?:) operator to use the conditional types. It will evaluate the condition, and based on the true or false result, it will select the new type.

    Syntax

    You can follow the syntax below to use the conditional types.

    typeA= Type extendsanotherType? TrueType : FalseType;
    • In the above syntax, the ‘Type extends anotherType’ condition will be evaluated first.
    • If the condition is true, ‘type A’ will contain the ‘TrueType’. Otherwise, it will contain the ‘FalseType’.
    • Here, ‘extends’ keywords check whether ‘Type’ is the same as ‘anotherType’ or at least contains all properties of the ‘anotherType’ type.

    Example

    In the code below, we have defined the ‘car’ type which contains the name, model, and year properties. We have also defined the ‘Name’ type which contains only ‘name’ properties.

    The ‘carNameType’ type variable stores either a string or any value based on the evaluation result of the ‘Car extends Name’ condition. Here, the condition will be evaluated as true as the ‘Car’ type contains all properties of the ‘Name’ type.

    After that, we have created the ‘carName’ variable of ‘carNameType’ and printed it in the console.

    typeCar={
    
    name:string,
    model:string,
    year:number,}typeName={ name:string}// If Car extends Name, then carNameType is string, otherwise it is anytypecarNameType= Car extendsName?string:any;// string// Define a variable carName of type carNameTypeconst carName: carNameType ='Ford';console.log(carName);// Ford</code></pre>

    On compiling, it will generate the following JavaScript code.

    // Define a variable carName of type carNameTypeconst carName ='Ford';console.log(carName);// Ford 

    Output

    Ford
    

    Generic Conditional Types

    Now, let's learn the generic conditional types. In TypeScript, generic types are similar to the parameters in the function. It allows developers to define a conditional type such that it can be used at multiple places in the code. It provides flexibility to use the different types in the conditional statements.

    Syntax

    You can follow the syntax below to use the generic conditional types.

    typeConditionalType<T>=TextendsType1? TrueType : FalseType;
    • In the above syntax, 'conditionalType<T>' has a type 'T' parameter, and 'conditionalType' is the name of the type.
    • The condition 'T extends Type1' checks whether type 'T' extends 'Type1'.
    • If the condition evaluates true, 'TrueType' will be assigned to the 'ConditionalType'. Otherwise, 'FalseType' will be assigned.

    Example

    In the code below, 'IsNumArray<T>' is a generic type that takes type 'T' as a parameter. It checks whether the type of the 'T' is an array of numbers (number[]). If yes, it returns 'number'. Otherwise, it returns 'string'.

    After that, we defined the 'num' and 'str' variables and used the 'IsNumArray' type with them. For the 'num' variable, we have used the 'number[]' parameter, and for the 'str' variable, we have used the 'string[]' parameter.

    // Generic conditional typestypeIsNumArray<T>=Textendsnumber[]?number:string;const num: IsNumArray<number[]>=5;// numberconst str: IsNumArray<string[]>='5';// stringconsole.log(num);console.log(str);

    On compiling, it will generate the following JavaScript code.

    const num =5;// numberconst str ='5';// stringconsole.log(num);console.log(str);

    Output

    5
    5
    

    Conditional Type Constraints

    Conditional type constraints are also called type assertions or conditional type predicates. It is used to add constraints on generic types. The generic types are reusable but if you want to reuse them for particular data types like array, number, etc. then you should add constraints with the generic type.

    Syntax

    You can follow the syntax below to use the conditional type constraints.

    typeConditionalType<T extends T1 | T2>=TextendsType1? TrueType : FalseType;

    In the above syntax, we have added the constraints for the type parameter 'T'. It accepts the values of type 'T1' and 'T2'.

    Example

    In the code below, 'CondionalType' is a generic type. It takes a number or string type as a value of the type parameter 'T'. The conditional type returns the number if the type of 'T' is a number. Otherwise, it returns a string.

    Next, we reused the ConditionalType type by passing the number and string as a type parameter.

    You can notice that when we try to use the 'boolean' as a type parameter, it throws an error. So, it allows to reuse of this generic conditional type with limited types.

    // Defining the conditional type with constraintstypeConditionalType<T extends number | string>=Textendsnumber?number:string;let x: ConditionalType<number>=10;let y: ConditionalType<string>='Hello';// let z: ConditionalType<boolean> = true; // Error: Type 'boolean' does not satisfy the constraint 'number | string'.console.log("The value of x is: ", x);console.log("The value of y is: ", y);

    On compiling, it will generate the following JavaScript code.

    let x =10;let y ='Hello';// let z: ConditionalType<boolean> = true; // Error: Type 'boolean' does not satisfy the constraint 'number | string'.console.log("The value of x is: ", x);console.log("The value of y is: ", y);

    Output

    The value of x is:  10
    The value of y is:  Hello
    

    Inferring Within Conditional Types

    In TypeScript, inferring within the conditional types means to infer the types with the conditional type definition. It allows you to create a more flexible type of transformation.

    For example, if you have an array of elements and match it with the type 'T' in the conditional types. After that, you want to return the type of the array element if the condition becomes true, in such case inferring conditional types is useful.

    Syntax

    You can use the 'infer' keyword to infer within the conditional types.

    typeFlatten<Type>= Type extendsArray<infer Item>? Item : Type;

    In the above syntax, the 'infer' keyword is used to extract the type of the array element.

    Example

    In the code below, we have created the 'Flatten' conditional type. We used the infer keyword with the type 'U'. It extracts the type of 'U' from the array of elements having type 'U'.

    For 'Flatten<boolean>', it will return a boolean. So, if we try to assign a string value to the 'bool1' variable, it throws an error.

    // Inferring within the conditional typestypeFlatten<T>=Textends(inferU)[]?U:T;let bool: Flatten<boolean[]>=true;// let bool1: Flatten<boolean> = "s"; // Error as string can't be assigned to booleanconsole.log(bool);

    On compiling, it will generate the following JavaScript code.

    let bool =true;// let bool1: Flatten<boolean> = "s"; // Error as string can't be assigned to booleanconsole.log(bool);

    Output

    true
    

    This way, you can use the conditional types in TypeScript. You can use the generic types, and infer them to make type transformation more flexible.