TypeScript


Good understanding of JavaScript/TypeScript (ES6+, async/await, modules)

A good understanding of modern JavaScript (ES6+) and TypeScript involves proficiency in key features for writing cleaner, more efficient, and robust code, particularly in managing asynchronous operations and organizing code into modules. 

Core Concepts

TypeScript Problem Solving Questions

console.clear();

function Foo() {
this.bar = 10;
}
Foo.prototype.bar = 42;
var foo = new Foo();
console.log("1:", foo.bar);
delete foo.bar;
console.log("1:", foo.bar);


// OUTPUT OF CODE
// [LOG]: "1:", 10
// [LOG]: "1:", 42




const x = [1,2,3];
x[-1] = -1;
console.log(x[x.indexOf(10000)]);

// OUTPUT OF CODE
// [LOG]: -1





let index1:number = 1;
function tutest() {
let index2:number = 2;
if(index2 > index1) {
let index3:number = 3;
index3++;
}


while(index1 < index2) {
let index4:number = 4;
index1++;
}

console.log(index1);
console.log(index2);
console.log(index3);
console.log(index4);

}
tutest();

// OUTPUT OF CODE
//[LOG]: 2
//[LOG]: 2
//[ERR]: "Executed JavaScript Failed:"
//[ERR]: index3 is not defined

 

use https://www.typescriptlang.org/play/?#code/Q


unknown vs any

In TypeScript, both unknown and any are special types that can represent any value, but they differ significantly in their approach to type safety.
any Type:
This provides maximum flexibility but sacrifices type safety, potentially leading to runtime errors that TypeScript was designed to prevent.
any is often used when integrating with untyped JavaScript libraries or when dealing with highly dynamic data structures where strict typing is impractical.
unknown Type:
The unknown type is a type-safe counterpart to any. 
Like any, you can assign any value to an unknown type variable. 
However, unlike any, TypeScript enforces type checking when you try to perform operations on an unknown variable.
Before you can use a value of type unknown in an operation (e.g., accessing a property, calling a method), you must first narrow its type using type guards (like typeof checks or instanceof).
This ensures that you explicitly handle the possible types of the value, promoting safer and more predictable code.
Key Differences Summarized:
Feature
any
unknown
Type Checking
Bypasses all type checking.
Enforces type checking before operations.
Operations
Allows any operation without prior checks.
Requires type narrowing (type guards) before operations.
Safety
Less type-safe, prone to runtime errors.
More type-safe, helps prevent runtime errors.
Usage
For maximum flexibility, or when strict typing is not feasible.
For type-safe handling of values with unknown types.
In essence:
Use any when you need to quickly bypass TypeScript's type system and accept the associated risks.
Prefer unknown when you want to handle values of an uncertain type in a type-safe manner, ensuring that you explicitly check and handle their potential types before using them.

Types of decorators in typescript

TypeScript offers several types of decorators that can be applied to different parts of a class declaration to add metadata or modify behavior. These types include: 
  • Class Decorators:
    • Applied directly before a class declaration.
    • They receive the class constructor as their argument.
    • Can be used to observe, modify, or even replace a class definition.
TypeScript

    function sealed(constructor: Function) {
        Object.seal(constructor);
        Object.seal(constructor.prototype);
    }

    @sealed
    class Greeter {
        greeting: string;
        constructor(message: string) {
            this.greeting = message;
        }
        greet() {
            return "Hello, " + this.greeting;
        }
    }
  • Method Decorators:
    • Applied before a method declaration within a class.
    • They receive the target object (either the constructor function for static members or the prototype for instance members), the name of the method, and the property descriptor of the method.
    • Can be used to modify the behavior of a method, such as adding logging or error handling.
TypeScript

    function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        const originalMethod = descriptor.value;
        descriptor.value = function (...args: any[]) {
            console.log(`Calling ${propertyKey} with arguments: ${JSON.stringify(args)}`);
            const result = originalMethod.apply(this, args);
            console.log(`${propertyKey} returned: ${JSON.stringify(result)}`);
            return result;
        };
        return descriptor;
    }

    class Calculator {
        @log
        add(a: number, b: number) {
            return a + b;
        }
    }
  • Accessor Decorators:
    • Applied before a getter or setter declaration.
    • Similar to method decorators, they receive the target, property key, and property descriptor.
    • Can be used to modify the behavior of property accessors.
