Skip to content

Instantly share code, notes, and snippets.

@jarhoads
Created June 4, 2019 19:19
Show Gist options
  • Save jarhoads/6122577c0b546d5a86cc5b4407847c31 to your computer and use it in GitHub Desktop.
Save jarhoads/6122577c0b546d5a86cc5b4407847c31 to your computer and use it in GitHub Desktop.
typescript generics
// it is possible to create generic functions, including generic methods, generic interfaces, and generic classes.
// Functions
// To make a function generic, you add a type parameter enclosed in angle brackets (< >)
// immediately after the function name.
// When you call a generic function, you can specify the type argument by
// placing it in angle brackets after the function name.
function reverse<T>(list: T[]) : T[] {
const reversedList: T[] = [];
for (let i = (list.length - 1); i >= 0; i--) {
reversedList.push(list[i]);
}
return reversedList;
}
// the type arguments can be omitted because the compiler is able to infer the type based on the arguments passed to the function.
const letters = ['a', 'b', 'c', 'd'];
// d, c, b, a
const reversedLetters = reverse<string>(letters);
const numbers = [1, 2, 3, 4];
// 4, 3, 2, 1
const reversedNumbers = reverse<number>(numbers);
// Interfaces
// the type parameters are placed directly after the interface name.
class CustomerId {
constructor(private customerIdValue: number) { }
get value(){
return this.customerIdValue;
}
}
class Customer {
constructor(public id: CustomerId, public name: string) { }
}
interface Repository<T, TId>{
getById(id: TId): T;
persist(model: T): TId;
}
class CustomerRepository implements Repository<Customer, CustomerId> {
constructor(private customers: Customer[]) { }
getById(id: CustomerId) {
return this.customers[id.value];
}
persist(customer: Customer) {
this.customers[customer.id.value] = customer;
return customer.id;
}
}
// Classes
// generic classes can save even more by supplying a single implementation to service many different type scenarios.
// The type parameters follow the class name and are surrounded by angle brackets.
// The type parameter can be used to annotate method parameters, properties, return types, and local variables within the class.
// a generic class to provide a single implementation for all named ID types in a domain model.
// This allows all ids to be named without requiring individual implementations for each named type.
// This is a common pattern that prevents accidental substitution of values.
class DomainId<T> {
constructor(private id: T) { }
get value(): T {
return this.id;
}
}
class OrderId extends DomainId<number> {
constructor(orderIdValue: number) {
super(orderIdValue);
}
}
class AccountId extends DomainId<string> {
constructor(accountIdValue: string) {
super(accountIdValue);
}
}
// Examples of compatibility
function onlyAcceptsOrderId(orderId: OrderId) {
// ...
}
function acceptsAnyDomainId(id: DomainId<any>) {
// ...
}
const accountId = new AccountId('GUID-1');
const orderId = new OrderId(5);
// Error: Argument of type 'AccountId' is not assignable to parameter of type 'OrderId'
// onlyAcceptsOrderId(accountId);
// OK
onlyAcceptsOrderId(orderId);
// OK
acceptsAnyDomainId(accountId);
// Type Constraints
// A type constraint can be used to limit the types that a generic function, interface, or class can operate on
// to specify a contract that all types must satisfy to be used as a type argument.
// Type constraints are specified using the extends keyword,
// whether the constraint is an interface, a class, or a type annotation that describes the constraint.
// If a type argument is specified that does not satisfy the constraint, the compiler will issue an error.
// The constraint also allows the TypeScript language service to supply autocompletion suggestions for the generically typed members.
// You can only specify a single class in a type constraint.
// Although you cannot specify multiple classes in a type constraint,
// you can create an interface that extends multiple classes and uses the interface as the constraint,
// which achieves the same result.
// Any types used with the constraint would then need to satisfy
// all the class signatures that have been combined into the single interface.
interface HasName {
name: string;
}
class Personalization {
static greet<T extends HasName>(obj: T) {
return 'Hello ' + obj.name;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment