TypeScript
- Good understanding of JavaScript/TypeScript (ES6+, async/await, modules)
- TypeScript Problem Solving Questions
- unknown vs any
- Types of decorators in typescript
- Why decorators in typescript ?
- syntax for inheritance in typescript
- Angular typescript example
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
- ES6+ (ECMAScript 2015 and later): This is the modern standard for JavaScript, introducing significant enhancements that improve code readability and structure. Key features include:
letandconst: Block-scoped variable declarations that prevent common bugs associated with the oldervarkeyword.- Arrow Functions: A more concise syntax for writing functions, which also provides a more intuitive handling of the
thiskeyword. - Classes: Syntactical sugar over JavaScript's existing prototype-based inheritance, making object-oriented programming more familiar to developers from other languages.
- Destructuring: A convenient way to extract values from arrays or properties from objects into distinct variables.
- Promises: Objects representing the eventual completion (or failure) of an asynchronous operation and its resulting value, which help manage "callback hell".
async/await: Introduced in ES2017,async/awaitis built on top of Promises and provides a syntax that makes asynchronous code look and behave more like synchronous code, making it much easier to read and debug.- The
asynckeyword declares a function that always returns a Promise. - The
awaitkeyword is used inside anasyncfunction to pause execution until a Promise settles (resolves or rejects), returning the resolved value. - Proper error handling is achieved using standard
try...catchblocks withinasyncfunctions.
- The
- Modules (
import/export): ES6 introduced a native, standardized module system for organizing code into separate, reusable files.exportstatements are used to share functions, objects, or variables from a module file.importstatements are used to access those exports in other files.- This modular approach helps in building scalable applications and managing dependencies efficiently.
- TypeScript: As a statically typed superset of JavaScript, TypeScript builds on ES6+ syntax and adds powerful features like type safety, type inference, and type annotations. This allows errors related to types to be caught at compile time rather than runtime, leading to more robust and maintainable code, especially in large-scale applications
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:- The
anytype essentially disables type checking for the variable it's assigned to. - You can assign any value to an
anytype variable, and you can perform any operation on it without TypeScript raising an error, even if the operation is invalid for the actual runtime type of the value.
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.
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.
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.
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.
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
undefinedfor 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.
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:
-
Decorators can add metadata or annotations to classes and their members, providing information that can be consumed by other parts of the application or frameworks.
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.
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.
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:{
"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:
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:
// 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:
-
Used in the class declaration of the derived class to specify which base class it inherits from (e.g.,
class DerivedClass extends BaseClass). -
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.
-
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):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):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.
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. 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.
// 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}`);
}
<!-- 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