Type assertions

Type assertions in TypeScript are a way to tell the compiler explicitly that you know the type of a variable better than it does. They are used when you have more information about the type of a value than TypeScript’s type checker is able to infer. Type assertions do not perform any runtime checks or conversions; they simply inform the compiler of the assumed type.

Syntax

Type assertions can be done in two ways:

Angle-bracket syntax:


let someValue: any = "this is a string";
let strLength: number = (someValue).length;

as syntax:


let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;

Examples

Example 1: Narrowing a type

Consider a scenario where you know a variable is of a more specific type than what TypeScript infers:


let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;

console.log(strLength); // Outputs: 16

Here, TypeScript would not know the exact type of someValue, but you, as the developer, know it’s a string. The type assertion someValue as string informs the compiler about this knowledge.

Example 2: Working with DOM elements

When working with the DOM, TypeScript often cannot infer the exact type of an element, especially when using methods like document.getElementById.


let inputElement = document.getElementById("input") as HTMLInputElement;
inputElement.value = "Hello, world!";

Without the type assertion, document.getElementById would return HTMLElement | null, and TypeScript would not know about the value property of HTMLInputElement.

Type Assertions vs Type Casting

Type assertions in TypeScript do not perform any runtime checks or type conversions. They purely tell the compiler to treat a value as a specific type, which is different from type casting in other languages that convert the type at runtime.

For example, in TypeScript:


let someValue: any = "this is a string";
let strLength: number = (someValue).length; // Type assertion

In contrast, in languages like C# or Java, type casting may involve actual runtime type conversion.

Type Assertions with Union Types

When dealing with union types, type assertions can help specify the exact type you are working with.


function getLength(value: string | number): number {
  if ((value as string).length !== undefined) {
    return (value as string).length;
  } else {
    return value.toString().length;
  }
}

console.log(getLength("Hello")); // Outputs: 5
console.log(getLength(12345));   // Outputs: 5

What is any type

In TypeScript, the any type is a type that can hold any value. It acts as a way to opt-out of type checking and allow a variable to have any type of value. Using any gives you the flexibility of JavaScript’s dynamic typing but at the expense of losing the benefits of TypeScript’s static type checking.

When to Use any

The any type can be useful in several scenarios:

Migrating from JavaScript to TypeScript: When gradually migrating a JavaScript codebase to TypeScript, any can be used to allow existing code to function without requiring complete type annotations.

Dynamic content: When dealing with data from dynamic content like third-party libraries, user inputs, or JSON responses where the structure is not known at compile time.

Prototyping: During the early stages of development or prototyping where the exact types are not yet defined.

Defining and Using any

Example


let looselyTyped: any = 4;
looselyTyped = "Now it's a string";
looselyTyped = true; // Now it's a boolean

function logMessage(message: any): void {
  console.log(message);
}

logMessage("Hello, world!");  // Outputs: Hello, world!
logMessage(123);              // Outputs: 123
logMessage({ key: "value" }); // Outputs: { key: 'value' }

In the example above, the variable looselyTyped can hold any value, and the function logMessage can accept an argument of any type.

Accessing Properties on any

When using any, you can access properties and methods that might not exist without causing a type error. This can be useful but also risky, as it might lead to runtime errors.


let obj: any = { x: 0 };
console.log(obj.x); // Outputs: 0
console.log(obj.y); // No error, but `y` is undefined
obj.foo();          // No compile error, but runtime error if `foo` is not a function

Losing Type Safety

Using any can lead to losing the type safety that TypeScript provides. This means TypeScript will not help catch type-related errors at compile time.


let someValue: any = "this is a string";
let strLength: number = someValue.length; // OK
let unknownValue: any = 4;
console.log(unknownValue.toUpperCase()); // Runtime error: `toUpperCase` is not a function

What is Enum

In TypeScript, an enum (short for “enumeration”) is a way to define a set of named constants. Enums allow you to create a collection of related values that can be numeric or string-based. They are useful for representing a set of discrete values, such as days of the week, months of the year, or status codes.

Types of Enums

TypeScript supports two types of enums: numeric and string.

Numeric Enums

Numeric enums are the default type of enums in TypeScript. The first value in a numeric enum has a default value of 0, and each subsequent value is incremented by 1.


enum Direction {
  Up,
  Down,
  Left,
  Right
}

let directionUp: Direction = Direction.Up;
let directionRight: Direction = Direction.Right;
console.log(directionUp); // Outputs: 0
console.log(directionRight); // Outputs: 3

You can also specify custom numeric values for the enum members:


enum Direction {
  Up = 1,
  Down,
  Left = 5,
  Right
}

console.log(Direction.Up);    // Outputs: 1
console.log(Direction.Down);  // Outputs: 2
console.log(Direction.Left);  // Outputs: 5
console.log(Direction.Right); // Outputs: 6

String Enums

String enums are enums where each member has a string value. They provide a clearer and more readable way to define enum values.


enum Direction {
  Up = "UP",
  Down = "DOWN",
  Left = "LEFT",
  Right = "RIGHT"
}

let direction: Direction = Direction.Up;
console.log(direction); // Outputs: "UP"

Using enum in switch case

Enums can be used to define and check for specific values within a set.


enum Status {
  New,
  InProgress,
  Completed
}

