typescript


Type Guards in TypeScript

Type guards are used to check the type of a value at runtime. This can be useful for ensuring that your code behaves as expected, and for handling different types of data in a specific way.

How to Use Type Guards

There are two main ways to use type guards:

  1. Using the typeof operator: The typeof operator returns the type of a value as a string. You can use this to compare the type of a value to a specific string. For example:

if (typeof value === "string") {
  // Do something with the value as a string
}
  1. Using the instanceof operator: The instanceof operator checks whether a value is an instance of a specific class. You can use this to determine whether a value is of a specific type. For example:

if (value instanceof String) {
  // Do something with the value as a string
}

Types of Type Guards

There are two main types of type guards:

  1. Structural type guards: Structural type guards check the structure of a value. This means that they check the properties and methods of a value, rather than its type. For example, the following type guard checks whether a value has a name property of type string:

function isPerson(value: any): value is { name: string } {
  return typeof value.name === "string";
}
  1. Nominal type guards: Nominal type guards check the type of a value. This means that they check the specific class or interface that a value is an instance of. For example, the following type guard checks whether a value is an instance of the Person class:

function isPerson(value: any): value is Person {
  return value instanceof Person;
}

Real-World Applications of Type Guards

Type guards can be used in a variety of real-world applications, including:

  • Input validation: Type guards can be used to validate user input, ensuring that it is of the correct type. For example, you could use a type guard to check that a user has entered a valid email address.

  • Error handling: Type guards can be used to handle errors in a more specific way. For example, you could use a type guard to check whether an error is a specific type, and then handle it accordingly.

  • Polymorphism: Type guards can be used to implement polymorphic behavior. For example, you could use a type guard to determine the type of a value, and then call a different method based on that type.


What are Intersection Types?

Imagine you have two types, like a "Dog" and a "Cat". They both have different properties like ("name", "age") for Dog and ("name", "color") for Cat.

An intersection type combines these two types into a new type called "DogAndCat", which has all the properties from both Dog and Cat ("name", "age", and "color").

Syntax:

type DogAndCat = Dog & Cat;

Example:

type Dog = {
  name: string;
  age: number;
};

type Cat = {
  name: string;
  color: string;
};

type DogAndCat = Dog & Cat;

const myPet: DogAndCat = {
  name: "Fluffy",
  age: 3,
  color: "white",
};

Real-World Application:

Intersection types can be useful when you want to represent an object that has characteristics from multiple interfaces or types. For example, a "User" type that combines the properties of a "Profile" type and a "Credentials" type.

Using Intersection Types with Generics

You can also use intersection types with generics to create reusable types. For example, you could create a generic "HasName" type that applies to any type that has a "name" property:

type HasName<T> = T & { name: string };

Example:

interface Dog {
  name: string;
  age: number;
}

interface Cat {
  name: string;
  color: string;
}

const hasNameDog: HasName<Dog> = {
  name: "Buddy",
  age: 5,
};

const hasNameCat: HasName<Cat> = {
  name: "Whiskers",
  color: "black",
};

Potential Applications:

  • Modeling complex objects with multiple traits

  • Enforcing type compatibility between different interfaces

  • Creating reusable types with generics


Union Types

Imagine you have a variable that can hold different types of values. For example, a variable called thing could hold either a number or a string. In TypeScript, we can represent this using a "union type":

let thing: number | string; // the type of thing can be either a number or a string

Operations with Union Types

When working with union types, we can perform operations that apply to all the possible types within the union. For example, we can assign a value to thing using:

thing = 10; // okay, thing is a number
thing = "Hello"; // also okay, thing is now a string

We can use ternary operators to check the type of thing:

if (typeof thing === "string") {
  // thing is a string
} else {
  // thing is a number
}

Type Guard Functions

Sometimes, we need to check the type of a union type more explicitly. We can use "type guard functions" to do this:

function isString(thing: number | string): thing is string {
  return typeof thing === "string";
}

if (isString(thing)) {
  // thing is definitely a string here
}

Real-World Applications

Union types are useful in many real-world scenarios:

  • Form Inputs: A form input can accept different types of values (e.g., text, numbers, emails).

  • Data Validation: We can check if a value is valid by defining a union type of valid values.

  • Dynamic Data Structures: Union types allow us to create data structures that can hold different types of elements.

Example Implementation

Consider a form with a text input and a checkbox:

interface FormValues {
  text: string;
  checkbox: boolean;
}

let formValues: FormValues = {
  text: "Username", // a string
  checkbox: true, // a boolean
};

// Check which type of value is stored in text
if (typeof formValues.text === "string") {
  // the text field contains a string
}

Unit Testing in TypeScript

What is Unit Testing? Unit testing is a technique used to test individual pieces of code (called units) in isolation. This helps ensure the unit behaves as expected without affecting other parts of the code.

Why Unit Testing?

  • Catch bugs early: Identify issues in code before they become big problems.

  • Improved code quality: Ensure the code meets design specifications and follows best practices.

  • Faster debugging: Narrow down issues to specific units, making it easier to find and fix bugs.

  • Improved confidence: Increase trust in the codebase, making it easier to refactor or extend it in the future.

How to Unit Test in TypeScript

1. Setup

  • Install a testing framework like Jest or Mocha.

  • Create a separate directory for tests.

2. Writing Tests

  • Use the describe() and it() methods to create test cases for specific units of code.

  • Use assertions like expect() to compare the actual output of the unit to the expected output.

Example:

describe("Calculator", () => {
  it("should add two numbers", () => {
    const calculator = new Calculator();
    const result = calculator.add(2, 3);
    expect(result).toBe(5);
  });
});

3. Running Tests

  • Run npm test in the command line to execute all tests in the project.

  • Jest will provide a report showing the test results.

4. Assertions

  • toBe(): Checks if two values are exactly equal.

  • toEqual(): Checks if two objects or arrays are equal in content.

  • not.toBe(): Checks if two values are not equal.

  • greaterThan(): Checks if one value is greater than another.

  • throws(): Checks if a function throws an error.

Real-World Applications

  • Front-end Development: Test individual components and event handlers in a web application.

  • Mobile Development: Ensure UI elements and app logic function correctly.

  • API Testing: Verify the behavior of RESTful APIs for different scenarios.

  • Backend Development: Test individual database queries, business logic, and server-side code.


1. Types

  • What is a type? A type describes the kind of value a variable can hold.

  • Example:

let age: number = 25; // number type
let name: string = "John"; // string type

2. Interfaces

  • What is an interface? An interface defines a blueprint for objects. It specifies the properties and methods that an object must have.

  • Example:

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

const person: Person = {
  name: "John",
  age: 25
};

3. Classes

  • What is a class? A class is a blueprint for creating objects. It defines the properties and methods that objects of that class will have.

  • Example:

class Person {
  name: string;
  age: number;

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

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

const person = new Person("John", 25);
person.greet(); // Output: "Hello, my name is John and I am 25 years old."

4. Generics

  • What are generics? Generics allow you to write code that can work with different types of data without having to specify the specific types.

  • Example:

function printArray<T>(array: T[]) {
  for (let item of array) {
    console.log(item);
  }
}

printArray([1, 2, 3]); // Print an array of numbers
printArray(["a", "b", "c"]); // Print an array of strings

5. Asynchronous Programming

  • What is asynchronous programming? Asynchronous programming allows you to perform tasks in the background without blocking the main thread. This is useful for tasks that take a long time to complete, such as fetching data from a server.

  • Example:

// Fetch data from a server asynchronously
fetch("https://example.com/data")
  .then(response => response.json())
  .then(data => {
    console.log(data);
  })
  .catch(error => {
    console.error(error);
  });

Real-World Applications:

  • Types: Ensuring data integrity and preventing errors by specifying the expected types of values.

  • Interfaces: Defining contracts for objects to ensure consistency and reduce coupling.

  • Classes: Encapsulating data and behavior into reusable units.

  • Generics: Writing code that can be reused for different types of data.

  • Asynchronous Programming: Improving performance and responsiveness by offloading long-running tasks to the background.


TypeScript: A Superset of JavaScript

What is TypeScript?

Imagine JavaScript with extra superpowers! TypeScript is like JavaScript, but it makes your code more powerful and organized. It's like a special tool that helps you write better code.

Why use TypeScript?

  • Catch errors early: TypeScript checks your code as you type, so you can fix any issues before they become big problems.

  • Improved code organization: TypeScript helps you structure your code in a way that makes it easy to read and understand.

  • Faster development: TypeScript can automate repetitive tasks, saving you time and effort.

Types in TypeScript

What are types?

Types tell TypeScript what kind of data your variables and functions can store. For example, you can specify that a variable can only hold numbers or strings. This makes your code more predictable.

Example:

// This variable can only hold numbers
let age: number;
age = 30;

// This function takes a string and returns a number
function calculateArea(length: string): number {
    // Code that calculates the area goes here
}

Variables in TypeScript

What are variables?

Variables are like boxes that can store data. In TypeScript, you can specify the type of data a variable can store.

Example:

// Declare a variable to store a name
let name: string;

// Assign a value to the variable
name = "John Doe";

// Access the value of the variable
console.log(`Hello, ${name}!`);

Functions in TypeScript

What are functions?

Functions are like instruction manuals. They contain a set of steps to be executed when you call them. In TypeScript, you can specify the type of data your functions will take as input and return as output.

Example:

// Define a function to calculate the area of a circle
function calculateArea(radius: number): number {
    return Math.PI * radius ** 2;
}

// Call the function and store the result in a variable
const area = calculateArea(5);

// Access the value of the variable
console.log(`The area is ${area}`);

Classes in TypeScript

What are classes?

Classes are like blueprints for creating objects. They define the properties and methods that objects of that class should have.

Example:

// Define a class representing a person
class Person {
    // Properties: represent the person's characteristics
    name: string;
    age: number;

    // Constructor: initializes the object's properties
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }

    // Methods: represent the person's actions
    greet() {
        console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
    }
}

// Create an object (instance) of the Person class
const john = new Person("John Doe", 30);

// Call the greet method on the object
john.greet();

Interfaces in TypeScript

What are interfaces?

Interfaces are like contracts that define the structure of objects. They specify the properties and methods that an object must have, but they don't provide any implementation.

Example:

// Define an interface representing a shape
interface Shape {
    area(): number;
    perimeter(): number;
}

// Define a class that implements the Shape interface
class Circle implements Shape {
    radius: number;

    constructor(radius: number) {
        this.radius = radius;
    }

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

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

Real-World Applications

TypeScript is used in a wide range of applications, including:

  • Web Development: Creating interactive websites and applications that run in the browser.

  • Mobile Development: Building native and cross-platform mobile apps.

  • Data Analysis: Writing code to process and analyze large datasets.

  • Cloud Computing: Developing applications that run on cloud platforms like AWS and Azure.

  • Enterprise Software: Creating complex and scalable software systems for businesses.


Variables in TypeScript

Imagine you have a box to store something. In TypeScript, we call this a variable. We give the box a name, like "myVariable", and we can put anything we want inside it, like a number, a string, or even another box.

Declaring Variables

To create a variable, we use the let keyword, like this:

let myVariable = 42;

This creates a variable named myVariable and stores the number 42 inside it.

Types

Variables can hold different types of data. The most common types are:

  • Number: A number, like 42.

  • String: A text value, like "Hello world".

  • Boolean: A true/false value, like true.

We can specify the type of a variable by adding a colon and the type after the name, like this:

let myNumber: number = 42;
let myString: string = "Hello world";
let myBoolean: boolean = true;

Real-World Applications

Variables are used everywhere in programming. Here are some examples:

  • Game development: Stores the player's position, score, and health.

  • Web applications: Stores user input, page data, and session information.

  • Databases: Stores data in tables and rows.

  • Artificial intelligence: Stores training data, model parameters, and predictions.

Variable Scope

The scope of a variable determines where it can be accessed in your code. There are two types of scope:

  • Local scope: A variable declared inside a block of code (e.g., a function) can only be accessed within that block.

  • Global scope: A variable declared outside of any block can be accessed from anywhere in your code.

For example:

function myFunction() {
  // Local variable
  let localVariable = 10;
}

// Global variable
let globalVariable = 20;

In the above code, localVariable can only be accessed within the myFunction function, while globalVariable can be accessed from anywhere in the code.

Variable Hoisting

In JavaScript, variables declared with var are hoisted to the top of their scope. This means that they can be accessed even before they are declared. In TypeScript, this behavior does not exist for variables declared with let or const.

// JavaScript
var myVar = 10;
console.log(myVar); // 10 (accessible before declaration)

// TypeScript
let myVar = 10;
console.log(myVar); // Error: Cannot access 'myVar' before initialization

Constant Variables

Constant variables cannot be reassigned once they are initialized. We use the const keyword to declare a constant variable, like this:

const myConstant = 42;
// myConstant = 10; // Error: Cannot assign to a constant variable

Constants are useful for values that should not change, such as the speed of light or the number of days in a year.

Summary

  • Variables store data in your code.

  • You can declare a variable using the let or const keywords.

  • Variables have a type, which specifies the kind of data they can hold.

  • Variables have a scope, which determines where they can be accessed.

