TypeScript Essentials

TypeScript Classes and Decorators

Learn TypeScript classes, access modifiers, abstract classes, and decorators — the foundation of Angular's component model.

Classes in TypeScript

Angular components, services, directives, and pipes are all TypeScript classes. Understanding classes is essential for Angular development.

Basic Class

typescript
class Animal {
  name: string;
  sound: string;

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

  speak(): string {
    return `${this.name} says ${this.sound}!`;
  }
}

const dog = new Animal('Dog', 'Woof');
console.log(dog.speak()); // "Dog says Woof!"

Parameter Properties (Shorthand)

TypeScript lets you declare and initialize properties directly in the constructor:

typescript
class User {
  constructor(
    public name: string,
    public email: string,
    private password: string,
  ) {}

  getProfile() {
    return { name: this.name, email: this.email };
  }
}

const user = new User('Alice', 'alice@test.com', 'secret123');
console.log(user.name);      // 'Alice' — accessible
// console.log(user.password); // Error: 'password' is private

Access Modifiers

ModifierAccessible FromAngular Usage
publicEverywhereTemplate-bound properties
privateSame class onlyInternal logic
protectedSame class + subclassesBase class methods
readonlyRead-only after initConstants, injected services
typescript
class BankAccount {
  public owner: string;
  private balance: number;
  protected accountType: string;
  readonly id: string;

  constructor(owner: string, initialBalance: number) {
    this.owner = owner;
    this.balance = initialBalance;
    this.accountType = 'checking';
    this.id = crypto.randomUUID();
  }

  public deposit(amount: number): void {
    this.balance += amount;
  }

  public getBalance(): number {
    return this.balance;
  }
}

Angular Tip: Properties bound to the template in Angular must be public (or have no modifier, which defaults to public).

Inheritance

typescript
class Shape {
  constructor(public color: string) {}

  describe(): string {
    return `A ${this.color} shape`;
  }
}

class Circle extends Shape {
  constructor(color: string, public radius: number) {
    super(color); // Call parent constructor
  }

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

  // Override parent method
  override describe(): string {
    return `A ${this.color} circle with radius ${this.radius}`;
  }
}

const circle = new Circle('red', 5);
console.log(circle.describe()); // "A red circle with radius 5"
console.log(circle.area());     // 78.539...

Abstract Classes

Abstract classes cannot be instantiated directly — they serve as base classes:

typescript
abstract class BaseComponent {
  abstract title: string;
  abstract render(): string;

  log(): void {
    console.log(`Rendering: ${this.title}`);
  }
}

class HeaderComponent extends BaseComponent {
  title = 'Site Header';

  render(): string {
    return `<header>${this.title}</header>`;
  }
}

// const base = new BaseComponent(); // Error: cannot instantiate abstract class
const header = new HeaderComponent();
header.log();     // "Rendering: Site Header"
header.render();  // "<header>Site Header</header>"

Implementing Interfaces

Classes can implement interfaces to guarantee they conform to a contract:

typescript
interface Serializable {
  toJSON(): string;
}

interface Loggable {
  log(): void;
}

class Product implements Serializable, Loggable {
  constructor(
    public name: string,
    public price: number,
  ) {}

  toJSON(): string {
    return JSON.stringify({ name: this.name, price: this.price });
  }

  log(): void {
    console.log(`Product: ${this.name} — $${this.price}`);
  }
}

Getters and Setters

typescript
class Temperature {
  private _celsius: number;

  constructor(celsius: number) {
    this._celsius = celsius;
  }

  get celsius(): number {
    return this._celsius;
  }

  set celsius(value: number) {
    if (value < -273.15) {
      throw new Error('Temperature below absolute zero!');
    }
    this._celsius = value;
  }

  get fahrenheit(): number {
    return this._celsius * 9 / 5 + 32;
  }
}

const temp = new Temperature(100);
console.log(temp.fahrenheit); // 212
temp.celsius = 0;
console.log(temp.fahrenheit); // 32

Understanding Decorators

Decorators are special functions that modify classes, methods, or properties. Angular relies heavily on decorators.

How Decorators Work

A decorator is a function that receives the target it decorates and can modify it:

typescript
// A simple class decorator
function LogClass(constructor: Function) {
  console.log(`Class created: ${constructor.name}`);
}

@LogClass
class MyService {
  // When this class is loaded, the console logs:
  // "Class created: MyService"
}

Angular's Built-in Decorators

DecoratorTargetPurpose
@Component()ClassDefines a component
@Injectable()ClassMarks a service for DI
@Directive()ClassDefines a directive
@Pipe()ClassDefines a pipe
@Input()PropertyAccepts data from parent
@Output()PropertyEmits events to parent
@ViewChild()PropertyReferences a child element
@HostListener()MethodListens to host element events

Decorator Factories (with Parameters)

Angular decorators are factories — they return the actual decorator:

typescript
// @Component is a factory
@Component({
  selector: 'app-user-card',
  standalone: true,
  templateUrl: './user-card.component.html',
  styleUrl: './user-card.component.css',
})
export class UserCardComponent {
  // Component logic
}

Property Decorators in Angular

typescript
import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-product-card',
  standalone: true,
  template: `
    <div class="card">
      <h3>{{ product.name }}</h3>
      <p>{{ product.price | currency }}</p>
      <button (click)="onAddToCart()">Add to Cart</button>
    </div>
  `,
})
export class ProductCardComponent {
  @Input() product!: Product;                    // Input from parent
  @Output() addToCart = new EventEmitter<Product>(); // Output to parent

  onAddToCart() {
    this.addToCart.emit(this.product);
  }
}

Practical Example: Angular Component Class

Here's a complete example combining classes, decorators, and TypeScript features:

typescript
import { Component, OnInit, inject } from '@angular/core';
import { CommonModule } from '@angular/common';

interface Task {
  id: number;
  title: string;
  completed: boolean;
}

@Component({
  selector: 'app-task-list',
  standalone: true,
  imports: [CommonModule],
  template: `
    <h2>Tasks ({{ incompleteTasks.length }} remaining)</h2>
    <ul>
      @for (task of tasks; track task.id) {
        <li [class.done]="task.completed">
          <input type="checkbox"
                 [checked]="task.completed"
                 (change)="toggle(task)" />
          {{ task.title }}
        </li>
      }
    </ul>
  `,
  styles: [`.done { text-decoration: line-through; opacity: 0.6; }`],
})
export class TaskListComponent implements OnInit {
  tasks: Task[] = [];

  get incompleteTasks(): Task[] {
    return this.tasks.filter(t => !t.completed);
  }

  ngOnInit() {
    this.tasks = [
      { id: 1, title: 'Learn TypeScript', completed: true },
      { id: 2, title: 'Build Angular app', completed: false },
      { id: 3, title: 'Deploy to production', completed: false },
    ];
  }

  toggle(task: Task) {
    task.completed = !task.completed;
  }
}

Next Steps

Now that you understand TypeScript classes and decorators, you're ready to dive into Angular components — the core building blocks of every Angular application.