function updateStatus(status: Status): void {
  switch (status) {
    case Status.New:
      console.log("Starting new task.");
      break;
    case Status.InProgress:
      console.log("Task is in progress.");
      break;
    case Status.Completed:
      console.log("Task is completed.");
      break;
  }
}

updateStatus(Status.InProgress); // Outputs: "Task is in progress."

What is Tuple

In TypeScript, a tuple is a typed array with a fixed number of elements whose types are known and can be different. Tuples allow you to express an array where the type of a fixed number of elements is known but need not be the same.

Defining Tuples

You define a tuple by specifying the types of its elements inside square brackets.

Example:


let tuple: [string, number];
tuple = ["Hello", 42]; // OK
// tuple = [42, "Hello"]; // Error: Type 'number' is not assignable to type 'string' and vice versa

Accessing Tuple Elements

You can access the elements of a tuple using array indexing.


let tuple: [string, number];
tuple = ["Hello", 42];

console.log(tuple[0]); // Outputs: Hello
console.log(tuple[1]); // Outputs: 42

Tuple Operations

Pushing to Tuples

You can push new elements to a tuple, but it may lose the tuple type and become a regular array type.


let tuple: [string, number];
tuple = ["Hello", 42];

tuple.push("World");
console.log(tuple); // Outputs: ["Hello", 42, "World"]

Destructuring Tuples

You can destructure tuples just like arrays.


let tuple: [string, number];
tuple = ["Hello", 42];

let [greeting, answer] = tuple;

console.log(greeting); // Outputs: Hello
console.log(answer);   // Outputs: 42

Tuple Types with Optional Elements

Tuples can also have optional elements.


let tuple: [string, number?];
tuple = ["Hello"];
tuple = ["Hello", 42];

console.log(tuple); // Outputs: ["Hello"] or ["Hello", 42]

Tuple Rest Elements

TypeScript 3.0 introduced support for rest elements in tuple types, which can represent a variable number of elements.


let tuple: [string, ...number[]];
tuple = ["Hello"];
tuple = ["Hello", 40, 41, 42];

console.log(tuple); // Outputs: ["Hello", 40, 41, 42]

What is Modules

Modules in TypeScript are a way to organize and encapsulate code into reusable and maintainable chunks. They allow you to split your code into separate files and then import/export functionalities between them. This makes the code easier to manage, especially in large projects. Here’s a detailed overview of how to work with modules in TypeScript:

Creating and Exporting Modules

In TypeScript, you can create a module by simply writing some code in a .ts file and exporting what you want to be accessible from other modules.

Named Exports

Named exports allow you to export multiple bindings from a module. Each export must be explicitly named.


// math.ts
export function add(a: number, b: number): number {
  return a + b;
}

export function subtract(a: number, b: number): number {
  return a - b;
}

Default Exports

A default export can be a function, class, object, or anything else. There can only be one default export per module.


// logger.ts
export default function log(message: string): void {
  console.log(message);
}

Importing Modules

You can import modules using the import statement.

Importing Named Exports

To import named exports, you use curly braces to specify the names of the exports you want to import.


// app.ts
import { add, subtract } from './math';

console.log(add(5, 3));  // Outputs: 8
console.log(subtract(5, 3));  // Outputs: 2

Importing Default Exports

To import a default export, you don’t use curly braces.


// app.ts
import log from './logger';

log('Hello, world!');  // Outputs: Hello, world!

Aliases

You can use aliases when importing to avoid name conflicts or to make names clearer.


// app.ts
import { add as addition, subtract as subtraction } from './math';

console.log(addition(5, 3));  // Outputs: 8
console.log(subtraction(5, 3));  // Outputs: 2

Re-exporting Modules

You can re-export modules, which is useful for creating a single entry point for multiple modules.


// index.ts
export { add, subtract } from './math';
export { default as log } from './logger';

Now you can import everything from index.ts instead of importing from individual modules.


// app.ts
import { add, subtract, log } from './index';

console.log(add(5, 3));  // Outputs: 8
console.log(subtract(5, 3));  // Outputs: 2
log('Hello, world!');  // Outputs: Hello, world!

Generics

Generics in TypeScript provide a way to create reusable components that can work with any data type. They allow you to define functions, classes, and interfaces that work with types specified by the caller, ensuring type safety while providing the flexibility to use different types as needed.

Key Concepts of Generics

Type Variables Generics use type variables to denote types that are specified when the generic is instantiated. The most commonly used type variable is T.

Generic Functions Functions can be made generic to operate on a variety of types while maintaining type safety.

Generic Classes Classes can be defined with generics to handle different types of data in a type-safe manner.

Generic Interfaces Interfaces can use generics to define structures that can work with multiple types.

Generic Functions

A generic function can accept arguments of any type and return a value of that same type.

Example


function identity<T>(arg: T): T {
    return arg;
}

let output1 = identity<string>("myString"); // Explicit type
let output2 = identity<number>(100); // Explicit type
let output3 = identity("myString"); // Type inference

Generic Classes

A generic class can operate on various data types while ensuring type safety.

Example


class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;

    constructor(zeroValue: T, addFn: (x: T, y: T) => T) {
        this.zeroValue = zeroValue;
        this.add = addFn;
    }
}