  • Constants are variables that cannot be reassigned.


TypeScript Project Configuration

What is TypeScript Project Configuration?

Imagine you're working on a big construction project, like building a house. You need a plan that describes the materials, tools, and steps involved. TypeScript Project Configuration is like that plan for your TypeScript code. It tells TypeScript how to compile your code and what settings to use.

Key Concepts

tsconfig.json: This is the main configuration file. It contains all the settings for your project.

compilerOptions: These are the settings that control how TypeScript compiles your code. Common options include:

  • target: The version of JavaScript that the code should be compiled to (e.g., "es5", "es6").

  • module: The module system to use (e.g., "commonjs", "amd").

  • outDir: The output directory where the compiled JavaScript files should be saved.

include: An array of glob patterns that specify the files and directories to include in the compilation.

exclude: An array of glob patterns that specify the files and directories to exclude from the compilation.

Code Examples

Basic tsconfig.json:

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs"
  }
}

Exclude node_modules from Compilation:

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs"
  },
  "exclude": ["node_modules"]
}

Real-World Applications

Building a React Application:

{
  "compilerOptions": {
    "target": "es5",
    "module": "esnext",
    "moduleResolution": "node",
    "jsx": "react"
  }
}

This configuration is optimized for building a React application with JavaScript modules and JSX support.

Compiling TypeScript to JavaScript in a Node.js Application:

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "outDir": "./dist"
  },
  "include": [
    "./src/**/*.ts"
  ]
}

This configuration compiles all TypeScript files in the "src" directory and saves the compiled JavaScript files in the "dist" directory.


Testing in TypeScript

What is Testing?

Testing is a way of checking if your code works as you expect it to. It involves writing code to simulate how users will interact with your application and checking the results to make sure they are correct.

Why is Testing Important?

Testing is important because it helps you catch bugs early and prevent them from causing problems for users. It also gives you confidence that your code is working correctly and helps you improve the quality of your application.

Types of Tests

There are two main types of tests: unit tests and integration tests.

  • Unit tests test individual functions or classes in isolation. They are the simplest type of test to write and are typically used to check the basic functionality of your code.

  • Integration tests test how different parts of your application work together. They are more complex to write than unit tests, but they can help you catch bugs that might not be caught by unit tests.

Writing Tests

To write tests in TypeScript, you can use a testing framework such as Jasmine, Mocha, or Jest. These frameworks provide a set of tools and helpers that make it easy to write and run tests.

Here is an example of a simple unit test written with Jasmine:

describe('MyFunction', () => {
  it('should return the sum of two numbers', () => {
    expect(myFunction(1, 2)).toBe(3);
  });
});

This test checks that the myFunction function returns the sum of two numbers. The describe function defines a test suite, and the it function defines a test case within that suite. The expect function checks the result of the test case, and the toBe function checks that the result is equal to the expected value.

Running Tests

To run tests in TypeScript, you can use a command like this:

npm run test

This command will run all the tests in your project and report any failures.

Potential Applications in the Real World

Testing is essential for any serious software development project. It can help you catch bugs early, prevent problems for users, and improve the quality of your application.

Here are some potential applications of testing in the real world:

  • Testing can help you verify that your application meets its requirements.

  • Testing can help you identify and fix bugs before they cause problems for users.

  • Testing can help you improve the performance of your application.

  • Testing can help you document your application's behavior.

  • Testing can help you maintain your application over time.


TypeScript Error Handling

Introduction

Error handling is a critical aspect of any software application. It allows developers to anticipate and gracefully handle errors that may occur during the execution of a program. TypeScript provides several mechanisms for error handling, making it easier to write robust and reliable code.

Error Types

Errors can be classified into two main types:

  • Syntax Errors: These errors occur during compilation and prevent the program from running. They are usually caused by incorrect syntax or missing code.

  • Runtime Errors: These errors occur while the program is running. They can be caused by various factors, such as invalid input, network issues, or hardware failures.

Error Handling Mechanisms

TypeScript offers several ways to handle errors:

1. try...catch Statements:

  • Allows you to specify a block of code that should be executed (the "try" block) and a separate block of code that handles any errors that occur (the "catch" block).

try {
  // Code that may throw an error
} catch (error) {
  // Code to handle the error
}

2. throw Statement:

  • Used to explicitly throw an error, which interrupts the execution of the current function and passes control to the nearest enclosing catch block.

function divide(a: number, b: number) {
  if (b === 0) {
    throw new Error("Division by zero is not allowed");
  }
  return a / b;
}

3. Custom Error Objects:

  • Allows you to create your own error types and provide additional information about the error.

class MyError extends Error {
  constructor(message: string) {
    super(message);
  }
}

const error = new MyError("Custom error message");

Real-World Applications

Error handling is essential in real-world applications for various reasons:

  • Improved Stability: Handling errors gracefully prevents the application from crashing or freezing.

  • User-Friendly Feedback: Custom error messages provide informative and actionable feedback to users.

  • Debugging and Troubleshooting: Error handling helps identify and fix issues in the code.

  • Communication with External Systems: Errors can be seamlessly communicated to other systems, facilitating debugging and issue resolution.

Code Examples

Handling Syntax Errors:

try {
  // Code with potential syntax errors
} catch (error) {
  console.error("Syntax error:", error.message);
}

Handling Runtime Errors:

try {
  divide(10, 0); // Division by zero error
} catch (error) {
  console.error("Runtime error:", error.message);
}

Using Custom Error Objects:

try {
  throw new MyError("Custom error occurred");
} catch (error) {
  console.error("Custom error:", error.message);
}

Type System

  • What is it?

    • A system for specifying the types of data in your program.

    • Ensures that variables and functions are used correctly.

  • Benefits:

    • Improves code quality and reliability.

    • Makes it easier to develop and maintain large programs.

  • Example:

let name: string = "Alice"; // Variable declaration with type annotation
function greet(name: string): void { // Function declaration with type annotation
    console.log(`Hello, ${name}!`);
}

Modules

  • What is it?

    • A way to organize code into reusable chunks.

    • Allows you to import and export code from different modules.

  • Benefits:

    • Makes it easier to manage and reuse code.

    • Improves code readability and maintainability.

  • Example:

// module.ts
export const PI = 3.14; // Export a constant

// main.ts
import { PI } from "./module"; // Import the exported constant
console.log(PI); // Output: 3.14

Classes

  • What is it?

    • A blueprint for creating objects.

    • Defines properties (variables) and methods (functions).

  • Benefits:

    • Encapsulation: Bundles related data and behavior together.

    • Polymorphism: Allows different classes to respond to the same message in different ways.

    • Inheritance: Enables classes to inherit properties and methods from other classes.

  • Example:

class Person {
    name: string; // Property
    age: number; // Property

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

    greet() { // Method
        console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
    }
}

const alice = new Person("Alice", 25); // Create an object
alice.greet(); // Output: Hello, my name is Alice and I am 25 years old.

Generics

  • What is it?

    • A way to define types that can work with multiple data types.

    • Allows you to create reusable code that can be used with different types.

  • Benefits:

    • Code reusability and flexibility.

    • Improved type safety.

  • Example:

// Generic function
function max<T>(a: T, b: T): T {
    if (a > b) {
        return a;
    }
    return b;
}

// Usage
const maxStr = max<string>("Alice", "Bob"); // Result: "Bob"
const maxNum = max<number>(10, 20); // Result: 20

Applications in Real World

  • Type System: Used extensively in libraries, frameworks, and large-scale software development to improve code quality and maintainability.

  • Modules: Used in modular development to organize code, improve reusability, and reduce coupling.

  • Classes: Used to model real-world entities and create complex and extensible applications.

  • Generics: Used in various applications, such as data structures, algorithms, and reusable components, to handle multiple data types and improve code flexibility.


Type Checking and Inference

  • What it is: TypeScript checks the types of variables, functions, and expressions to make sure they match. It can also infer types based on usage.

  • Simplified example:

let age: number = 10;
  • Real-world application: Ensures that functions and variables are used correctly, reducing errors and improving code quality.

Static Type Checking

  • What it is: TypeScript performs type checking before the code is run, catching errors early.

  • Simplified example:

function add(a: number, b: number): number {
  return a + b;
}

Any attempt to call the function with non-number arguments will be caught during type checking.

  • Real-world application: Prevents runtime errors caused by mismatched types, improving stability.

Type Inference

  • What it is: TypeScript can infer the type of a variable based on its value or usage.

  • Simplified example:

const myArray = [1, 2, 3]; // TypeScript infers that myArray is an array of numbers
  • Real-world application: Simplifies code by removing the need to explicitly specify types, reducing development time.

Interface and Type Aliases

  • Interface: Defines the shape of an object, specifying the properties it must have and their types.

  • Type alias: An alternative way to define types, similar to interfaces, but can be used for any type, including primitive types.

  • Simplified examples:

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

type Age = number; // Type alias for number
  • Real-world application: Enforces consistent object structures and improves code readability and maintainability.

Generics

  • What it is: Generics allow functions and types to work with different types of data without needing to be rewritten.

  • Simplified example:

function map<T>(arr: T[], callback: (item: T) => T): T[] {
  // ...
}

The map function can be used to transform an array of any type, such as numbers, strings, or objects.

  • Real-world application: Allows for flexible and reusable code, reducing duplication and complexity.

Encapsulation and Access Modifiers

  • Encapsulation: Hiding the implementation details of a class or object from external access.

  • Access modifiers: Keywords (public, protected, private) that control the visibility of members within a class.

  • Simplified example:

class Person {
  private age: number; // Only accessible within the class

  public getName() {
    // Accessible from anywhere
  }
}
  • Real-world application: Improves code security and maintainability by preventing unintended access to internal data or methods.


Understanding TypeScript

TypeScript is like a superpower for JavaScript, making it easy to write complex code that's less likely to break. It adds special features like data types and error checking to JavaScript.

Key Features:

  • Data Types: TypeScript lets you define specific data types for variables, ensuring they hold the right kind of information. For example, you can have a variable called "name" that can only store strings (like "John Doe").

    let name: string = "John Doe";
  • Error Checking: TypeScript checks your code for potential errors as you write it, highlighting issues like incorrect data types or missing semicolons.

    // Error: "name" is not defined
    console.log(name);
  • Enhanced Code Structure: TypeScript supports object-oriented programming and modular code organization, making it easier to manage large codebases.

    class Person {
      name: string;
      age: number;
    }

Applications in the Real World:

  • Building Large-Scale Web Applications: TypeScript's powerful features and error checking make it ideal for developing complex and reliable web applications.

  • Developing Mobile Apps: TypeScript can be used with frameworks like React Native to create cross-platform mobile apps.

  • Automating Cloud Functions: TypeScript can enhance the development of serverless functions that run in cloud environments.

Additional Examples:

  • Conditional Types: TypeScript allows you to create types that depend on conditions.

    type UserType = string | number;
  • Generics: TypeScript lets you create functions and types that work with any type of data.

    function logSomething<T>(value: T): void {
      console.log(value);
    }
  • Asynchronous Programming: TypeScript supports modern asynchronous programming techniques, making it easier to handle asynchronous operations.

    async function getData() {
      const response = await fetch("https://example.com/api");
      const data = await response.json();
      return data;
    }

Conclusion:

TypeScript takes JavaScript to the next level, providing enhanced data types, error checking, object-oriented programming capabilities, and support for modern programming techniques. It's widely used in real-world applications like web development, mobile app development, and cloud automation.


TypeScript HTTP Client

Understanding the Basics

Imagine TypeScript as a special language that helps you build programs and apps. It's like a superpower you can use to create cool stuff on the web. And one of its superpowers is the HTTP Client, which lets you communicate with other websites and apps.

HTTP Requests and Responses

When you use the HTTP Client, you can send messages called HTTP requests to other websites or apps. These requests can contain data like your name or email address. The receiving website or app then sends back a response, which can contain information like the weather forecast or the latest news.

Creating an HTTP Request

Here's how to create an HTTP request:

// Create a new request object
const request = new Request('https://example.com');

// Send the request
fetch(request)
  .then(response => {
    // Process the response
  })
  .catch(error => {
    // Handle the error
  });

Customizing Requests with Options

You can customize your requests using options:

// Create a new request object with options
const options = {
  method: 'POST', // HTTP verb
  headers: { 'Content-Type': 'application/json' }, // Request headers
  body: JSON.stringify({ name: 'John', email: 'john@example.com' }) // Request body
};

const request = new Request('https://example.com', options);

Handling Responses

Once you send a request, you'll get a response. Here's how to handle it:

// Handle the response
fetch(request)
  .then(response => {
    // Check the response status
    if (response.ok) {
      // Process the response body
      response.json().then(data => {
        // Use the data from the response
      });
    } else {
      // Handle the error
      console.log('Error:', response.status);
    }
  })
  .catch(error => {
    // Handle the error
  });

Real-World Code Implementation

Let's build a simple app that fetches weather data from a website:

// Fetch weather data
const weatherRequest = new Request('https://api.openweathermap.org/data/2.5/weather?q=London');

fetch(weatherRequest)
  .then(response => {
    if (response.ok) {
      response.json().then(data => {
        // Display the weather data
        console.log(`Current temperature in London: ${data.main.temp}°C`);
      });
    } else {
      console.log('Error getting weather: ', response.status);
    }
  })
  .catch(error => {
    console.log('Error getting weather: ', error);
  });

Potential Applications

The TypeScript HTTP Client can be used in various applications, such as:

  • Fetching data from websites and apps

  • Sending data to databases

  • Authenticating users

  • Generating reports