TypeScript

    function configurable(value: boolean) {
        return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
            descriptor.configurable = value;
        };
    }

    class Point {
        private _x: number;
        constructor(x: number) {
            this._x = x;
        }

        @configurable(false)
        get x() {
            return this._x;
        }
    }
  • Property Decorators:
    • Applied before a property declaration within a class.
    • They receive the target object and the name of the property.
    • Primarily used to add metadata to a property, which can then be read at runtime using libraries like reflect-metadata.
TypeScript

    import "reflect-metadata";

    const formatMetadataKey = Symbol("format");

    function format(formatString: string) {
        return Reflect.metadata(formatMetadataKey, formatString);
    }

    function getFormat(target: any, propertyKey: string) {
        return Reflect.getMetadata(formatMetadataKey, target, propertyKey);
    }

    class Post {
        @format("DD/MM/YYYY")
        createdAt: Date;
        constructor(createdAt: Date) {
            this.createdAt = createdAt;
        }
    }
  • Parameter Decorators:
    • Applied before a parameter in a class constructor or a method declaration.
    • They receive the target, the name of the method (or undefined for constructor parameters), and the index of the parameter in the argument list.
    • Can be used to add metadata to parameters, often for validation or dependency injection purposes.
TypeScript

    import "reflect-metadata";

    const requiredMetadataKey = Symbol("required");

    function required(target: Object, propertyKey: string | symbol, parameterIndex: number) {
        let existingRequiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || [];
        existingRequiredParameters.push(parameterIndex);
        Reflect.defineMetadata(requiredMetadataKey, existingRequiredParameters, target, propertyKey);
    }

    class GreeterWithValidation {
        greet(@required message: string) {
            return "Hello, " + message;
        }
    }

Why decorators in typescript ?

TypeScript decorators are a special kind of declaration that can be attached to class declarations, methods, accessors, properties, or parameters. They are functions that are prefixed with the @ symbol and are called at runtime with information about the decorated declaration.
Purpose of Decorators:
Modification and Extension: 
They allow for the modification or extension of the behavior of classes or their members at design time, without directly altering the original code. This is a form of meta-programming.
Aspect-Oriented Programming (AOP): 
Decorators facilitate AOP by enabling the injection of cross-cutting concerns (like logging, validation, or authentication) into various parts of an application in a declarative manner.
Code Reusability and Organization: 
They promote cleaner, more organized, and reusable code by encapsulating common functionalities that can be applied across multiple classes or members.
Types of Decorators:
TypeScript supports several types of decorators, each applied to a specific declaration type:
Class Decorators: Applied to a class declaration.
Method Decorators: Applied to a method within a class.
Accessor Decorators: Applied to a getter or setter (accessor) within a class.
Property Decorators: Applied to a property within a class.
Parameter Decorators: Applied to parameters of a method or constructor (supported in older decorator syntax, not in the current Stage 3 ECMAScript proposal).
Enabling Decorators:
To use decorators, they must be enabled in your TypeScript configuration. For the older, experimental Stage 2 decorators, you would set experimentalDecorators to true in your tsconfig.json file:
Code
{
  "compilerOptions": {
    "target": "ES5",
    "experimentalDecorators": true
  }
}
For the newer Stage 3 decorators (introduced in TypeScript 5.0), experimentalDecorators should be set to false (which is the default).
Decorator Factories:
Decorators can also be created using decorator factories, which are functions that return the actual decorator function. This allows for customization and parameterization of decorators.
Example of a Class Decorator:
TypeScript
function Logger(constructor: Function) {
  console.log('Logging...');
  console.log(constructor);
}