let myGenericNumber = new GenericNumber<number>(0, (x, y) => x + y);
console.log(myGenericNumber.add(5, 10)); // Output: 15

Generic Interfaces

Interfaces can use generics to describe a structure that can work with multiple types.

Example


interface GenericIdentityFn<T> {
    (arg: T): T;
}

function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: GenericIdentityFn<number> = identity;
console.log(myIdentity(5)); // Output: 5

Using Multiple Type Variables

Generics can use multiple type variables to create more complex and flexible components.

Example


function merge<T, U>(obj1: T, obj2: U): T & U {
    return { ...obj1, ...obj2 };
}

let mergedObj = merge({ name: "Alice" }, { age: 25 });
console.log(mergedObj); // Output: { name: "Alice", age: 25 }

Generic Constraints

Sometimes you want to limit the kinds of types that can be passed as type arguments. You can use constraints to achieve this.

Example


interface Lengthwise {
    length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length); // Now we know it has a .length property, so no error
    return arg;
}

loggingIdentity({ length: 10, value: 3 });

Default Type Parameters

You can provide default types for generics to make the generic components more flexible and easier to use.

Example:


function createArray<T = string>(length: number, value: T): T[] {
    return Array(length).fill(value);
}

let stringArray = createArray(3, "x"); // Output: ["x", "x", "x"]
let numberArray = createArray<number>(3, 5); // Output: [5, 5, 5]

Function in Typescript

Functions in TypeScript are similar to functions in JavaScript, but TypeScript allows you to define the types of the parameters and the return type of the function, adding type safety and improving the development experience. Here, we will cover various aspects of functions in TypeScript, including basic function syntax, optional and default parameters, rest parameters, function overloading, and more.

Basic Function Syntax

Here is a simple example of a function in TypeScript:


function greet(name: string): string {
    return `Hello, ${name}!`;
}

console.log(greet("John")); // Output: Hello, John!

In this example:

name: string specifies that the name parameter must be a string.

: string after the parentheses specifies that the function returns a string.

Function with Optional Parameters

You can define optional parameters using the ? symbol:


function greet(name: string, age?: number): string {
    if (age) {
        return `Hello, ${name}. You are ${age} years old.`;
    } else {
        return `Hello, ${name}.`;
    }
}

console.log(greet("John")); // Output: Hello, John.
console.log(greet("Tom", 25)); // Output: Hello, Tom. You are 25 years old.

Default Parameters

You can also provide default values for parameters:


function greet(name: string, greeting: string = "Hello"): string {
    return `${greeting}, ${name}!`;
}

console.log(greet("John")); // Output: Hello, John!
console.log(greet("Tom", "Hi")); // Output: Hi, Tom!

Rest Parameters

Rest parameters allow you to pass an arbitrary number of arguments to a function:


function sum(...numbers: number[]): number {
    return numbers.reduce((acc, num) => acc + num, 0);
}

console.log(sum(1, 2, 3)); // Output: 6
console.log(sum(1, 2, 3, 4, 5)); // Output: 15

Function Types


type GreetFunction = (name: string) => string;

const greet: GreetFunction = (name: string) => {
    return `Hello, ${name}!`;
};

console.log(greet("John")); // Output: Hello, John!

Arrow Functions

TypeScript also supports arrow functions, which can be especially useful for inline functions:


const greet = (name: string): string => {
    return `Hello, ${name}!`;
};

console.log(greet("John")); // Output: Hello, John!

Overloading

TypeScript allows you to define multiple signatures for a function:


function greet(name: string): string;
function greet(name: string, age: number): string;
function greet(name: string, age?: number): string {
    if (age !== undefined) {
        return `Hello, ${name}. You are ${age} years old.`;
    } else {
        return `Hello, ${name}.`;
    }
}

console.log(greet("John")); // Output: Hello, John.
console.log(greet("Tom", 25)); // Output: Hello, Tom. You are 25 years old.

In this example, there are two function signatures for greet, one with a single name parameter and one with both name and age parameters. The implementation handles both cases.

These examples cover the basics of functions in TypeScript. You can use these concepts to write more complex functions as needed.

Nullish coalescing in Typescript

Nullish coalescing in TypeScript is a feature that allows you to provide a default value when dealing with null or undefined values. It is represented by the ?? operator. This operator is useful in scenarios where you want to ensure that a variable has a meaningful value, falling back to a default if the variable is null or undefined.

Syntax and Usage

The syntax for nullish coalescing is straightforward:


let value = someVariable ?? defaultValue;

Here, someVariable is checked, and if it is null or undefined, defaultValue is used. Otherwise, someVariable is used.

Example

Let’s look at a few examples to understand how nullish coalescing works:


let name: string | null = null;
let defaultName: string = "Default Name";

let result = name ?? defaultName;
console.log(result);  // Output: "Default Name"

in this example, name is null, so the defaultName is used.

Difference from the Logical OR (||) Operator

It’s important to understand the difference between the nullish coalescing operator (??) and the logical OR operator (||). The logical OR operator will return the right-hand side value if the left-hand side value is falsy (e.g., false, 0, NaN, "", null, undefined). In contrast, the nullish coalescing operator only considers null and undefined.


let value1 = 0;
let defaultValue1 = 10;