TypeScript/Code Linting

What is Linting?

Imagine you have a set of rules that make sure your code follows certain guidelines, like having consistent spacing, using proper naming conventions, and avoiding certain errors. Linting is like a code inspector that checks your code against these rules to identify any issues.

Types of Linting

  • Static Analysis: Looks for potential errors without actually running the code.

  • Format Checking: Ensures your code conforms to a specific style guide.

Benefits of Linting

  • Improved Code Quality: Linting helps you write clean, consistent, and error-free code.

  • Reduced Debugging Time: By catching errors early, linting saves you time troubleshooting later.

  • Enforced Coding Standards: Linting enforces team-wide coding standards, promoting code readability and collaboration.

How to Use Linting

1. Install a Linter:

  • For TypeScript: Install "eslint" or "prettier" using npm/yarn.

  • For JavaScript: Install "eslint" or "jshint" using npm/yarn.

2. Configure the Linter:

  • Create a configuration file (.eslintrc.js, .prettierrc.js) that defines the linting rules.

  • Example:

{
  "rules": {
    "indent": [2, "space"],
    "semi": [2, "always"],
    "quotes": [2, "single"]
  }
}

3. Run the Linter:

  • Run the linter using a command like "npx eslint ." or "npx prettier --write ."

  • The linter will report any errors or warnings.

4. Fix the Issues:

  • Address any errors or warnings reported by the linter.

  • Use automated tools like "eslint --fix" or "prettier --write" to automatically fix some issues.

Real-World Applications

  • Team Collaboration: Ensure consistency and readability across large codebases maintained by multiple developers.

  • Code Review Automation: Integrate linting into code review processes to save time and improve code quality.

  • Code Refactoring: Linting can highlight opportunities for improving code structure and readability.

Example Code Implementations

JavaScript Linting with ESLint:

// .eslintrc.js
{
  "rules": {
    "no-console": "warn",
    "no-var": "error",
    "prefer-const": "error"
  }
}

// code.js
console.log("Hello World!"); // eslint warning: no-console
var name = "John"; // eslint error: no-var
const greeting = "Welcome"; // eslint no issues

TypeScript Linting with Prettier:

// .prettierrc.js
{
  "semi": true,
  "trailingComma": "all",
  "arrowParens": "avoid"
}

// code.ts
const addNumbers = (x: number, y: number) => x + y; // Prettier no issues

TypeScript Release Notes

Summary

TypeScript is a popular language that combines the power of JavaScript with the type safety of statically-typed languages like C++ and Java. This allows developers to write more robust and maintainable code.

Detailed Explanation

What's New in TypeScript 4.5

  • Automatic type inference for array destructuring: TypeScript can now infer the types of elements in an array when destructuring.

const [x, y] = [1, 2]; // x is now inferred as number
  • Improved developer error messages: TypeScript now provides more helpful error messages when developers make mistakes.

let x: string = 123; // Error: Type 'number' is not assignable to type 'string'
  • Error recovery for missing import statements: TypeScript can now automatically add missing import statements when it detects errors caused by missing imports.

import { Button } from "./Button"; // Error: Cannot find module './Button'
// TypeScript will automatically add the import statement

Real-World Applications

  • Web Development: TypeScript is a powerful tool for building scalable and maintainable web applications. It allows developers to create type-safe code that is less prone to errors.

  • Mobile Development: TypeScript can be used to develop mobile applications for both iOS and Android using frameworks like Ionic and React Native.

  • Desktop Development: TypeScript can be used to build desktop applications using frameworks like Electron and NW.js.

Extensive Code Examples

Automatic Type Inference for Array Destructuring

const [x, y] = [1, 2];
console.log(typeof x); // Outputs: "number"

Improved Developer Error Messages

let x: string = 123;
console.log(x); // Error: Type 'number' is not assignable to type 'string'

Error Recovery for Missing Import Statements

import { Button } from "./Button";
// Error: Cannot find module './Button'
// TypeScript will automatically add the import statement
console.log(Button); // This will now work

Potential Applications in Real World

Web Development

// TypeScript code for a simple counter
const counter = 0;
const button = document.querySelector("button");
button.addEventListener("click", () => {
  counter += 1;
  console.log(counter);
});

Mobile Development

// TypeScript code for a simple mobile app
import { IonicApp, IonicPage, NavController } from "ionic-angular";
@IonicPage()
@IonicApp({
  templateUrl: "main.html",
})
class MyApp {
  constructor(public navCtrl: NavController) {}
}

Desktop Development

// TypeScript code for a simple desktop app
import { app, BrowserWindow, Menu } from "electron";
const mainWindow = new BrowserWindow();
app.on("ready", () => {
  mainWindow.loadFile("index.html");
  const menu = Menu.buildFromTemplate([
    {
      label: "File",
      submenu: [{ label: "Open" }, { label: "Save" }, { label: "Exit" }],
    },
  ]);
  Menu.setApplicationMenu(menu);
});
app.on("activate", () => {
  if (mainWindow.isVisible()) mainWindow.focus();
  else mainWindow.show();
});

Generic Types

Imagine you have a function that takes an array and returns the first element. You could write the function like this:

function first<T>(arr: T[]): T {
  return arr[0];
}

This function works great for any type of array, such as an array of strings, numbers, or even objects. However, it's not very specific, so it doesn't provide any type safety.

To make the function more specific, you can use a generic type parameter. This allows you to specify the type of elements in the array:

function first<T>(arr: T[]): T {
  return arr[0];
}

Now, the function will only work for arrays of the specified type. For example, if you call the function with an array of numbers, the function will return a number. If you call the function with an array of strings, the function will return a string.

Conditional Types

A conditional type is a type that is determined based on a condition. For example, you could create a conditional type that checks whether a type is a string:

type IsString<T> = T extends string ? true : false;

This type will be true if the type is a string, and false otherwise.

Conditional types can be used to create complex types that are difficult to express with regular types. For example, you could create a type that checks whether a type is a string or a number:

type IsStringOrNumber<T> = T extends string | number ? true : false;

This type will be true if the type is a string or a number, and false otherwise.

Mapped Types

A mapped type is a type that is created by applying a mapping function to another type. For example, you could create a mapped type that converts all the properties of an object to optional:

type MakeOptional<T> = {
  [P in keyof T]?: T[P];
};

This type will take an object type and create a new object type where all the properties are optional.

Mapped types can be used to create complex types that are difficult to express with regular types. For example, you could create a type that converts all the properties of an object to readonly:

type MakeReadonly<T> = {
  readonly [P in keyof T]: T[P];
};

This type will take an object type and create a new object type where all the properties are readonly.

Real-World Applications

Generic types, conditional types, and mapped types are powerful tools that can be used to create complex and flexible types. They can be used in a variety of real-world applications, such as:

  • Creating data structures that can store different types of data

  • Validating user input

  • Creating reusable components that can be used with different types of data

  • Improving the performance of your code by avoiding unnecessary type conversions


Type Compatibility

Imagine types like building blocks. Each block has a certain shape and size, and they can only fit together in certain ways. Type compatibility is like checking if two blocks fit together.

Strict Null Checks

By default, TypeScript assumes that variables can be null, even if you don't explicitly assign them to null. With strict null checks, you can tell TypeScript to be more strict and enforce that variables are never null unless explicitly assigned to null.

Example:

let myString: string | null; // Allow null
myString = null;

// With strict null checks:
let myString2: string; // Error: Cannot be null
myString2 = null;

Union Types

Union types let you combine multiple types into a single type. For example, a variable with a union type of string | number can hold either a string or a number.

Example:

let myValue: string | number;
myValue = "Hello";
myValue = 10;

Intersection Types

Intersection types let you combine multiple types into a single type that has the properties of both types. For example, a variable with an intersection type of { name: string } & { age: number } must have both a name property of type string and an age property of type number.

Example:

interface Person {
  name: string;
}

interface Employee extends Person {
  age: number;
}

let employee: Employee = {
  name: "John",
  age: 30,
};

Type Aliases

Type aliases let you create new names for existing types. For example, you could create a type alias called MyString for the type string.

Example:

type MyString = string;

let myString: MyString;
myString = "Hello";

Real-World Applications:

  • Strict Null Checks: Ensure that variables are never null, avoiding potential errors and confusion.

  • Union Types: Represent values that can take multiple forms, such as an input that can be either text or a number.

  • Intersection Types: Create complex types that combine properties from different types, such as a customer that has both an address and a phone number.

  • Type Aliases: Simplify code by using shorter, more descriptive names for complex types, improving readability and maintainability.


Type Assertions in TypeScript

What are Type Assertions?

Imagine you have a variable assigned to a value that you know has a specific type. But TypeScript doesn't know that type due to its dynamic nature. That's where type assertions come in. They allow you to tell TypeScript, "Hey, trust me, this variable is actually of this type."

Syntax

variable as <type>;

Example:

// We have this variable assigned to a number
const num = 10;

// But TypeScript thinks it's just `any`
console.log(typeof num); // logs 'number'

// We know it's a number, so we can assert it
const numAsNumber = num as number;

// Now TypeScript knows its type
console.log(typeof numAsNumber); // logs 'number'

Non-Null Assertions

There's also a special type assertion operator !, called the non-null assertion operator. It's used when you have a variable that's defined later or you're sure it won't be null or undefined.

Syntax:

variable!;

Real-World Example:

const inputElement = document.getElementById('input');

// We know it will always be a HTMLInputElement
const inputAsElement = inputElement as HTMLInputElement;

// Now we can safely access its methods
inputAsElement.value = 'Hello, world!';

Potential Applications:

  • Type-Checking: Ensures that variables are being used as expected.

  • Code Reusability: Allows you to create generic functions that can work with different types.

  • Debugging: Can help identify type errors and narrow down potential issues.

Complete Code Implementation:

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

function greetPerson(person: Person) {
    console.log(`Hello, ${person.name}!`);
}

const person = {
    name: 'John',
    age: 30,
};

// Type assertion to pass an object into a function expecting a Person
greetPerson(person as Person); // logs 'Hello, John!'

TypeScript Integration Testing

Integration testing involves testing how different parts of your application work together, like how the frontend interacts with the backend or how a service interacts with a database. Here's a simplified explanation and some code examples:

Mocks and Stubs

Mocks: Instead of using the actual implementation of a class or function, you can create a "mock" version that simulates its behavior. Mocks allow you to control the output of functions and verify if they were called with the correct arguments.

Example:

// Real function under test
const add = (a: number, b: number): number => a + b;

// Mock function
jest.mock('path/to/add.js');
const mockedAdd = jest.fn().mockReturnValue(10);

// Replace the real function with the mock
jest.mock('path/to/add.js', () => mockedAdd);

// Test the real function using the mock
expect(add(5, 5)).toBe(10);
expect(mockedAdd).toHaveBeenCalledWith(5, 5);

Stubs: Similar to mocks, stubs also simulate the behavior of a class or function. However, stubs are typically used when you want to control the input and output of a function without having to verify specific calls.

API Testing

Example:

// API testing framework (e.g., Supertest)
import supertest from 'supertest';

// Create an instance of the API to be tested
const request = supertest('http://localhost:3000');

// Perform a GET request
const response = await request.get('/api/users');

// Assert the HTTP status code
expect(response.status).toBe(200);

// Assert the response body
expect(response.body).toEqual({ users: [{ id: 1, name: 'John' }] });

Database Testing

Example:

// Database testing framework (e.g., Jest Postgres)
import { Pool } from 'pg';

// Create a database connection pool
const pool = new Pool({
  connectionString: 'postgresql://user:password@host:port/database',
});

// Query the database
const results = await pool.query('SELECT * FROM users');

// Assert the query results
expect(results.rows).toEqual([{ id: 1, name: 'John' }] );

Real-World Applications

Integration testing helps ensure that:

  • Different components of your application work together seamlessly.

  • The frontend and backend communicate correctly.

  • Services and databases are accessed and updated as expected.


Type Inference

Type inference is a feature of TypeScript that allows the compiler to automatically determine the type of a variable based on its value. This can be a very useful feature, as it can help to reduce the amount of code that you need to write.

How Type Inference Works

The TypeScript compiler uses a number of heuristics to determine the type of a variable. These heuristics include:

  • The type of the variable's initializer

  • The context in which the variable is used

  • The types of other variables that are related to the variable

For example, if you declare a variable and initialize it to a string, the compiler will infer that the variable is of type string. Similarly, if you declare a variable and use it as a parameter to a function that expects a number, the compiler will infer that the variable is of type number.

Benefits of Type Inference

Type inference can provide a number of benefits, including:

  • Reduced code complexity: Type inference can help to reduce the amount of code that you need to write. This is because you don't need to explicitly specify the type of every variable.

  • Improved code readability: Type inference can help to make your code more readable. This is because the compiler will automatically insert type annotations into your code, which can make it easier to understand the types of the variables in your code.

  • Improved code safety: Type inference can help to make your code more safe. This is because the compiler will check the types of your variables and will raise an error if it finds any type errors.

Real-World Applications of Type Inference

Type inference is used in a wide variety of real-world applications. Some examples include:

  • Web development: Type inference is used in many web development frameworks, such as React and Angular. This can help to reduce the amount of code that you need to write and can make your code more readable and maintainable.

  • Mobile development: Type inference is also used in many mobile development frameworks, such as React Native and Xamarin. This can help to improve the performance of your mobile apps and can make them more stable.

  • Game development: Type inference is used in many game development engines, such as Unity and Unreal Engine. This can help to improve the performance of your games and can make them more stable.