@Logger
class User {
  name: string;

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

const user = new User('Alice');
In this example, the Logger decorator is applied to the User class, causing the Logger function to execute when the User class is defined, even before any instance is created.

syntax for inheritance in typescript

In TypeScript, class inheritance is achieved using the extends keyword, allowing a derived class (child class) to inherit properties and methods from a base class (parent class). The super keyword is used within the derived class to access the constructor and methods of the base class.
Here is the general syntax for inheritance in TypeScript:
TypeScript
// Base Class (Parent/Superclass)
class BaseClass {
  // Properties and methods of the base class
  property1: string;

  constructor(value: string) {
    this.property1 = value;
  }

  method1(): void {
    console.log(`BaseClass method1: ${this.property1}`);
  }
}

// Derived Class (Child/Subclass)
class DerivedClass extends BaseClass {
  // Properties and methods specific to the derived class
  property2: number;

  constructor(baseValue: string, derivedValue: number) {
    // Call the constructor of the base class using super()
    super(baseValue);
    this.property2 = derivedValue;
  }

  method2(): void {
    console.log(`DerivedClass method2: ${this.property2}`);
    // Call a method of the base class using super.methodName()
    super.method1();
  }
}

// Example usage
const myDerivedObject = new DerivedClass("Hello from Base", 123);
myDerivedObject.method1(); // Inherited from BaseClass
myDerivedObject.method2(); // Specific to DerivedClass
Key elements of the syntax:
  • extends keyword: 
    Used in the class declaration of the derived class to specify which base class it inherits from (e.g., class DerivedClass extends BaseClass).
  • super()
    Used within the constructor of the derived class to call the constructor of the base class. This is necessary to initialize any properties defined in the base class's constructor. 
  • super.methodName()
    Used within the derived class to call a method of the base class. This allows the derived class to extend or override base class functionality while still being able to invoke the original implementation.

Angular typescript example

Angular is built with TypeScript, so all Angular examples inherently use TypeScript. A fundamental example involves defining a component with a TypeScript class, an HTML template, and a CSS selector. 

Basic Angular Component Example
This example demonstrates a simple HelloComponent that uses a string property in its template via interpolation. 
hello.component.ts (TypeScript class with metadata):

typescript
import { Component } from '@angular/core';

@Component({
  selector: 'app-hello', // The tag used in HTML, e.g., <app-hello></app-hello>
  template: `<h1>{{ title }}</h1>
             <p>Welcome to the Angular application!</p>`, // Inline HTML template
  standalone: true // Marks the component as standalone (modern approach)
})
export class HelloComponent {
  title = 'Hello world!'; // A TypeScript property available to the template
}
main.ts (Bootstrapping the application):

typescript
import { bootstrapApplication } from '@angular/platform-browser';
import { HelloComponent } from './hello.component';

bootstrapApplication(HelloComponent)
  .catch(err => console.error(err));

TypeScript Fundamentals in Angular
Angular makes extensive use of core TypeScript features. 

Interfaces
Interfaces are used for static typing, ensuring objects have the correct structure. 

typescript
interface Product {
  id: number;
  name: string;
  price: number;
}

const productList: Product[] = [
  { id: 1, name: 'Laptop', price: 999 },
  { id: 2, name: 'Phone', price: 499 }
];

Classes and Decorators
Components and services are defined as TypeScript classes. Decorators (like @Component or @Injectable) attach metadata to these classes, telling Angular how to process them. 

typescript
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class ProductService {
  getProducts(): Product[] {
    // Logic to fetch data (e.g., from an API using HttpClient)
    return productList;
  }
}

Data Binding
Angular uses TypeScript properties and methods with data binding syntax in the HTML templates. 

typescript
// In a component class (.ts file)
username: string = 'JaneDoe';

deleteItem(index: number): void {
  // Logic to handle an event
  console.log(`Deleting item at index ${index}`);
}

html
<!-- In the corresponding template (.html file) -->
<input [(ngModel)]="username"> <!-- Two-way binding -->
<button (click)="deleteItem(i)">Delete</button> <!-- Event binding -->
<p>{{ username }}</p> <!-- Interpolation -->
For more in-depth examples and tutorials, the official Angular documentation and the TypeScript handbook are excellent resources