let result1 = value1 || defaultValue1;
console.log(result1);  // Output: 10, because 0 is falsy

let result2 = value1 ?? defaultValue1;
console.log(result2);  // Output: 0, because 0 is not null or undefined

Nested Nullish Coalescing

You can use nullish coalescing with nested expressions to handle multiple potential null or undefined values:


let firstName: string | null = null;
let middleName: string | null = null;
let lastName: string = "Taylor";

let fullName = firstName ?? middleName ?? lastName;
console.log(fullName);  // Output: "Taylor"

Combining with Optional Chaining

Nullish coalescing can be particularly powerful when combined with optional chaining (?.). Optional chaining allows you to safely access nested properties that might not exist, without throwing an error.


type User = {
  profile?: {
    name?: string;
  };
};

let user: User = {};

let userName = user.profile?.name ?? "Anonymous";
console.log(userName);  // Output: "Anonymous"

In this example, user.profile?.name safely attempts to access name, returning undefined if profile or name does not exist. The nullish coalescing operator then provides a fallback value of “Anonymous”.

What is Interface in Typescript

In TypeScript, an interface is a powerful way to define the structure of an object. Interfaces allow you to specify the types of properties and methods that an object can have. They provide a way to define contracts within your code, ensuring that certain classes or objects adhere to specific structures and types.

Defining an Interface

An interface is defined using the interface keyword followed by the interface name. Inside the interface, you define properties and methods with their respective types.


interface Person {
  name: string;
  age: number;
  greet(): void;
}

In this example:

Person is an interface with three members: name, age, and greet.

name and age are properties of type string and number, respectively.

greet is a method that returns void.

Using Interfaces

You can use interfaces to type-check objects, ensuring they adhere to the defined structure.


const person: Person = {
  name: "Alice",
  age: 30,
  greet() {
    console.log(`Hello, my name is ${this.name}`);
  },
};

person.greet();  // Output: Hello, my name is Alice

In this example, the person object conforms to the Person interface, so TypeScript does not raise any type errors.

Implementing Interfaces in Classes

Classes can implement interfaces to ensure they meet the required structure and behavior.


class Employee implements Person {
  constructor(public name: string, public age: number) {}

  greet() {
    console.log(`Hello, my name is ${this.name}`);
  }
}

const employee = new Employee("John", 40);
employee.greet();  // Output: Hello, my name is John

In this example:

The Employee class implements the Person interface.

The class provides implementations for the name, age, and greet members as required by the Person interface.

Optional Properties

Interfaces can define optional properties using the ? symbol.


interface Car {
  brand: string;
  model: string;
  year?: number;  // Optional property
}

const myCar: Car = {
  brand: "Toyota",
  model: "Corolla",
};

In this example, year is an optional property, so objects adhering to the Car interface can include year but are not required to.

Readonly Properties

Interfaces can define readonly properties that cannot be changed after initialization.


interface Book {
  readonly title: string;
  author: string;
}

const myBook: Book = {
  title: "TypeScript Guide",
  author: "John Taylor",
};

// myBook.title = "New Title";  // Error: Cannot assign to 'title' because it is a read-only property.

In this example, title is a readonly property, so any attempt to modify it will result in a compile-time error.

Extending Interfaces

Interfaces can extend other interfaces, allowing you to build on existing structures.


interface Animal {
  name: string;
}

interface Dog extends Animal {
  breed: string;
}

const myDog: Dog = {
  name: "Buddy",
  breed: "Golden Retriever",
};

In this example:

Animal is an interface with a name property.

Dog extends Animal, adding a breed property.

myDog is an object that conforms to the Dog interface, including both name and breed.

static class in Typescript

In TypeScript, the static keyword is used to define class members that belong to the class itself rather than to any instance of the class. Static members can include properties and methods that are shared by all instances of the class and can be accessed directly on the class itself without creating an instance.

Static Properties

Static properties are variables that are shared across all instances of a class. They can be accessed directly on the class.


class MyClass {
  static staticProperty: string = 'I am a static property';

  static logStaticProperty() {
    console.log(MyClass.staticProperty);
  }
}

// Accessing static property
console.log(MyClass.staticProperty);  // Output: I am a static property

Static Methods

Static methods are functions that belong to the class itself rather than any object created from the class. These methods can be called on the class itself.


class Calculation {
  static square(x: number): number {
    return x * x;
  }

  static cube(x: number): number {
    return x * x * x;
  }
}

// Calling static methods
console.log(Calculation.square(5));  // Output: 25
console.log(Calculation.cube(3));    // Output: 27

Static Initialization Blocks

TypeScript supports static initialization blocks, which allow for more complex static member initialization. This feature is part of ECMAScript 2022.


class Config {
  static settings: { [key: string]: string } = {};

  static {
    Config.settings['appName'] = 'MyApp';
    Config.settings['version'] = '1.0.0';
  }

  static getSetting(key: string): string | undefined {
    return Config.settings[key];
  }
}

// Accessing static settings
console.log(Config.getSetting('appName'));  // Output: MyApp
console.log(Config.getSetting('version'));  // Output: 1.0.0

Use Cases for Static Members

Utility Functions: Functions that perform operations that do not depend on instance-specific data.


class Utility {
  static max(a: number, b: number): number {
    return a > b ? a : b;
  }
}