Conclusion

Type inference is a powerful feature of TypeScript that can help to reduce the amount of code that you need to write, improve the readability of your code, and improve the safety of your code. It is used in a wide variety of real-world applications, including web development, mobile development, and game development.


TypeScript Security Features

TypeScript adds type checking to JavaScript, which can help you catch certain security issues. For example:

Type Checking

TypeScript can help you catch errors where you try to access a property or method on an object that doesn't exist. This can prevent you from accidentally leaking sensitive information.

Code Example:

const user = {
  name: "John",
};

// TypeScript will throw an error here because "age" is not a property of the "user" object.
console.log(user.age);

Strict Mode

TypeScript's strict mode turns on additional type checking rules that can help you catch more potential security issues.

Code Example:

// Enable strict mode
"use strict";

const user = {
  name: "John",
};

// TypeScript will throw an error here because "age" is not a property of the "user" object.
console.log(user.age);

Null and Undefined Checking

TypeScript can help you catch errors where you try to access a property or method on a null or undefined object. This can prevent you from accidentally leaking sensitive information or causing your application to crash.

Code Example:

let user; // user is initially undefined

// TypeScript will throw an error here because "name" is a property of the "user" object, which is undefined.
console.log(user.name);

Object Property Access Control

TypeScript allows you to control access to properties on objects. This can help you prevent other code from modifying sensitive properties.

Code Example:

class User {
  private name: string;

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

  // Only the User class can access the "name" property.
  getName() {
    return this.name;
  }
}

TypeScript provides several security-related type aliases that can help you define and enforce security policies.

Code Example:

// Define a type alias for a secure string.
type SecureString = string;

// Create a variable of type "SecureString".
const password: SecureString = "mypassword";

// TypeScript will throw an error if you try to assign a non-string value to "password".
password = 1234;

Real-World Applications

TypeScript's security features can be used in a variety of real-world applications, such as:

  • Preventing SQL injection attacks

  • Preventing cross-site scripting (XSS) attacks

  • Securing API endpoints

  • Hardening web applications against common security vulnerabilities


Content Security Policy (CSP)

CSP is a security feature that helps protect your website from vulnerabilities like cross-site scripting (XSS) attacks. It works by restricting the sources from which the browser can load content, such as scripts, styles, and images.

How CSP Works

CSP operates by defining a set of rules that specify where the browser is allowed to load content from. These rules are defined in a "CSP header" that is sent along with the HTTP response.

The CSP header looks like this:

Content-Security-Policy: [rules]

The rules are a list of directives, each of which specifies a type of content and the allowed sources for that content. For example:

Content-Security-Policy: script-src 'self' https://example.com; style-src 'self'; img-src *

This CSP header allows scripts to be loaded from the same origin (i.e., "self") and from the domain example.com. It allows styles to be loaded from the same origin, and it allows images to be loaded from any domain.

Benefits of Using CSP

CSP provides several benefits for website security:

  • Prevents XSS attacks: By restricting the sources from which scripts can be loaded, CSP helps prevent XSS attacks, which are malicious attacks that inject malicious scripts into a website.

  • Reduces the risk of data breaches: CSP can help reduce the risk of data breaches by preventing malicious scripts from accessing sensitive data.

  • Improves website performance: By limiting the number of sources from which the browser can load content, CSP can improve website performance.

Implementing CSP

To implement CSP, you need to add a CSP header to your HTTP responses. This can be done using the following code:

app.use((req, res, next) => {
  res.setHeader("Content-Security-Policy", "script-src 'self' https://example.com; style-src 'self'; img-src *");
  next();
});

CSP Directives

The following is a list of the most common CSP directives:

  • script-src: Specifies the allowed sources for scripts.

  • style-src: Specifies the allowed sources for styles.

  • img-src: Specifies the allowed sources for images.

  • font-src: Specifies the allowed sources for fonts.

  • media-src: Specifies the allowed sources for media (e.g., audio and video).

  • frame-src: Specifies the allowed sources for frames.

  • object-src: Specifies the allowed sources for objects.

  • connect-src: Specifies the allowed sources for connections (e.g., WebSocket connections).

  • child-src: Specifies the allowed sources for child frames.

CSP Reports

CSP supports reporting, which allows you to track violations of your CSP policies. You can configure your website to send CSP violation reports to a specified endpoint. This can help you identify and fix potential security vulnerabilities.

To configure CSP reporting, you need to add the following header to your HTTP responses:

Content-Security-Policy-Report-Only: [rules]

This header will send violation reports to the specified endpoint without actually blocking the violating content. This can be useful for testing your CSP policies before enforcing them.

Potential Applications

CSP can be used in a variety of real-world applications, including:

  • E-commerce websites: Protect customer data from malicious scripts.

  • Financial institutions: Prevent data breaches and protect sensitive information.

  • Government websites: Ensure the integrity of government information and services.

  • Healthcare websites: Protect patient data and maintain compliance with HIPAA regulations.


Topic: TypeScript Basic Types

Explanation:

TypeScript uses basic types to represent different types of data, such as numbers, strings, and booleans. Here are some of the most common basic types:

1. Numbers:

  • Represents numeric values.

  • Integer (whole numbers): e.g., 1, 2, 3

  • Decimal (floating-point numbers): e.g., 1.234, 5.678

2. Strings:

  • Represents sequences of characters.

  • Enclosed in single or double quotes: e.g., "Hello", 'World'

3. Booleans:

  • Represents true or false values.

  • True: true or 1

  • False: false or 0

4. Null:

  • Represents the absence of a value.

  • Used to indicate that a variable is not initialized or has no value.

5. Undefined:

  • Represents a variable that has not been assigned a value.

  • Automatically assigned to variables declared without an initializer.

Code Examples:

// Numbers
let age: number = 30;
let price: number = 19.99;

// Strings
let name: string = "John Doe";
let message: string = "Hello, world!";

// Booleans
let isLoggedIn: boolean = true;
let hasErrors: boolean = false;

// Null
let emptyValue: null = null;

// Undefined
let uninitializedVariable: undefined = undefined;

Real-World Applications:

  • Numbers: To represent quantities, measurements, or prices.

  • Strings: To represent text, names, or descriptions.

  • Booleans: To represent binary choices, flags, or conditions.

  • Null: To represent missing values or uninitialized data.

  • Undefined: To indicate that a variable has not been used or assigned a value.


What are Decorators?

Decorators are a way to enhance (decorate) classes, methods, or properties with additional functionality. They are a powerful feature in TypeScript that allow you to add features without modifying the original code.

Decorators in Action

Imagine you have a game where you have characters with different abilities. You want all characters to have a way to move. Instead of adding a move method to each character, you can use a decorator to automate this.

Code Example:

// Decorator
function Movable(target: any) {
  target.prototype.move = function() {
    console.log("Moving...");
  };
}

// Class
@Movable
class Character {
  // No need to add move method here
}

// Usage
const char = new Character();
char.move(); // Logs "Moving..."

Creating Custom Decorators

To create a decorator, you use the @ symbol followed by the name of the decorator function. The decorator function takes a target as an argument, which represents the class, method, or property being decorated. You can then add functionality to the target.

Example:

// Logger decorator
function Logger(target: any, key: string) {
  const originalMethod = target[key]; // Store original method

  // Overwrite method
  target[key] = function(...args: any[]) {
    console.log("Calling method:", key);
    return originalMethod.apply(this, args); // Call original method
  };
}

// Class
class MyClass {
  @Logger
  public method() {
    console.log("Method called without arguments");
  }
}

// Usage
const obj = new MyClass();
obj.method(); // Logs "Calling method: method" and "Method called without arguments"

Real-World Applications

  • Logging debugging information: Use decorators to log method calls, arguments, and return values.

  • Validation: Ensure that values meet certain criteria before passing them to methods.

  • Performance tracking: Track the time it takes for methods to execute.

  • Injecting dependencies: Automatically inject dependencies into class constructors.

  • Extending functionality: Add additional functionality to existing classes without modifying their code.


Generics in TypeScript

Simplified Explanation:

Imagine generics as containers that can hold different types of data. This allows you to write code that works with any type of data without having to rewrite it for each type.

Basic Syntax

function printArray<T>(arr: T[]): void {
  for (const item of arr) {
    console.log(item);
  }
}

Explanation:

  • <T> is the generic type parameter. It represents the type of data the array arr will hold.

  • You can use T anywhere in the function to refer to the type of the array elements.

  • In this example, T could be any type, such as number, string, or a custom object.

Real-World Example: Implementing a Queue

class Queue<T> {
  private data: T[] = [];

  enqueue(item: T): void {
    this.data.push(item);
  }

  dequeue(): T | undefined {
    return this.data.shift();
  }
}

const numberQueue = new Queue<number>();
numberQueue.enqueue(10);
numberQueue.enqueue(20);
console.log(numberQueue.dequeue()); // Output: 10

const stringQueue = new Queue<string>();
stringQueue.enqueue('Hello');
stringQueue.enqueue('World');
console.log(stringQueue.dequeue()); // Output: Hello

Application in Real World:

Queues are commonly used in computer science to manage a sequence of tasks or events. For example, they can be used to:

  • Handle requests in a web server

  • Process jobs in a batch system

  • Send messages between different parts of an application

Advanced Topics

Generic Constraints:

You can specify constraints on generic parameters to ensure that they meet certain requirements. For example:

function compareValues<T extends Comparable>(a: T, b: T): number {
  return a.compareTo(b);
}

interface Comparable {
  compareTo(other: this): number;
}

Explanation:

  • <T extends Comparable> means that T must implement the Comparable interface.

  • This ensures that both a and b have a compareTo method that can be used to compare them.

Higher-Order Generics:

You can also pass generics as parameters to other generics. For example:

function map<T, R>(arr: T[], f: (item: T) => R): R[] {
  const result = [];
  for (const item of arr) {
    result.push(f(item));
  }
  return result;
}

Explanation:

  • map takes a generic array arr and a function f that takes an item of type T and returns a value of type R.

  • It applies f to each item in arr and returns an array of the results.

Real-World Example: Implementations of Sorting Algorithms

class Sorter<T extends Comparable> {
  sort(arr: T[]): T[] {
    // Implement a sorting algorithm like bubble sort or quick sort
  }
}

const numberSorter = new Sorter<number>();
const numbers = [10, 5, 25, 2, 1];
const sortedNumbers = numberSorter.sort(numbers);
console.log(sortedNumbers); // Output: [1, 2, 5, 10, 25]

const stringSorter = new Sorter<string>();
const strings = ['Hello', 'World', 'JavaScript'];
const sortedStrings = stringSorter.sort(strings);
console.log(sortedStrings); // Output: ['Hello', 'JavaScript', 'World']

Application in Real World:

  • Sorting is a fundamental operation used in many applications, such as:

    • Alphabetical ordering of strings

    • Numerical ordering of numbers

    • Sorting a list of objects by a property


What is TypeScript?

TypeScript is a programming language that adds features to JavaScript, making it more type-safe and easier to maintain. Think of TypeScript as a "helper" for JavaScript that makes writing code more reliable.

Benefits of TypeScript:

  • Type Safety: TypeScript ensures that variables have the correct data type, preventing errors from occurring at runtime.

  • Code Maintenance: TypeScript's static typing makes it easier to find and fix errors before they cause problems in your application.

  • Code Reusability: TypeScript's type annotations help you reuse code more easily and confidently.

Installing TypeScript:

To install TypeScript, you can use a package manager like npm or yarn.

npm install -g typescript
# or
yarn global add typescript

Creating TypeScript Code:

TypeScript files have the .ts extension. You can create a new TypeScript file using your preferred code editor.

Example:

// filename: hello.ts
console.log("Hello, world!");

Compiling TypeScript to JavaScript:

Once you have written your TypeScript code, you need to compile it to JavaScript. You can use the tsc command for this.

tsc hello.ts

This will create a hello.js file that contains the compiled JavaScript code.

Using TypeScript in Real-World Applications:

TypeScript is widely used in both frontend and backend development. For example:

  • Frontend: Creating interactive web applications, mobile apps, and games.

  • Backend: Building scalable web services, APIs, and serverless functions.

Key TypeScript Concepts:

1. Data Types:

TypeScript supports various data types, including primitive types (number, string, boolean) and object types (arrays, objects).

let age: number = 20;
let name: string = "John";
let isMarried: boolean = false;

let numbers: number[] = [1, 2, 3];
let person: { name: string; age: number } = { name: "Mary", age: 25 };

2. Type Annotations:

Type annotations are used to specify the type of a variable or function.

let age: number; // Number
let name: string; // String
let isMarried: boolean; // Boolean

function sum(a: number, b: number): number { // Function returning a number
  return a + b;
}

3. Interfaces:

Interfaces define the structure of an object. They ensure that an object has certain properties and methods.

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

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

4. Classes:

Classes define blueprints for creating objects.

class Person {
  name: string;
  age: number;

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

  greet() {
    console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
  }
}

let person = new Person("John", 25);
person.greet();

5. Modules:

Modules group related code together.

// module.ts
export const PI = 3.14;

// main.ts
import { PI } from "./module";
console.log(`The value of PI is ${PI}`);

Real-World Code Examples:

1. Type-Safe Web Application:

Code:

// app.ts
interface Todo {
  id: number;
  title: string;
  completed: boolean;
}

let todos: Todo[] = [
  { id: 1, title: "Buy milk", completed: false },
  { id: 2, title: "Fix the car", completed: true }
];

function addTodo(title: string): void {
  const newTodo = { id: todos.length + 1, title, completed: false };
  todos.push(newTodo);
}

addTodo("Read a book");

console.log(todos);

Explanation:

This code creates a simple to-do application. The Todo interface defines the structure of a to-do item. The todos array stores a list of to-do items. The addTodo function adds a new to-do item to the list.

2. Class-Based Serverless Function:

Code:

// index.ts
import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda";

class HelloFunction {
  async hello(event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> {
    return {
      statusCode: 200,
      body: JSON.stringify({ message: "Hello, World!" })
    };
  }
}

export const handler = new HelloFunction().hello;

Explanation:

This code creates a simple serverless function that returns a "Hello, World!" message. The HelloFunction class defines the implementation of the function. The handler function is exported and will be invoked when the function is called.

Conclusion:

TypeScript is a powerful tool that enhances the capabilities of JavaScript. It improves code reliability, maintainability, and reusability. By leveraging TypeScript, developers can create more robust and scalable applications.


Interfaces

An interface is like a blueprint for a class. It defines the properties and methods that a class must have.

Creating an Interface

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

This interface defines a Person object with two properties: name (a string) and age (a number).

Implementing an Interface

To use an interface, you create a class that implements it. The class must have all the properties and methods defined in the interface.

class Person implements Person {
  name: string;
  age: number;

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

This class implements the Person interface. It has the required properties and a constructor that initializes them.

Benefits of Interfaces

  • Code documentation: Interfaces help document your code by specifying the expected behavior of classes.

  • Enforce consistency: Interfaces ensure that classes implement the required properties and methods, promoting consistency across your codebase.

  • Modularity: You can create reusable interfaces that can be used by multiple classes, improving code organization and maintainability.

Real-World Example

Database Wrapper Class:

interface Database {
  connect(): void;
  query(query: string): any[];
  close(): void;
}

class MySQLDatabase implements Database {
  // ... Implementation details ...
}

class MongoDBDatabase implements Database {
  // ... Implementation details ...
}

// Usage:
const db = new MySQLDatabase();
db.connect();
const results = db.query("SELECT * FROM users");
db.close();

In this example, the Database interface defines the contract for a database connection. The MySQLDatabase and MongoDBDatabase classes implement this interface, allowing you to interact with different databases consistently and easily.


TypeScript Authorization

Authorization Basics

Authorization is the process of determining if a user has permission to access a resource or perform an action. In TypeScript, authorization can be implemented using decorators, middleware, or by creating custom authorization logic.

Decorators

Decorators are a way to add metadata to a class or method. Authorization decorators can be used to restrict access to certain methods or classes based on user roles or permissions.

Example:

import { Auth, Roles } from '@nestjs/auth';

@Auth('jwt')
@Roles('admin')
export class AdminController {
  // Only users with the 'admin' role can access this controller
}

Middleware

Middleware is a way to intercept incoming requests and perform some action before the request reaches the controller. Authorization middleware can be used to check for user permissions and deny access if necessary.

Example:

import { Injectable, NestMiddleware } from '@nestjs/common';

@Injectable()
export class AuthMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: Function) {
    // Check if the user has the necessary permissions
    if (userHasPermissions()) {
      next();
    } else {
      res.status(403).send('Forbidden');
    }
  }
}

Custom Authorization Logic

In some cases, you may need to implement your own authorization logic. This can be done by creating a custom authorization guard or by using the @CanActivate decorator.

Example:

import { CanActivate, ExecutionContext } from '@nestjs/common';

export class CustomAuthGuard implements CanActivate {
  canActivate(context: ExecutionContext) {
    // Check if the user has the necessary permissions
    return userHasPermissions();
  }
}

Real-World Applications

Authorization is essential for protecting sensitive data and ensuring that only authorized users have access to certain resources or perform certain actions. Here are some real-world applications:

  • E-commerce websites: Only registered users should be able to purchase items.

  • Banking applications: Only authorized users should be able to access their accounts.

  • Social media platforms: Only the owner of a post should be able to edit or delete it.

  • Enterprise software: Only employees with the appropriate roles should be able to access certain files or features.


TypeScript Performance Optimization

Introduction

TypeScript is a superset of JavaScript that makes your code easier to read, understand, and maintain. It can also help you catch bugs before your code runs. However, adding TypeScript to your project can also add some overhead, which can slow down your code.

There are a few things you can do to optimize your TypeScript code and improve performance.

1. Use a Compiler

The TypeScript compiler can help you optimize your code by removing unnecessary code, inlining functions, and doing other optimizations.

To use the compiler, you can install it globally using npm:

npm install -g typescript

Then, you can compile your TypeScript code using the following command:

tsc mycode.ts

This will create a JavaScript file called mycode.js that contains the compiled code.

2. Use Type Annotations

Type annotations can help the TypeScript compiler generate more efficient code. For example, the following code uses type annotations to specify the type of the x variable:

let x: number = 10;

This tells the compiler that x is a number, which allows it to generate more efficient code for operations that involve x.

3. Avoid Circular Dependencies

Circular dependencies can occur when two or more modules depend on each other. This can lead to slower compilation times and can also make it more difficult to debug your code.

To avoid circular dependencies, you should try to organize your code into modules that are as independent as possible.

4. Use Lazy Loading

Lazy loading can help you improve the performance of your application by only loading the code that is needed when it is needed.

To use lazy loading, you can use the async and await keywords. For example, the following code uses lazy loading to load the myModule module only when the myFunction function is called:

async function myFunction() {
  const myModule = await import('./myModule');
  // Use the myModule module here
}

5. Use a Caching Mechanism

A caching mechanism can help you improve the performance of your application by storing frequently used data in memory. This can reduce the number of times that your application needs to access the database or other slow resources.

There are a number of different caching mechanisms that you can use, such as:

  • In-memory caching

  • Database caching

  • CDN caching

Real-World Examples

Here are a few real-world examples of how you can use TypeScript performance optimization techniques:

  • Use a compiler to optimize your code: A compiler can help you remove unnecessary code, inline functions, and do other optimizations that can improve the performance of your code.

  • Use type annotations: Type annotations can help the TypeScript compiler generate more efficient code. For example, if you have a function that takes a number as an argument, you should specify the type of the argument so that the compiler can generate more efficient code for that function.

  • Avoid circular dependencies: Circular dependencies can lead to slower compilation times and can also make it more difficult to debug your code. To avoid circular dependencies, you should try to organize your code into modules that are as independent as possible.

  • Use lazy loading: Lazy loading can help you improve the performance of your application by only loading the code that is needed when it is needed. For example, if you have a large module that is not used by all of the pages in your application, you can use lazy loading to only load that module when it is needed.

  • Use a caching mechanism: A caching mechanism can help you improve the performance of your application by storing frequently used data in memory. This can reduce the number of times that your application needs to access the database or other slow resources.

Conclusion

TypeScript performance optimization techniques can help you improve the performance of your applications. By following these techniques, you can reduce compilation times, reduce the size of your code, and improve the efficiency of your code.


Modules in TypeScript

What are modules?

Modules are like containers that hold related code together. They allow you to organize your code into logical pieces and control access to their contents.

Types of modules:

  • Internal modules: Used within the current project (like moduleA.ts importing from moduleB.ts).

  • External modules: Downloaded from a package registry (like importing lodash from npm).

Creating Modules

Internal modules:

// moduleA.ts
export const greetA = () => "Hello from A";
// moduleB.ts
import { greetA } from "./moduleA";
console.log(greetA()); // "Hello from A"

External modules:

Install the module using a package manager like npm:

npm install lodash
import * as _ from "lodash";
console.log(_.join(["Hello", "World"], " ")); // "Hello World"

Exporting Content

Named exports:

// moduleA.ts
export const greetA = () => "Hello from A";
export function greetB() { return "Hello from B"; }
// moduleB.ts
import { greetA, greetB } from "./moduleA";
console.log(greetA()); // "Hello from A"
console.log(greetB()); // "Hello from B"

Default exports:

Only one default export allowed per module.

// moduleA.ts
export default function greet() { return "Hello from A"; }
// moduleB.ts
import greet from "./moduleA";
console.log(greet()); // "Hello from A"

Importing Content

Named imports:

// moduleA.ts
export const greetA = () => "Hello from A";
export const greetB = () => "Hello from B";
// moduleB.ts
import { greetA } from "./moduleA";
console.log(greetA()); // "Hello from A"

Importing all (wildcard):

// moduleA.ts
export const greetA = () => "Hello from A";
export const greetB = () => "Hello from B";
// moduleB.ts
import * as moduleA from "./moduleA";
console.log(moduleA.greetA()); // "Hello from A"
console.log(moduleA.greetB()); // "Hello from B"

Default imports:

// moduleA.ts
export default function greet() { return "Hello from A"; }
// moduleB.ts
import greet from "./moduleA";
console.log(greet()); // "Hello from A"

Potential Applications

  • Code organization: Group related functionality into separate modules.

  • Code reuse: Import and reuse functions, objects, and data from other modules.

  • Dependency management: External modules allow you to incorporate third-party libraries into your code.

  • Encapsulation: Hide implementation details from other modules, improving code security and maintainability.


End-to-End (E2E) Testing in TypeScript

What is E2E Testing?

E2E testing involves testing an application from the user's perspective, simulating real-world scenarios. It verifies that the entire system works as expected, from start to finish.

Types of E2E Tests

  • Functional Tests: Ensure that the application's main functions work correctly.

  • Integration Tests: Test how different parts of the application communicate and interact.

  • UI Tests: Verify that the user interface functions as expected and provides a good user experience.

  • Performance Tests: Measure how quickly and efficiently the application responds under various conditions.

Tools for E2E Testing in TypeScript

  • Cypress: A popular testing framework designed for modern web applications.

  • Selenium WebDriver: A widely used tool for automated testing of web applications.

  • Puppeteer: A headless browser that allows you to control and test web applications programmatically.

Code Examples

Functional Test with Cypress:

describe('My app', () => {
  it('can login', () => {
    cy.visit('/');
    cy.get('#username').type('admin');
    cy.get('#password').type('secret');
    cy.get('#login-button').click();
    cy.url().should('include', '/dashboard');
  });
});

Integration Test with Selenium WebDriver:

import { By } from 'selenium-webdriver';
import { WebDriver } from 'selenium-webdriver/chrome';

const driver = new WebDriver();
driver.get('http://localhost:4200');
const usernameInput = driver.findElement(By.id('username'));
const passwordInput = driver.findElement(By.id('password'));
const loginButton = driver.findElement(By.id('login-button'));

usernameInput.sendKeys('admin');
passwordInput.sendKeys('secret');
loginButton.click();

driver.getCurrentUrl().then((url) => {
  console.log(`Current URL: ${url}`);
  expect(url).toContain('/dashboard');
  driver.quit();
});

UI Test with Puppeteer:

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('http://localhost:4200/dashboard');

// Check if the dashboard title is displayed
const title = await page.$eval('h1', (el) => el.textContent);
expect(title).toBe('Dashboard');

// Take a screenshot of the dashboard
await page.screenshot({ path: 'dashboard.png' });

await browser.close();

Real-World Applications

  • E-commerce Websites: Ensure that the shopping process, from browsing to checkout, works flawlessly.

  • Social Media Platforms: Test that users can create accounts, post content, and interact with others as intended.

  • Banking Applications: Verify that financial transactions, account management, and security measures are functioning correctly.

  • Healthcare Systems: Test that patient records, medical appointments, and prescription management work seamlessly.


Generics

Generics allow you to create functions or classes that can work with different types of data without having to rewrite the code for each type.

// Define a generic function that takes an array of any type and returns the first element
function first<T>(arr: T[]): T | undefined {
  return arr[0];
}

// Use the generic function with an array of numbers
const firstNumber = first([1, 2, 3]); // firstNumber is type number

// Use the generic function with an array of strings
const firstString = first(["Hello", "World"]); // firstString is type string

Real-world application: Generics can be used in libraries to create functions that work with any type of data, making the library more versatile.

Type Guards

Type guards are used to check the type of a value at runtime. This can be useful in situations where you need to handle different types of data in different ways.

// Define a type guard function that checks if a value is a number
function isNumber(value: unknown): value is number {
  return typeof value === "number";
}

// Use the type guard function to check the type of a value
if (isNumber(value)) {
  // The value is a number
} else {
  // The value is not a number
}

Real-world application: Type guards can be used to validate user input or to handle different types of data in a consistent way.

Mapped Types

Mapped types allow you to create new types based on existing types. This can be useful for creating types that have additional properties or that are more specific than the original type.

// Define a mapped type that adds a "length" property to an array type
type ArrayWithLength<T> = T[] & {
  length: number;
};

// Use the mapped type to create a new type for an array of numbers
type NumberArrayWithLength = ArrayWithLength<number>;

Real-world application: Mapped types can be used to create custom types that meet specific requirements or that extend existing types with additional functionality.

Conditionals

Conditionals allow you to create different types based on a condition. This can be useful for creating types that are more specific or that depend on a runtime value.

