Class Composition

in                

Agenda

Software design
TypeScript's versatility
Inheritance and Polymorphism
Composition in Angular
Demo

Andrés Gesteira

X - @GeorgeKaplan_G

Born in Madrid.

Senior Frontend Architect  @ Fever

Angular lover.

Likes cinema, books, and food.

Software design

Design concepts

Paradigms

Principles

Patterns

TypeScript's versatility

TypeScript paradigms

  • Prototype-based​ since it is a superset of JavaScript.
  • OOP through constructor functions or ES6 classes.
  • Functional programming avoiding mutable data.
  • Event-driven programming with e.g. mouse and load events.
  • Asynchronous programming with e.g. callbacks and promises.
  • Reactive programming with observable streams.
  • Interface-based programming based on TS's static typing.

Criteria

  • Backend projects should apply paradigms.
  • Frontend projects should apply patterns.
  • Most frontend projects are component-based.
  • Backend aligns with business logic.
  • Components apply visual logic and associated behavior.

Inheritance and Polymorphism

Stay D.R.Y.

Be an animal

abstract class Animal {
    abstract makeSound(): void;
    move(distanceInMeters: number): void {
      console.log(`This animal moved ${distanceInMeters} meters.`);
    }
}

class Dog extends Animal {
    makeSound(): void {
      console.log('The dog barks.');
    }
}

class Cat extends Animal {
    makeSound(): void {
      console.log('The cat meows.');
    }
}

const dog = new Dog();
const cat = new Cat();
dog.makeSound(); // Output: The dog barks.
dog.move(10);    // Output: This animal moved 10 meters.
cat.makeSound(); // Output: The cat meows.
cat.move(5);     // Output: This animal moved 5 meters.

Any animal

  • Humanity hates snakes, but they are also animals.
  • They are just silent animals.
  • So we need 2 abstractions in hierarchy chain:
    • NoisyAnimal.
    • SilentAnimal.
  • ​Which would create 2 very (too) similar sub-chains.

Avoid abstract classes

  • They can only be extended by one other class.
  • They break the Liskov substitution principle.
  • Hierarchy chains can become very complex.
  • Polymorphism may involve runtime method resolution.
  • Code can get very difficult to debug.

Composition in Angular

Composition vs Inheritance

  • Composition offers a more flexible approach.
  • It follows the mixin pattern.
  • Mixins are objects that can add reusable functionality to another object or class, without using inheritance.
  • The best way to apply it is through TypeScript classes.

TS class frameworks

NG Directive composition API

Allows us to apply Angular directives to a component's host element from within this component's TypeScript class.

It does not allow us to use properties or methods from any host directive directly in the component class.

@Component({
  standalone: true,
  [...],
  hostDirectives: [MenuBehaviorDirective],
})
export class AdminMenuComponent {
  menuOpen: boolean = inject(MenuBehaviorDirective).menuOpen;
}

Mixins and decorators

  • The only 2 effective ways of doing TS class composition.
  • Mixins are explicitly designed for composition, whereas decorators are more inclined towards metaprogramming by annotating classes or class members.
  • Decorators are currently at stage 3 of the TC39 process.
  • TypeScript 5 can implement the stage 3 spec without using any compiler options for it.
  • However, you cannot access NG's injection context with a decorator, so we will choose mixin classes over them.

Why not using Object.assign()

  • You merge properties and methods from plain objects into a single object at runtime.
  • Produces a shallow copy of enumerable own properties.
  • You lose the type safety.
  • Applies mutation if you do not use `{}` as target.

With helper functions

Demo

Conclusion

Use mixins for class compositions that involve either adding or replacing class members. However, do use decorators for edge cases that may involve initializing or modifying functionality.

Made with Slides.com