console.log(Utility.max(10, 20));  // Output: 20

Constants: Defining constants that are used throughout the application.


class Constants {
  static readonly PI: number = 3.14159;
  static readonly MAX_USERS: number = 100;
}

console.log(Constants.PI);  // Output: 3.14159
console.log(Constants.MAX_USERS);  // Output: 100

Configuration and Settings: Storing global configuration or settings.


class AppConfig {
  static readonly ENVIRONMENT: string = 'production';
  static readonly VERSION: string = '1.0.0';
}

console.log(AppConfig.ENVIRONMENT);  // Output: production
console.log(AppConfig.VERSION);  // Output: 1.0.0

Abstract class in Typescript

Abstract classes in TypeScript provide a way to define classes that cannot be instantiated directly. They are used to create a base class with common functionality and structure, which other classes can extend and implement. Abstract classes can include both implemented methods and abstract methods. Abstract methods are methods that do not have an implementation and must be implemented by any subclass.

Here’s a detailed look at how abstract classes work in TypeScript:

Defining an Abstract Class

To define an abstract class, use the abstract keyword before the class keyword. Within an abstract class, you can define abstract methods using the abstract keyword. Abstract methods must be implemented by any derived classes.

Example of an Abstract Class


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

  // Abstract method (does not have an implementation)
  abstract makeSound(): void;

  // Normal method
  move(distance: number): void {
    console.log(`${this.name} moved ${distance} meters.`);
  }
}

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

  // Implementing the abstract method
  makeSound(): void {
    console.log('Woof! Woof!');
  }
}

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

  // Implementing the abstract method
  makeSound(): void {
    console.log('Meow!');
  }
}

const dog = new Dog('Tiger');
dog.makeSound();  // Output: Woof! Woof!
dog.move(10);  // Output: Tiger moved 10 meters.

const cat = new Cat('Miti');
cat.makeSound();  // Output: Meow!
cat.move(7);  // Output: Miti moved 7 meters.

In this example:

  • Animal is an abstract class with an abstract method makeSound and a regular method move.
  • Dog and Cat are concrete classes that extend Animal and implement the makeSound method.

Key Points

Cannot Instantiate Abstract Class: You cannot create an instance of an abstract class directly.


const animal = new Animal('Pet Animal');  // Error: Cannot create an instance of an abstract class.

Abstract Methods: Abstract methods do not have an implementation in the abstract class. Subclasses must provide an implementation for these methods.


abstract class Shape {
  abstract calculateArea(): number;
}

class Circle extends Shape {
  constructor(public radius: number) {
    super();
  }

  calculateArea(): number {
    return Math.PI * this.radius * this.radius;
  }
}

const circle = new Circle(5);
console.log(circle.calculateArea());  // Output: 78.53981633974483

Regular Methods: Abstract classes can also contain methods with implementations, which can be inherited by subclasses.


abstract class Vehicle {
  constructor(public name: string) {}

  abstract startEngine(): void;

  stopEngine(): void {
    console.log(`${this.name} engine stopped.`);
  }
}

class Car extends Vehicle {
  constructor(name: string) {
    super(name);
  }

  startEngine(): void {
    console.log(`${this.name} engine started.`);
  }
}

const car = new Car('Tata');
car.startEngine();  // Output: Tata engine started.
car.stopEngine();  // Output: Tata engine stopped.

Protected Members: You can use protected members in abstract classes to allow access within derived classes while preventing access from outside the class hierarchy.


abstract class Machine {
  protected status: string = 'inactive';

  abstract activate(): void;

  getStatus(): string {
    return this.status;
  }
}

class Robot extends Machine {
  activate(): void {
    this.status = 'active';
    console.log('Robot activated.');
  }
}

const robot = new Robot();
robot.activate();  // Output: Robot activated.
console.log(robot.getStatus());  // Output: active

Conclusion

Abstract classes in TypeScript are powerful tools for creating a common base with shared functionality and structure that other classes can extend. They help enforce a contract for derived classes to implement specific methods while providing a mechanism to share common behavior.

Inheritance in Typescript

Inheritance in TypeScript allows you to create a new class based on an existing class. This is useful for creating a hierarchy of classes that share common characteristics while allowing for specific variations. TypeScript uses the extends keyword to establish inheritance.

Here’s an overview of how inheritance works in TypeScript, along with some examples:

Basic Inheritance

You define a base class and a derived class that extends the base class:


class Animal {
  name: string;

  constructor(name: string) {
    this.name = name;
  }

  move(distance: number = 0) {
    console.log(`${this.name} moved ${distance} meters.`);
  }
}

class Dog extends Animal {
  bark() {
    console.log('Woof! Woof!');
  }
}

const dog = new Dog('Tiger');
dog.bark();  // Output: Woof! Woof!
dog.move(10);  // Output: Rex moved 10 meters.

In this example:

  • Animal is the base class.
  • Dog is the derived class that extends Animal.
  • The Dog class inherits the name property and the move method from the Animal class.
Overriding Methods

A derived class can override methods from the base class:


class Bird extends Animal {
  move(distance: number = 0) {
    console.log(`${this.name} flew ${distance} meters.`);
  }
}

const bird = new Bird('crow');
bird.move(10);  // Output: crow flew 10 meters.