// Define a conditional type that creates a type for either a string or a number based on a condition
type StringOrNumber<Condition extends boolean> = Condition extends true ? string : number;

// Use the conditional type to create a new type based on a runtime value
const condition = true;
const type = StringOrNumber<condition>; // type is type string

Real-world application: Conditionals can be used to create types that are more specific or that depend on a runtime value, making your code more flexible and maintainable.

Intersection Types

Intersection types allow you to combine multiple types into a single type. This can be useful for creating types that have the properties of multiple types.

// Define an intersection type that combines a type for a person and a type for an employee
type PersonAndEmployee = {
  name: string;
} & {
  employeeId: number;
};

// Use the intersection type to create a new type for a person who is also an employee
type JohnDoe = PersonAndEmployee;

Real-world application: Intersection types can be used to create types that combine the properties of multiple types, making your code more concise and easier to understand.

Union Types

Union types allow you to create a type that can be any of multiple types. This can be useful for creating types that can represent a variety of different values.

// Define a union type that can be either a string or a number
type StringOrNumber = string | number;

// Use the union type to create a new type for a value that can be either a string or a number
const value: StringOrNumber = "Hello"; // value can be either a string or a number

Real-world application: Union types can be used to create types that can represent a variety of different values, making your code more flexible and maintainable.

Conditional Types

Conditional types allow you to create a type based on a condition. This can be useful for creating types that are more specific or that depend on a runtime value.

// Define a conditional type that creates a type for either a string or a number based on a condition
type StringOrNumber<Condition extends boolean> = Condition extends true ? string : number;

// Use the conditional type to create a new type based on a runtime value
const condition = true;
const type = StringOrNumber<condition>; // type is type string

Real-world application: Conditional types can be used to create types that are more specific or that depend on a runtime value, making your code more flexible and maintainable.

Mapped Types

Mapped types allow you to create a new type based on an existing type. This can be useful for creating types that have additional properties or that are more specific than the original type.

// Define a mapped type that adds a "length" property to an array type
type ArrayWithLength<T> = T[] & {
  length: number;
};

// Use the mapped type to create a new type for an array of numbers
type NumberArrayWithLength = ArrayWithLength<number>;

Real-world application: Mapped types can be used to create custom types that meet specific requirements or that extend existing types with additional functionality.


Debugging Tools

Imagine you're building a lego tower but some pieces are missing. Debugging is like finding those missing pieces to make your tower (code) work.

1. Built-in Console Logging

Logging is like leaving breadcrumbs in your code. You use console.log() to print out information or values at specific points in your program. This helps you see what's happening under the hood.

Example:

console.log("Starting the program...");
// Do other stuff
console.log("Program ended.");

2. Source Maps

When you compile your TypeScript code into JavaScript, the file names and line numbers change. Source maps help map the TypeScript code to the JavaScript code, so you can easily debug your TypeScript code even after it's compiled.

Example:

// TypeScript code
const name = "John";
console.log(name);
// Compiled JavaScript code (after source map)
var name = "John";
console.log(name);

3. Breakpoints

Breakpoints are like pause buttons. You set them in your code to stop the execution at specific points and inspect what's going on.

Example:

const names = ["John", "Jane", "Bob"];

// Set a breakpoint here
names.forEach((name) => console.log(name));

4. Node.js Debugger

If you're running your TypeScript code on Node.js, you can use the Node.js debugger for advanced debugging capabilities. It allows you to inspect variables, set breakpoints, and step through your code line by line.

Example:

// In your TypeScript code
debugger;
// In your terminal
node --inspect index.ts

Potential Applications

  • Finding and fixing errors: Debug tools help you identify and resolve bugs in your code.

  • Understanding code flow: Logging and breakpoints allow you to track the execution path of your program and see how values change.

  • Testing and validation: You can use debug tools to verify that your code is behaving as expected and meets your requirements.

  • Performance optimization: By tracing the code flow, you can identify bottlenecks and optimize the performance of your program.


Debugging in TypeScript

Debugging is the process of finding and fixing errors in your code. TypeScript provides several tools and techniques to help you debug your code, including:

1. Console Logging

Console logging allows you to print messages to the console window, which can be helpful for debugging purposes. You can use the console.log() function to print messages, like this:

console.log("Hello, world!");

2. Breakpoints

Breakpoints allow you to pause the execution of your code at a specific point, so you can inspect the values of variables or step through the code line by line. You can set breakpoints in the TypeScript panel of your IDE or by using the debugger statement in your code, like this:

debugger;

3. The Debugger Object

The debugger object provides a number of properties and methods that can be used to help you debug your code. These include properties such as enabled, which indicates whether the debugger is enabled, and methods such as log(), which can be used to print messages to the console.

4. Unit Testing

Unit testing is a technique for writing automated tests that verify that your code is working correctly. Unit tests can be run independently of your main application, which makes them a good way to test individual functions or modules. TypeScript provides a number of libraries for unit testing, such as Mocha and Jasmine.

5. Source Maps

Source maps are files that map the compiled JavaScript code back to the original TypeScript code. This allows you to debug your TypeScript code using the same tools that you would use to debug JavaScript code.

Real-World Applications

Debugging is an essential skill for any developer, and TypeScript provides a number of tools and techniques to help you debug your code effectively.

Here are some real-world applications of the debugging techniques described above:

  • Console logging can be used to log error messages to the console, which can help you track down the source of an error.

  • Breakpoints can be used to pause the execution of your code at a specific point, so you can inspect the values of variables or step through the code line by line.

  • The debugger object can be used to access a number of properties and methods that can help you debug your code, such as the enabled property and the log() method.

  • Unit testing can be used to verify that your code is working correctly, which can help you prevent errors and bugs from being introduced into your application.

  • Source maps can be used to debug your TypeScript code using the same tools that you would use to debug JavaScript code.


Formatting in TypeScript

What is Formatting?

Formatting is the process of arranging and adjusting code to make it easier to read and understand. It includes things like indentation, spacing, and line breaks.

Benefits of Formatting:

  • Makes code easier to read and understand

  • Improves maintainability and collaboration

  • Reduces bugs

  • Saves time in code reviews

How to Format TypeScript Code

Manual Formatting

You can format your code manually by following the following guidelines:

  • Use 2 spaces for indentation.

  • Use spaces to align code vertically.

  • Use line breaks to improve readability.

Example:

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

Automated Formatting

You can also use automated formatting tools to format your code. There are several popular tools available, such as:

  • Prettier

  • ESLint

  • Stylelint

Example:

To use Prettier, install it using npm:

npm install prettier --save-dev

Then, run the following command to format your code:

prettier --write src/**/*.ts

Code Formatting Rules

There are a number of different code formatting rules that you can follow. Some common rules include:

  • Indentation: Use consistent indentation throughout your code.

  • Spacing: Use spaces to separate elements in your code, such as variables, operators, and function calls.

  • Line Breaks: Use line breaks to improve readability and avoid long lines of code.

  • Comments: Use comments to explain your code and provide context.

Real-World Applications of Formatting

Formatting is essential for any real-world TypeScript project. It makes it easier to read and understand code, which can save time in development and maintenance. It also helps to reduce bugs and improve collaboration.

Here are some real-world examples of how formatting is used:

  • Open-source projects: Many open-source TypeScript projects use automated formatting tools to ensure consistency and readability.

  • Enterprise applications: Large enterprise applications often have strict formatting guidelines to improve maintainability and reduce bugs.

  • Code reviews: Formatting can help to make code reviews faster and more efficient.

  • Educational materials: Formatting can make it easier for students and developers to learn TypeScript.


TypeScript Declaration Files

What are Declaration Files?

Imagine you have a library written in another language, like C or C++. To use it in TypeScript, you need to tell TypeScript what kinds of variables, functions, and objects that library provides. Declaration files are special files (usually with a .d.ts extension) that describe these types.

How do Declaration Files Work?

Declaration files don't contain any code, they just describe what is available in the library. TypeScript reads these files and uses them to understand the types and definitions in the library.

Benefits of Declaration Files

  • Improved type safety: TypeScript can check your code against the types provided in the declaration files, making sure you're using the library correctly.

  • Autocompletion: IDEs like Visual Studio Code use declaration files to provide autocompletion suggestions when you're using the library.

  • Documentation: Declaration files serve as inline documentation, explaining how to use the library.

Writing Declaration Files

To write a declaration file:

  • Start with the declare keyword.

  • Describe the types, variables, functions, and objects provided by the library.

  • Use TypeScript syntax to define the types.

// my-library.d.ts
declare module "my-library" {
  export function addNumbers(a: number, b: number): number;
  export const PI: number;
}

Real-World Examples

  • jQuery: The jQuery declaration files describe the types and functions available in the jQuery library.

  • React: The React declaration files provide type information for React components and hooks.

  • Lodash: The Lodash declaration files define the types and functions for the Lodash utility library.

Applications in Real World

  • Development Speed: Declaration files can speed up development by providing autocompletion and type checking, making it easier to write correct code.

  • Code Reuse: Declaration files can be shared with other developers, allowing them to use your libraries with confidence.

  • Improved Code Quality: Declaration files help ensure that your code is well-typed and free from errors.


TypeScript/Compiler Flags

Overview: Compiler flags are options that you can use to control how the TypeScript compiler behaves. They can be used to change the output of the compiler, enable or disable certain features, or specify the target environment for the compiled code.

Common Compiler Flags:

  • -t, --target: Sets the target version of JavaScript that the compiler will output.

  • -m, --module: Sets the module system that the compiler will use.

  • -p, --project: Specifies the project file to use for compilation.

  • -out, --outFile: Specifies the name of the output file.

  • -d, --declaration: Generates declaration files (.d.ts) for the compiled code.

Advanced Compiler Flags:

  • --strict: Enables or disables strict mode, which enforces additional type checking rules.

  • --noImplicitAny: Disallows the implicit use of the any type.

  • --noImplicitReturns: Requires explicit returns in functions.

  • --emitDecoratorMetadata: Emits metadata about decorators into the compiled code.

  • --experimentalDecorators: Enables experimental support for decorators.

Usage:

Compiler flags can be specified as arguments on the command line when compiling TypeScript code. For example:

tsc -t es5 -m commonjs --out output.js

This command would compile the TypeScript code to ES5 JavaScript using the CommonJS module system and output the result to the file output.js.

Real-World Applications:

  • Target Compatibility: The -t flag can be used to ensure that the compiled code is compatible with a specific version of JavaScript.

  • Module System: The -m flag can be used to specify the module system that the compiled code will use. This is important for interoperability with other JavaScript code.

  • Output Control: The -out flag can be used to control the name and location of the output file.

  • Type Checking: The --strict flag can be used to enforce stricter type checking rules, which can help prevent errors.

  • Decorator Support: The --emitDecoratorMetadata and --experimentalDecorators flags can be used to enable decorator support, which allows for advanced features such as dependency injection.


Topic: Cross-Origin Resource Sharing (CORS)

Introduction: CORS is a mechanism that allows browsers to make requests to resources located on a different domain than the one the browser is currently on. This is necessary because of security restrictions that prevent browsers from making requests to resources on different domains without the explicit permission of the server.

How CORS works: When a browser makes a request to a resource on a different domain, the browser first sends a preflight request to the server. The preflight request is used to determine if the server allows the request to be made. If the server does not allow the request, the browser will not make the actual request.

The preflight request includes the following information:

  • The request method (e.g., GET, POST, PUT, DELETE)

  • The request headers

  • The origin of the request (e.g., the domain of the browser that is making the request)

The server responds to the preflight request with a response that includes the following information:

  • The Access-Control-Allow-Origin header, which specifies the domains that are allowed to make requests to the resource

  • The Access-Control-Allow-Methods header, which specifies the methods that are allowed to be used to make requests to the resource

  • The Access-Control-Allow-Headers header, which specifies the headers that are allowed to be included in requests to the resource

If the server allows the request, the browser will send the actual request to the resource. The actual request includes the same information as the preflight request, but it also includes the data that is being sent to the resource.

Code Example:

// JavaScript code that makes a CORS request
fetch('https://example.com/api/v1/users')
  .then(response => {
    if (response.ok) {
      // The request was successful
      return response.json();
    } else {
      // The request failed
      throw new Error('The request failed with a status of ' + response.status);
    }
  })
  .then(data => {
    // The request was successful and the data has been parsed
    console.log(data);
  })
  .catch(error => {
    // The request failed
    console.error('The request failed with the following error: ' + error);
  });

// Server-side code that responds to CORS requests
const express = require('express');
const app = express();

app.get('/api/v1/users', (req, res) => {
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Methods', 'GET');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
  res.json([
    { id: 1, name: 'John Doe' },
    { id: 2, name: 'Jane Doe' },
  ]);
});

app.listen(3000);

Potential Applications: CORS is used in a variety of applications, including:

  • Loading data from a different domain into a web page

  • Making requests to APIs that are hosted on a different domain

  • Sharing data between different web applications


Namespace and Module Declarations

Namespace:

  • Think of a namespace as a container that groups related functions, classes, and variables and prevents collisions with other code.

  • It allows you to organize your code and avoid naming conflicts.

Example:

// Create a namespace called "MyNamespace"
namespace MyNamespace {
  export function greet(name: string) {
    console.log(`Hello, ${name}!`);
  }
}

// Access the function from the namespace
MyNamespace.greet("John"); // Output: "Hello, John!"

Interface Declarations

Interface:

  • An interface defines a contract that describes the structure of an object.

  • It specifies the properties and methods that an object must implement to conform to the interface.

Example:

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

function printPersonInfo(person: Person) {
  console.log(`Name: ${person.name}, Age: ${person.age}`);
}

const john: Person = { name: "John", age: 30 };
printPersonInfo(john); // Output: "Name: John, Age: 30"

Class Declarations

Class:

  • A class is a blueprint for creating objects.

  • It defines the properties and methods that all objects of that class will have.

Example:

class Car { // Class Declaration
  private make: string; // Property
  private model: string; // Property

  constructor(make: string, model: string) { // Constructor
    this.make = make;
    this.model = model;
  }

  getMake(): string { // Method
    return this.make;
  }
}

const myCar = new Car("Tesla", "Model S"); // Object Creation
console.log(myCar.getMake()); // Output: "Tesla"

Type Declarations

Type:

  • A type specifies the data type of a variable, property, or parameter.

  • It helps enforce data integrity and prevents errors.

Example:

let age: number = 30; // Type Annotation

function addNumbers(a: number, b: number): number { // Type Annotations
  return a + b;
}

Decorators

Decorator:

  • A decorator is a function that modifies the behavior of a class, method, or property.

  • It allows you to add additional functionality or change the behavior of other code.

Example:

@log // Decorator
class Person {
  name: string;

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

function log(target: any) {
  console.log(`Class ${target.name} created`);
}

const john = new Person("John"); // Output: "Class Person created"

Generics

Generics:

  • Generics allow you to create code that can work with different types of data.

  • They enable you to write code that is more flexible and reusable.

Example:

// Generic function that takes an array of any type and a value to search for
function find<T>(arr: T[], value: T): number | undefined {
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] === value) {
      return i;
    }
  }
  return undefined;
}

const arr1 = [1, 2, 3, 4, 5];
const arr2 = ["a", "b", "c", "d", "e"];

console.log(find(arr1, 3)); // Output: 2
console.log(find(arr2, "c")); // Output: 2

Real-World Applications

  • Namespaces: Used for organizing large codebases, preventing collisions, and managing dependencies.

  • Interfaces: Ensure consistent object structures, improve code readability, and enable unit testing.

  • Classes: Provide a structured and object-oriented approach to code organization and reusability.

  • Types: Guarantee data integrity, prevent errors, and improve code performance.

  • Decorators: Add extra functionality and modify the behavior of code, such as logging, caching, or dependency injection.

  • Generics: Create reusable code that can handle different data types, making it more flexible and extensible.


TypeScript Advanced Types

1. Union Types

Imagine a box that can hold either apples or oranges. TypeScript allows you to create a type that can be either one of two or more other types. This is called a "union type".

type Fruit = "apple" | "orange";

let fruit: Fruit = "apple";  // fruit can be either "apple" or "orange"

Example: A function that takes a parameter of type Fruit could be used to count the number of apples and oranges in a given list:

function countFruits(fruits: Fruit[]): { apples: number, oranges: number } {
  let appleCount = 0;
  let orangeCount = 0;
  for (let fruit of fruits) {
    if (fruit === "apple") {
      appleCount++;
    } else if (fruit === "orange") {
      orangeCount++;
    }
  }
  return { apples: appleCount, oranges: orangeCount };
}

2. Intersection Types

Imagine a box that can hold both apples and oranges. TypeScript allows you to create a type that combines two or more other types. This is called an "intersection type".

type FruitBasket = Fruit & { type: "basket" };

let basket: FruitBasket = { type: "basket", fruit: "apple" };

Example: A function that takes a parameter of type FruitBasket could be used to check if a basket contains any fruit:

function hasFruit(basket: FruitBasket): boolean {
  return basket.fruit !== undefined;
}

3. Mapped Types

Imagine a list of numbers that you want to add 10 to each number. TypeScript allows you to create a new type based on an existing type, with modifications applied to the properties. This is called a "mapped type".

type NumberPlusTen = { [key: string]: number };  // map property names to number type

let numbers: number[] = [1, 2, 3];
let numbersPlusTen: NumberPlusTen = numbers.map(n => n + 10);  // { 0: 11, 1: 12, 2: 13 }

Example: A function that takes a parameter of type Partial<T> could be used to create a partial copy of an object, with some properties optional:

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

function createPartialPerson(name: string): Partial<Person> {
  return { name };
}

4. Conditional Types

Imagine you have a function that takes an array of numbers. You want to check if the array contains only even numbers, and return a different type based on the result. TypeScript allows you to define a new type based on the condition of another type. This is called a "conditional type".

type IsEven<T> = T extends number ? (T extends (even: number) => void ? true : false) : never;  // recursively check if T is even

let numbers: number[] = [2, 4, 6];
let isEven: IsEven<typeof numbers> = true;  // true because all numbers in numbers are even

Example: A function that takes a parameter of type T extends Foo ? FooBar : Foo could be used to create a new type that inherits from Foo if T extends Foo, otherwise it just returns Foo.

interface Foo { name: string; }
interface FooBar extends Foo { age: number; }

function createFooOrFooBar(name: string, age?: number): T extends Foo ? FooBar : Foo {
  if (age) {
    return { name, age };
  } else {
    return { name };
  }
}

5. Template Literal Types

Imagine a function that takes a string as an argument and returns a string with the first letter of each word capitalized. TypeScript allows you to define a new type based on a string literal. This is called a "template literal type".

type CapitalizeFirstLetter<S extends string> = S extends `${infer FirstLetter}${infer Rest}` ? `${Capitalize<FirstLetter>}${Rest}` : S;

let sentence: CapitalizeFirstLetter<"hello world"> = "Hello World";

Example: A function that takes a parameter of type keyof T could be used to get all the property keys of an object of type T.

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

function getKeys<T extends object>(obj: T): (keyof T)[] {
  return Object.keys(obj) as (keyof T)[];
}

Classes in TypeScript

What are classes?

Classes are like blueprints for creating objects. They define the structure and behavior of objects.

// Define a class
class Person {
  // Properties (like variables)
  name: string;
  age: number;

  // Constructor (a special function that runs when an object is created)
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  // Methods (like functions)
  greet() {
    console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
  }
}

How to create objects from classes:

// Create an object (instance) of the Person class
let person1 = new Person("John", 30);

// Access the properties and methods of the object
console.log(person1.name); // John
person1.greet(); // Hello, my name is John and I'm 30 years old.

Inheritance

What is inheritance?

Inheritance allows you to create new classes based on existing ones. The child class inherits the properties and methods of the parent class.

// Define a parent class Shape
class Shape {
  width: number;
  height: number;

  constructor(width: number, height: number) {
    this.width = width;
    this.height = height;
  }

  getArea() {
    return this.width * this.height;
  }
}

// Define a child class Rectangle that inherits from Shape
class Rectangle extends Shape {
  constructor(width: number, height: number) {
    super(width, height); // Call the parent constructor
  }

  // Overriding the getArea method
  getArea() {
    return super.getArea() * 2; // Multiply the area by 2 (for a rectangle)
  }
}

// Create an object of the Rectangle class
let rectangle = new Rectangle(5, 10);

// Access the properties and methods of the rectangle object
console.log(rectangle.getArea()); // 100 (5 * 10 * 2)

Polymorphism

What is polymorphism?

Polymorphism means "many forms." It allows objects to behave differently based on their class.

// Define an abstract class Animal
abstract class Animal {
  abstract makeSound(): void; // An abstract method (without an implementation)
}

// Define a child class Dog that inherits from Animal
class Dog extends Animal {
  makeSound(): void {
    console.log("Woof!");
  }
}

// Define a child class Cat that inherits from Animal
class Cat extends Animal {
  makeSound(): void {
    console.log("Meow!");
  }
}

// Create arrays of Dog and Cat objects
let dogs: Dog[] = [new Dog(), new Dog()];
let cats: Cat[] = [new Cat(), new Cat()];

// Loop through the Dog array and call makeSound()
for (let dog of dogs) {
  dog.makeSound(); // Woof!
}

// Loop through the Cat array and call makeSound()
for (let cat of cats) {
  cat.makeSound(); // Meow!
}

Real-World Applications

Classes can be used in a variety of real-world applications, including:

  • Modeling real-world entities, such as people, animals, and products

  • Building reusable components and libraries

  • Creating complex applications with multiple layers of abstraction

  • Implementing inheritance and polymorphism to create flexible and extensible code


Namespaces

Namespaces are a way to organize code into logical groups. They help prevent collisions between identifiers from different parts of your codebase.

Example:

// Define a namespace called "MyNamespace"
namespace MyNamespace {
  export class MyClass {
    // Class members
  }
}

// Use the "MyClass" class from the "MyNamespace" namespace
const myClass = new MyNamespace.MyClass();

Modules

Modules are a way to organize code into reusable units. They can be imported and exported, allowing you to share code between different parts of your application.

Example:

// Define a module called "MyModule"
export module MyModule {
  export class MyClass {
    // Class members
  }
}

// Import the "MyClass" class from the "MyModule" module
import { MyClass } from "./MyModule";

// Use the "MyClass" class
const myClass = new MyClass();

Real-World Applications

Namespaces and modules are used in a wide variety of real-world applications, including:

  • Organizing code in large-scale applications

  • Preventing collisions between identifiers

  • Sharing code between different parts of an application

  • Creating libraries and frameworks

Potential Applications

Here are some potential applications for namespaces and modules:

  • Organizing code in a large-scale application: A large-scale application can be divided into multiple namespaces, each representing a different part of the application. This makes it easier to find and manage the code.

  • Preventing collisions between identifiers: If two different parts of your codebase use the same identifier, there can be a conflict. Namespaces and modules can be used to prevent this by creating separate scopes for the identifiers.

  • Sharing code between different parts of an application: Modules can be used to share code between different parts of an application. This can reduce duplication and make it easier to maintain the code.

  • Creating libraries and frameworks: Libraries and frameworks can be packaged as modules. This makes it easy to distribute and use the code in other applications.


TypeScript/Build Tools Integration

Introduction

TypeScript is a programming language that adds static typing to JavaScript. Build tools like webpack and Rollup can help you compile TypeScript code into JavaScript and bundle it into a single file for deployment.

Webpack

What is Webpack?

Webpack is a build tool that bundles JavaScript code into a single file. It can also handle dependencies, transpile TypeScript code, and minify the output.

How to Use Webpack with TypeScript?

  1. Install Webpack and TypeScript:

npm install --save-dev webpack typescript
  1. Create a webpack.config.js file:

module.exports = {
  entry: './src/index.ts',
  output: {
    filename: 'bundle.js',
  },
  module: {
    rules: [
      {
        test: /\.ts$/,
        use: 'ts-loader',
      },
    ],
  },
  resolve: {
    extensions: ['.ts', '.js'],
  },
};
  1. Run Webpack:

npx webpack

Real-World Application:

  • Bundling your TypeScript application for deployment on a website.

  • Splitting your TypeScript code into multiple bundles for faster loading.

Rollup

What is Rollup?

Rollup is another build tool that bundles JavaScript code, similar to Webpack. It's known for its fast build times and small bundle sizes.

How to Use Rollup with TypeScript?

  1. Install Rollup and TypeScript:

npm install --save-dev rollup typescript
  1. Create a rollup.config.js file:

import typescript from 'rollup-plugin-typescript2';

export default {
  input: './src/index.ts',
  output: {
    file: 'bundle.js',
    format: 'iife',
  },
  plugins: [
    typescript(),
  ],
};
  1. Run Rollup:

npx rollup --config rollup.config.js

Real-World Application:

  • Bundling libraries or components that need to be loaded independently.

  • Creating small and efficient bundles for mobile or embedded devices.

Babel

What is Babel?

Babel is a transpiler that can convert modern JavaScript syntax into older versions that are compatible with older browsers. It can also be used to transpile TypeScript code.

How to Use Babel with TypeScript?

  1. Install Babel and TypeScript:

npm install --save-dev babel-core typescript babel-preset-typescript
  1. Create a .babelrc file:

{
  "presets": ["typescript"]
}

Real-World Application:

  • Transpiling TypeScript code to support older browsers.

  • Using new JavaScript features in legacy projects.

Conclusion

Build tools like Webpack, Rollup, and Babel can help you streamline the compilation and bundling of TypeScript code. By integrating these tools into your development workflow, you can create efficient and maintainable JavaScript applications.


TypeScript Documentation Simplified

Introduction

TypeScript is a superset of JavaScript, which means it includes all the features of JavaScript plus additional features that make it more powerful and convenient.

Features

Type System:

  • TypeScript uses a type system to enforce data types and prevent errors.

  • Types define what kind of value a variable can hold, such as a number, string, or object.

let age: number = 25; // Specifies that 'age' can only hold numbers

Interfaces:

  • Interfaces define the structure of objects and ensure that objects adhere to that structure.

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

Classes:

  • Classes allow you to create and organize code using object-oriented principles.

  • They provide encapsulation, inheritance, and polymorphism.

class Employee {
  name: string;
  salary: number;

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

Generics:

  • Generics allow you to create reusable code that works with any type of data.

function print<T>(value: T) {
  console.log(value);
}

Real-World Applications

  • Enhancing code quality: TypeScript's type system helps detect errors early in development, improving code quality.