Here, the move method in the Bird class overrides the move method in the Animal class.

Using super

You can call methods from the base class using the super keyword:


class Snake extends Animal {
  constructor(name: string) {
    super(name);  // Call the base class constructor
  }

  move(distance: number = 5) {
    console.log('Slithering...');
    super.move(distance);  // Call the base class method
  }
}

const snake = new Snake('Slither');
snake.move();  // Output: Slithering... Slither moved 5 meters.

In this example:

  • The Snake class calls the constructor of the Animal class using super(name).
  • The move method in the Snake class calls the move method of the Animal class using super.move(distance).

readonly modifier in Typescript

In TypeScript, the readonly modifier is used to make a property immutable, meaning that once it is assigned a value, it cannot be changed. This is particularly useful for defining constants or values that should not be modified after their initial assignment.

Using readonly with Properties

Here’s an example to illustrate the usage of the readonly modifier:


class Person {
    // Readonly properties
    readonly name: string;
    readonly age: number;

    constructor(name: string, age: number) {
        this.name = name;
        this.age= age;
    }

    // Method to display person details
    displayDetails(): void {
        console.log(`Name: ${this.name}, Age: ${this.age}`);
    }
}

const person = new Person("John", 40);
console.log(person.name); // John
console.log(person.age); // 40

// Trying to modify readonly properties (will cause an error)
// person.name = "Tom"; // Error: Cannot assign to 'name' because it is a read-only property.
// person.age= 38; // Error: Cannot assign to 'age' because it is a read-only property.

In this example, the properties name and age are marked as readonly, which means their values can only be set when the object is created (in the constructor) and cannot be changed afterwards.

Using readonly with Arrays and Tuples

The readonly modifier can also be used with arrays and tuples to make them immutable:


// Readonly array
const readonlyArray: readonly number[] = [1, 2, 3];
// readonlyArray[0] = 10; // Error: Index signature in type 'readonly number[]' only permits reading.
// readonlyArray.push(4); // Error: Property 'push' does not exist on type 'readonly number[]'.

// Readonly tuple
let readonlyTuple: readonly [number, string] = [1, "hello"];
// readonlyTuple[0] = 10; // Error: Index signature in type 'readonly [number, string]' only permits reading.

Using readonly with Interfaces and Type Aliases

You can also define readonly properties in interfaces and type aliases:


interface Person {
    readonly name: string;
    readonly age: number;
}

const person: Person = { name: "John", age: 40};
// person.name = "Tom"; // Error: Cannot assign to 'name' because it is a read-only property.

type Point = {
    readonly x: number;
    readonly y: number;
};

const point: Point = { x: 10, y: 20 };
// point.x = 5; // Error: Cannot assign to 'x' because it is a read-only property.

Summary

  • readonly modifier: Used to make properties immutable.
  • Usage: Can be applied to properties, arrays, tuples, interfaces, and type aliases.
  • Effect: Once a readonly property is assigned a value, it cannot be changed.

The readonly modifier is a powerful feature in TypeScript that helps enforce immutability and ensures that certain values remain constant throughout the lifecycle of an object.

Access Modifiers in Typescript

Access modifiers in TypeScript are keywords that set the accessibility of classes, properties, methods, and constructors. They determine whether a class member can be accessed from outside the class. TypeScript provides three primary access modifiers:

Public: This is the default modifier. Members with the public modifier are accessible from anywhere.

Private: Members with the private modifier are accessible only within the class they are defined in.

Protected: Members with the protected modifier are accessible within the class they are defined in and in derived classes (subclasses).

Example of Access Modifiers

Here’s a detailed example demonstrating the use of public, private, and protected access modifiers:


class Animal {
    // Public property
    public name: string;

    // Private property
    private age: number;

    // Protected property
    protected species: string;

    // Constructor with public and private properties
    constructor(name: string, age: number, species: string) {
        this.name = name;
        this.age = age;
        this.species = species;
    }

    // Public method
    public getName(): string {
        return this.name;
    }

    // Private method
    private getAge(): number {
        return this.age;
    }

    // Protected method
    protected getSpecies(): string {
        return this.species;
    }
}

class Dog extends Animal {
    constructor(name: string, age: number, species: string) {
        super(name, age, species);
    }

    // Public method accessing protected property
    public getDetails(): string {
        return `Name: ${this.name}, Species: ${this.species}`;
    }

    // Public method trying to access private property (will cause an error)
    public tryToGetAge(): number {
        // return this.age; // Error: Property 'age' is private and only accessible within class 'Animal'.
        return 0; // Placeholder
    }
}

const animal = new Animal("Lion", 5, "Panthera leo");
console.log(animal.name); // Accessible
// console.log(animal.age); // Error: Property 'age' is private and only accessible within class 'Animal'.
// console.log(animal.species); // Error: Property 'species' is protected and only accessible within class 'Animal' and its subclasses.

const dog = new Dog("Buddy", 3, "Canis lupus familiaris");
console.log(dog.getName()); // Accessible
console.log(dog.getDetails()); // Accessible
// console.log(dog.getSpecies()); // Error: Property 'getSpecies' is protected and only accessible within class 'Animal' and its subclasses.
Summary

Public:

  • Accessible from anywhere.
  • Default if no modifier is specified.
  • Example: public name: string;