  • Increased code maintainability: Interfaces and classes make code more organized and easier to understand.

  • Improved testability: TypeScript's type annotations make it easier to write tests and ensure that code behaves as expected.

  • Cross-platform development: TypeScript can be compiled into JavaScript, allowing you to create cross-platform applications.

Conclusion

TypeScript is a powerful tool that enhances JavaScript by adding a type system, interfaces, classes, and generics. This makes it a great choice for developing complex and maintainable applications.


Enums in TypeScript

An enum (short for enumeration) is a data type that represents a fixed set of constants. In TypeScript, enums are defined using the enum keyword.

Creating an Enum

enum Color {
  Red,
  Green,
  Blue,
}

This creates an enum named Color with three constants: Red, Green, and Blue.

Accessing Enum Values

You can access the values of an enum using the dot operator:

console.log(Color.Red); // Output: 0
console.log(Color.Green); // Output: 1
console.log(Color.Blue); // Output: 2

Note that the values of an enum start from 0 by default. You can specify custom values using the = operator:

enum Color {
  Red = "red",
  Green = "green",
  Blue = "blue",
}

Using Enums in Switch Statements

Enums are often used in switch statements to provide more readable code:

switch (color) {
  case Color.Red:
    console.log("The color is red.");
    break;
  case Color.Green:
    console.log("The color is green.");
    break;
  case Color.Blue:
    console.log("The color is blue.");
    break;
}

Reverse Enum Mapping

Sometimes, you may need to map an enum value back to its name. You can do this by creating a reverse enum mapping:

const colorNames: { [key: number]: string } = {};
for (const color in Color) {
  colorNames[Color[color]] = color;
}

This creates an object where the keys are the enum values and the values are the enum names:

console.log(colorNames[0]); // Output: "Red"
console.log(colorNames[1]); // Output: "Green"
console.log(colorNames[2]); // Output: "Blue"

Real-World Applications

Enums have a variety of applications in the real world, including:

  • Representing states of objects, such as the status of an order or the availability of a product

  • Enumerating options in a user interface, such as the choices in a dropdown menu

  • Defining constants for calculations, such as the value of pi or the dimensions of a shape


Memory Management in TypeScript

Introduction

Memory management is a crucial aspect of programming that ensures efficient use of computer memory. In TypeScript, memory management is handled automatically by the underlying JavaScript engine, which is a garbage-collected language.

Garbage Collection

What is garbage collection?

Garbage collection is an automated process that identifies and frees up memory that is no longer being used by the program. This helps prevent memory leaks, where unused memory is held onto unnecessarily, potentially causing performance issues.

How does garbage collection work in TypeScript?

  • Mark-and-sweep algorithm: The JavaScript engine uses a mark-and-sweep algorithm to perform garbage collection. It marks all live objects (objects that are still being used) and sweeps up any unmarked objects (objects that are no longer needed) to free up memory.

  • Incremental garbage collection: TypeScript uses incremental garbage collection, which runs periodically in the background without interrupting the execution of your code. This allows for more efficient memory management without significant performance penalties.

Types of Memory Leaks

What are memory leaks?

Memory leaks occur when memory is allocated to objects that are no longer needed but are still being referenced in your code, causing the memory to be held onto indefinitely.

Common types of memory leaks in TypeScript:

  • Circular references: When two or more objects reference each other, creating a circular loop that prevents the garbage collector from freeing up either object.

  • Global variables: Declaring variables as global can hold onto memory even if they are no longer being used elsewhere in the program.

  • Event listeners: Failing to remove event listeners when they are no longer needed can prevent the corresponding elements from being garbage collected.

Preventing Memory Leaks

Best practices to prevent memory leaks:

  • Avoid circular references: Break any circular references by using weak references or separating objects into different scopes.

  • Use local variables: Declare variables locally within the smallest possible scope to reduce the risk of accidentally holding onto them longer than needed.

  • Remove event listeners: When elements are removed from the DOM, be sure to remove any associated event listeners to prevent leaks.

  • Use memory profiling tools: Tools like Chrome DevTools can help identify potential memory leaks and suggest ways to mitigate them.

Real-World Applications

Applications of effective memory management in TypeScript:

  • Performance optimization: Preventing memory leaks and managing memory efficiently can significantly improve the performance of your application, especially in memory-intensive systems.

  • Resource conservation: Garbage collection frees up memory that would otherwise be held onto unnecessarily, allowing your application to use resources more effectively.

  • Bug prevention: Memory leaks can lead to unpredictable behavior and crashes, which can be difficult to debug. Effective memory management helps prevent these issues.

Code Examples

Circular Reference Example:

// Create two objects with circular references
const obj1 = { name: "Object 1", ref: null };
const obj2 = { name: "Object 2", ref: obj1 };
obj1.ref = obj2;

Avoiding Circular References Example:

// Use a weak reference to break the circular dependency
const obj1 = { name: "Object 1", ref: new WeakRef(null) };
const obj2 = { name: "Object 2", ref: new WeakRef(obj1) };

Global Variable Leak Example:

// Declaring a global variable can lead to a memory leak
window.globalVariable = { name: "Global Variable" };

Local Variable Example:

// Declaring a local variable limits its scope and prevents leaks
function myFunction() {
  const localVariable = { name: "Local Variable" };
}

Event Listener Leak Example:

// Failing to remove event listeners can cause memory leaks
const element = document.getElementById("myElement");
element.addEventListener("click", () => {
  console.log("Clicked!");
});

Removing Event Listener Example:

// Remove the event listener when it is no longer needed
element.removeEventListener("click", () => {
  console.log("Clicked!");
});

Functions

Functions are blocks of code that can be reused throughout a program. They allow you to organize your code and make it easier to read and maintain.

Declaring Functions

To declare a function, you use the function keyword followed by the function's name and parentheses. Inside the parentheses, you can specify the parameters that the function will accept. The function's body, which contains the code that will be executed, is enclosed in curly braces.

function greet(name: string) {
  console.log(`Hello, ${name}!`);
}

Calling Functions

To call a function, you simply use the function's name followed by parentheses. If the function accepts any parameters, you need to pass them in the parentheses.

greet("John"); // Logs "Hello, John!" to the console

Parameters

Parameters are variables that can be passed to a function when it is called. They allow you to pass information into the function so that it can perform specific tasks.

In the following example, the greet function takes one parameter, name, which is the name of the person to greet.

function greet(name: string) {
  console.log(`Hello, ${name}!`);
}

Return Values

Functions can return values using the return keyword. The returned value can be of any type, including primitive types like numbers and strings, or complex types like objects and arrays.

In the following example, the sum function takes two parameters, a and b, and returns their sum.

function sum(a: number, b: number): number {
  return a + b;
}

Real-World Applications

Functions can be used in a variety of real-world applications, including:

  • Organizing code: Functions help to organize code by grouping related functionality into reusable blocks.

  • Encapsulating logic: Functions can be used to encapsulate complex logic, making it easier to understand and maintain.

  • Reusing code: Functions can be reused throughout a program, reducing the amount of repetitive code.

  • Creating custom functionality: Functions can be used to create custom functionality that can be easily added to other parts of a program.


TypeScript Compiler Configuration

TypeScript (TS) allows you to define custom configurations to guide the compiler's behavior. Here's a detailed overview:

tsconfig.json File

The central configuration file is tsconfig.json, typically located in the project root. It defines various compiler options and settings.

Example:

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "sourceMap": true
  }
}

Explanation:

  • compilerOptions: An object containing the actual compiler options.

  • target: Specifies the ECMAScript (ES) version to compile to (e.g., "es5" for ES5 compatibility).

  • module: Defines the module system to use (e.g., "commonjs" for Node.js CommonJS, "esnext" for ES modules).

  • sourceMap: Generates source maps for debugging (true/false).

Compiler Options

tsconfig.json can define various compiler options, including:

Basic Options:

OptionDescription

strict

Enforces stricter type checking

noImplicitAny

Disallows implicit any type assignments

Module System Options:

OptionDescription

module

Specifies the module system to use (see above)

moduleResolution

Controls how modules are resolved (e.g., "node" for Node.js module resolution)

Output Options:

OptionDescription

outFile

Combines multiple input files into a single output file

sourceMap

Generates source maps for debugging (see above)

Code Examples

Example 1: Enforcing Strict Type Checking

{
  "compilerOptions": {
    "strict": true
  }
}

Example 2: Using ES Modules

{
  "compilerOptions": {
    "module": "esnext"
  }
}

Example 3: Generating Source Maps

{
  "compilerOptions": {
    "sourceMap": true
  }
}

Real-world Applications

Potential Applications:

  • Enforcing Code Standards: tsconfig.json ensures consistent coding practices across team members.

  • Tailoring Code for Specific Platforms: Different target options can produce code compatible with various environments (e.g., web, Node.js).

  • Integrating with Development Environments: IDEs and code editors often use tsconfig.json to configure text editors, code completion, and debugging.

  • Improving Debugging: Source maps generated by sourceMap allow developers to debug compiled code as if it were the original TypeScript source.

  • Optimizing Code Performance: outFile can combine multiple files into a single bundle, reducing HTTP requests and improving load times.


Authentication in TypeScript

Authentication is the process of verifying the identity of a user. It is essential for securing applications and ensuring that only authorized users have access to certain features or resources.

Topics in TypeScript Authentication

1. Token-Based Authentication

In token-based authentication, a server issues a token to a client after verifying the client's credentials. The token is then used by the client to authenticate future requests to the server.

Code Example:

// Server-side code
const jwt = require('jsonwebtoken');
const secret = 'mySecret';

function generateToken(user) {
  return jwt.sign({ id: user.id }, secret, { expiresIn: '1h' });
}

// Client-side code
const token = localStorage.getItem('token');

const request = new Request('https://example.com/api/user', {
  headers: {
    Authorization: `Bearer ${token}`
  }
});

fetch(request).then(response => response.json());

2. OAuth 2.0

OAuth 2.0 is an industry-standard protocol for authorization. It allows users to grant third-party applications access to their data without sharing their credentials.

Code Example:

// Client-side code
const GoogleAuth = require('google-auth-library');

const auth = new GoogleAuth.OAuth2();

auth.signIn().then(response => {
  const accessToken = response.credentials.access_token;
  const idToken = response.credentials.id_token;
});

Subtopics and Topics under Authentication in TypeScript

1. JWT (JSON Web Token)

JWT is a standard for creating secure tokens that can be used for authentication. JWTs are typically signed with a secret key, which ensures their authenticity and integrity.

Code Example:

// Server-side code
const jwt = require('jsonwebtoken');
const secret = 'mySecret';

function generateJWT(user) {
  return jwt.sign({ id: user.id }, secret);
}

2. OAuth 2.0 Authorization Code Grant

This grant type is used by applications that need to access a user's data on a server. It involves a three-way handshake between the client, the user, and the server.

Code Example:

// Client-side code
const GoogleAuth = require('google-auth-library');

const auth = new GoogleAuth.OAuth2();

auth.authorizeUrl({
  redirect_uri: 'https://example.com/callback',
  scope: 'email'
});

Real-World Applications

  • E-commerce: Token-based authentication can be used to authenticate users for online purchases and protect sensitive customer information.

  • Social media: OAuth 2.0 is widely used by social media platforms to allow users to log in with their existing accounts.

  • Healthcare: JWTs can be used to securely exchange patient data between healthcare providers, while OAuth 2.0 can be used to grant authorized access to electronic health records.

  • Education: Token-based authentication can be used to verify the identities of students and instructors in online learning platforms.


Interoperability with JavaScript

1. Type Compatibility between TypeScript and JavaScript

  • TypeScript to JavaScript: TypeScript types are automatically converted to equivalent JavaScript types when compiling.

  • JavaScript to TypeScript: TypeScript can infer types from JavaScript objects, but it's recommended to explicitly annotate them for clarity.

2. Writing TypeScript for JavaScript Code

  • Ambient Declarations (.d.ts Files): Declare JavaScript libraries and objects, allowing TypeScript to recognize and provide type information.

  • Type Annotations: Explicitly declare types for JavaScript variables, functions, and objects to improve code readability and error checking.

Code Example:

// Ambient declaration for jQuery
declare const $: any;

// Type annotation for a function
function greet(name: string): void {
  console.log(`Hello, ${name}!`);
}

3. Using JavaScript Libraries in TypeScript

  • npm Packages: Install JavaScript libraries using npm and import them as modules in TypeScript code.

  • Direct Inclusion: Embed JavaScript files directly into HTML and access them in TypeScript code.

Code Example:

// Using an npm package
import { sum } from "lodash";

// Using a JavaScript file
<script src="my-script.js"></script>

Real-World Application:

  • Interoperability with JavaScript allows TypeScript to be used with existing JavaScript codebases, libraries, and frameworks, enabling seamless integration and code reuse.

4. JavaScript Interop Patterns

  • Type Casting: Manually convert TypeScript types to their corresponding JavaScript counterparts using the any type or the as operator.

  • Function Overloads: Define multiple signatures for a function to accommodate JavaScript and TypeScript calling conventions.

Code Example:

// Type casting:
let x: any = 1; // Assigns number to any
console.log((x as string).length); // Type cast x to string

// Function overload:
function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: any, b: any): any {
  return a + b;
}

Real-World Application:

  • Type casting and function overloads facilitate the integration of TypeScript and JavaScript code written in different styles.