Private:

  • Accessible only within the class where it is defined.
  • Example: private age: number;

Protected:

  • Accessible within the class and its subclasses.
  • Example: protected species: string;

These access modifiers help in implementing encapsulation and protecting the internal state and behavior of objects in TypeScript.

What is Types

TypeScript offers a robust type system that includes both basic and advanced types. Here’s an overview of the different types available in TypeScript:

Basic Types

Number: Represents all numbers (both integer and floating-point).


let num: number = 10;

String: Represents text data.


let name: string = "Hello John";

Boolean: Represents true/false values.


let isTrue: boolean = true;

Array: Represents a collection of elements of a specific type.


let numArray: number[] = [1, 2, 3];
let strArray: string[] = ["a", "b", "c"];

Tuple: Represents an array with a fixed number of elements of specified types.


let rule: [number, string] = [1, "Admin"];

Enum: Represents a set of named constants.


enum Color { Red, Green, Blue }
let c: Color = Color.Green;

Any: Represents any type and disables type checking for that variable.


let data: any;
data = "Hello";
console.log(data);
data = 15;
console.log(data);

Void: Represents the absence of any type, commonly used as the return type for functions that do not return a value.


function message(): void {
    console.log("This returns nothing");
}

Null and Undefined: Represents the absence of a value. By default, null and undefined are subtypes of all other types.


let n: null = null;
let u: undefined = undefined;

Never: Represents the type of values that never occur (e.g., a function that always throws an error).


function error(message: string): never {
    throw new Error(message);
}
Advanced Types

Union Types: Represents a value that can be one of several types.


let value: string | number;
value = "Hello";
value = 45;

Intersection Types: Combines multiple types into one.


interface A { a: number; }
interface B { b: string; }
let ab: A & B = { a: 45, b: "Hello" };

Type Aliases: Creates a new name for a type.


type StringOrNumber = string | number;
let value: StringOrNumber;
value = "Hello";
value = 45;

Literal Types: Represents specific values.


let literal: "Hello" | "world";
literal = "Hello"; // valid
literal = "world"; // valid
// literal = "Hi"; // invalid

Nullable Types: Allows a type to be null or undefined.


let nullableString: string | null | undefined;
nullableString = "hello";
nullableString = null;
nullableString = undefined;

Function Types: Describes the type of a function.


let myFunction: (x: number, y: number) => number;
myFunction = (x, y) => x + y;

Type Assertions: Tells the compiler to treat a value as a specific type.


let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;

Interfaces: Describes the shape of an object.


interface Person {
    name: string;
    age: number;
}
let person: Person = { name: "John", age: 25 };

How to install Typescript?

To install TypeScript, you’ll need Node.js and npm (Node Package Manager) installed on your system. If you haven’t already installed them, you can download and install Node.js from the official website. npm comes bundled with Node.js.

Here are the steps to install TypeScript:

Step 1: Install Node.js and npm
  1. Go to Node.js download page and download the installer for your operating system.
  2. Once installed, open a terminal or command prompt and verify the installation by running

node -v

npm -v

These commands should output the version numbers of Node.js and npm respectively.

Step 2: Install TypeScript
  1. Open a terminal or command prompt.
  2. Run the following command to install TypeScript globally:

npm install -g typescript

The -g flag installs TypeScript globally, making it available from any location on your system.

Step 3: Verify the Installation

After the installation is complete, verify it by checking the TypeScript version.


tsc -v

This command should output the version number of the TypeScript compiler (tsc).

Run the code into the typescript file

create the app.ts file and put the code into it.


console.log("Hello World");

Compile the TypeScript file to JavaScript:


tsc app.ts

This command generates an app.js file from your app.ts file.

Run the compiled JavaScript file:


node app.js

now, it will show Hello World

What is TypeScript

TypeScript is a statically typed superset of JavaScript developed and maintained by Microsoft. It extends JavaScript by adding static types, which allows developers to catch errors early in the development process through type checking. This makes it particularly useful for large-scale applications and projects where code maintainability and error reduction are critical.

Here are some key features and benefits of TypeScript:

Static Typing: TypeScript introduces type annotations and type inference, enabling static type checking at compile time.

Type Inference: Even if types are not explicitly declared, TypeScript can infer them based on the code, providing a balance between explicit type annotations and flexibility.

Advanced Type System: It supports complex types such as unions, intersections, and generics, enhancing code expressiveness and robustness.

Compatibility with JavaScript: TypeScript code compiles down to plain JavaScript, ensuring compatibility with existing JavaScript projects and libraries.

Enhanced IDE Support: Due to its static typing, TypeScript offers better code completion, refactoring, and navigation features in integrated development environments (IDEs) like Visual Studio Code.

ES6+ Features: TypeScript includes features from newer ECMAScript standards (like ES6, ES7, etc.), such as classes, modules, and async/await, often before they are supported by all browsers.

Rich Tooling: The TypeScript ecosystem includes robust tooling for linting, debugging, and testing, which can improve developer productivity and code quality.

Advantages

If there is error in the code then it shows the error before the code execution.​
OR

It shows compiler errors get detected by compiler at the time of code development.​

Note:- Runtime errors are not get detected by compiler and hence identified at the time of code execution.

Disadvantage

Browser can’t execute it as like a JavaScript​

Example

Here’s a simple example to illustrate the differences between JavaScript and TypeScript:

JavaScript:

function greet(name) {
    return "Hello, " + name;
}

console.log(greet("John")); // Works fine
console.log(greet(123)); // Works but may cause runtime errors
TypeScript:

function greet(name: string): string {
    return "Hello, " + name;
}

console.log(greet("John")); // Works fine
console.log(greet(123)); // Compilation error: Argument of type 'number' is not assignable to parameter of type 'string'

In the TypeScript example, the function greet expects a string parameter, and passing a number results in a compile-time error, preventing potential runtime issues.

What is Type Alias

In TypeScript, a type alias is a way to create a new name for a type. This can be useful for making your code more readable and manageable, especially when dealing with complex types. Type aliases are defined using the type keyword followed by the new type name and an assignment to a type expression.

Syntax


type AliasName = Type;

Example of Type Aliases


type StringAlias = string;
type NumberAlias = number;

let myString: StringAlias = "Hello";
let myNumber: NumberAlias = 123;

In this example, StringAlias is an alias for the string type, and NumberAlias is an alias for the number type.

Union Types


type ID = string | number;
let userId: ID;
userId = "abc"; // OK
userId = 1234567;   // OK

Here, ID is a type alias for the union of string and number, making it easier to reuse this type definition.

Object Types

You can also use type aliases to define object types.


type User = {
  id: number;
  name: string;
  email: string;
};

let user: User = {
  id: 1,
  name: "John Doe",
  email: "john.doe@example.com"
};

In this example, User is a type alias for an object with id, name, and email properties. This makes the code more readable and easier to maintain.

Function Types

Type aliases can be used to define function types as well.


type Operation = (a: number, b: number) => number;

const add: Operation = (x, y) => x + y;
const subtract: Operation = (x, y) => x - y;

console.log(add(10, 4));       // Output: 14
console.log(subtract(10, 4));  // Output: 6

Here, Operation is a type alias for a function that takes two numbers and returns a number.

Nested Types

You can create type aliases for more complex, nested types.


type Address = {
  street: string;
  city: string;
  country: string;
};

type UserProfile = {
  id: number;
  name: string;
  address: Address;
};

let profile: UserProfile = {
  id: 1,
  name: "Jane Doe",
  address: {
    street: "123 Main St",
    city: "Anytown",
    country: "USA"
  }
};

In this example, UserProfile includes another type alias, Address, making it easier to manage nested structures.

What is Union Type

In TypeScript, a union type is a type that allows a value to be one of several specified types. It’s useful when you want to allow a variable to have multiple potential types. You define a union type using the vertical bar (|) to separate each type.

Example of Union Type


let data: string | number;
data = "Hello World"; // OK
data = 100;      // OK
data = true;    // Error: Type 'boolean' is not assignable to type 'string | number'.

In this example, the variable data can be either a string or a number, but not any other type like boolean.

Usage in Functions


function format(input: string | number): string {
  if (typeof input === "string") {
    return input.toUpperCase();
  } else {
    return input.toFixed(2);
  }
}

console.log(format("Hello World")); // Output: "HELLO WORLD"
console.log(format(120));      // Output: "120.00"

Here, the format function accepts either a string or a number as its input and handles each type appropriately.

Union Types with Objects


type Cat = { name: string; purrs: boolean };
type Dog = { name: string; barks: boolean };

type Pet = Cat | Dog;
function makeSound(pet: Pet): string {
  if ("purrs" in pet) {
    return pet.name + " purrs.";
  } else {
    return pet.name + " barks.";
  }
}

const cat: Cat = { name: "Pussy", purrs: true };
const dog: Dog = { name: "Tiger", barks: true };

console.log(makeSound(cat)); // Output: "Pussy purrs."
console.log(makeSound(dog)); // Output: "Tiger barks."

What is class in Typescript

In TypeScript, a class is a blueprint for creating objects with predefined properties and methods. It is an essential feature of object-oriented programming (OOP) and provides a way to model real-world entities and their behaviors in your code.

Here’s a brief overview of classes in TypeScript:

Class Declaration: A class is declared using the class keyword, followed by the class name and a body enclosed in curly braces {}. The class body can contain properties, methods, and constructors.

Properties: These are variables that belong to the class. They define the state of the class instances.

Methods: These are functions that belong to the class. They define the behavior of the class instances.

Constructor: A special method called constructor is used to initialize class properties. It is called automatically when an instance of the class is created.

Access Modifiers: TypeScript supports public, private, and protected access modifiers to control the visibility of class members.

Here’s a simple example to illustrate the concept of classes in TypeScript:


class Person {
// Properties
private name: string;
private age: number;
}

// Method



public greet(): void {
console.log(Hello, my name is ${this.name} and I am ${this.age} years old.);
}
}

// Constructor


constructor(name: string, age: number) 
{
this.name = name;
this.age = age;
}

// Creating an instance of the Person class


const person1 = new Person('John', 38);

// Calling the greet method


person1.greet(); // Output: Hello, my name is John and I am 38 years old.

Key Points

Class Declaration: class Person { ... }

Properties: name and age with private access modifier.

Method: greet() { ... }

Instance Creation: const person1 = new Person('John', 38);

Method Call: person1.greet();