Go to content
The Codest
  • About Us
    • Staff Augmentation
    • Project Development
    • Cloud Engineering
    • Quality Assurance
    • Web Development
  • Our Team
  • Case studies
    • Blog
    • Meetups
    • Webinars
    • Resources
Careers Get in touch
  • About Us
    • Staff Augmentation
    • Project Development
    • Cloud Engineering
    • Quality Assurance
    • Web Development
  • Our Team
  • Case studies
    • Blog
    • Meetups
    • Webinars
    • Resources
Careers Get in touch
2020-05-26
Software Development

Generics in TypeScript

Mateusz Staniuk

Generics in TypeScript - Image

Generics provide reusable bits of code that work with a number of types instead of a single type. Generics provide a way to treat type as a variable and specify it on usage, similar to function parameters.

Generics can be used in conjunction with functions (creating generic function), classes (creating generic class), and interfaces (creating generic interface).

Basic usage

You have probably used generics in the past even without knowing that - the most common usage of generics is declaring an array:

const myArray: string\[\];

It’s not too special at first glance, we just declaring myArray as an array of strings, but it is the same as a generic declaration:

const myArray: Array<string\>;

Keepin’ things explicit

Let’s start with very simple example - how could we port this vanilla JS function to TypeScript:

function getPrefiledArray(filler, length) {
    return (new Array(length)).fill(filler);
}

This function will return array filled with given amount of filler, so length will be number and whole function will return array of filler - but what is filler? At this point it can be anything so one option is to use any:

function getPrefiledArray(filler: any, length: number): any\[\] {
    return (new Array(length)).fill(filler);
}

Using any is certainly generic - we can pass literally anything, so “work with a number of types instead of a single type” from the definition is fully covered, but we lose the connection between filler type and the return type. In this case, we want to return some common thing, and we can explicitly define this common thing as type parameter:

function getPrefiledArray<T>(filler: T, length: number): T\[\] {
    return (new Array(length)).fill(filler);
}

and use like this:

const prefilledArray = getPrefiledArray<number\>(0, 10);

Generic Constraints

Let’s look at different, probably more common cases. Why do we actually use types in functions? For me, it is to ensure that arguments passed to the function will have some properties that I want to interact with.

Once again let’s try to port simple vanilla JS function to TS.

function getLength(thing) {
    return thing.length;
}

We have a nontrivial conundrum - how to ensure that the thing has a length property, and first thought may be to do something like:

function getLength(thing: typeof Array):number {
    return thing.length;
}

and depending on the context it might be correct, overall we are a bit generic - it’ll work with arrays of multiple types, but what if we don’t really know if the thing should always be an array - maybe the thing is a football field or a banana peel? In this case, we have to collect the common properties of that thing into a construct that can define properties of an object - an interface:

interface IThingWithLength {
  length: number;
}

We can use IThingWithLength interface as type of the thing parameter:

function getLength(thing: IThingWithLength):number {
    return thing.length;
}

quite frankly in this simple example, it’ll be perfectly fine, but if we want to keep this type generic, and not face the issue from the first example we can use Generic Constraints:

function getLength<T extends IThingWithLength>(thing: T):number  {
    return thing.length;
}

and use it:

interface IBananaPeel {
  thickness: number;
  length: number;
}

const bananaPeel: IBananaPeel = {thickness: 0.2, length: 3.14};
getLength(bananaPeel);

Using extends ensures that T will contain properties that are defined by IThingWithLength.

Generic Classes

Up to this point, we were working with generic functions, but it’s not the only place where generics shine, let’s see how can we incorporate them into classes.

First of all let’s try to store bunch of bananas in the bananas basket:

class Banana {
  constructor(
    public length: number,
    public color: string,
    public ionizingRadiation: number
  ) {}
}

class BananaBasket {
  private bananas: Banana\[\] = \[\];

  add(banana: Banana): void {
    this.bananas.push(banana);
  }
}

const bananaBasket = new BananaBasket();
bananaBasket.add(new Banana(3.14, ‘red’, 10e-7));

Now let’s try to create general purpose basket, to different stuff with the same type:

class Basket<T\> {
  private stuff: T\[\] = \[\];

  add(thing: T): void {
    this.stuff.push(thing);
  }
}

const bananaBasket = new Basket<Banana>();

And lastly, let's assume that our basket is a radioactive material container and we can only store matter that has ionizingRadiation property:

interface IRadioactive {
  ionizingRadiation: number;
}

class RadioactiveContainer<T extends IRadioactive> {
  private stuff: T\[\] = \[\];

  add(thing: T): void {
    this.stuff.push(thing);
  }
}

Generic Interface

Lastly let’s try to gather all our knowledge and build radioactive empire also using Generic Interfaces:

// Define common attributes for containers
interface IRadioactive {
  ionizingRadiation: number;
}


// Define something that is radioactive
interface IBanana extends IRadioactive {
  length: number;
  color: string;
}

// Define something that is not radioactive
interface IDog {
  weight: number;
}

// Define interface for container that can hold only radioactive stuff
interface IRadioactiveContainer<T extends IRadioactive\> {
  add(thing: T): void;
  getRadioactiveness():number;
}

// Define class implementing radioactive container interface
class RadioactiveContainer<T extends IRadioactive\> implements IRadioactiveContainer<T\> {
  private stuff: T\[\] = \[\];

  add(thing: T): void {
    this.stuff.push(thing);
  }

  getRadioactiveness(): number {
      return this.stuff.reduce((a, b) => a + b.ionizingRadiation, 0)
  }
}

// ERROR! Type 'IDog' does not satisfy the constraint 'IRadioactive'
// And it’s kinda brutal to store dogs inside radioactive container
const dogsContainer = new RadioactiveContainer<IDog>();

// All good fam!
const radioactiveContainer = new RadioactiveContainer<IRadioactive>();

// Remember to sort your radioactive waste - create separate bin for bananas only
const bananasContainer = new RadioactiveContainer<IBanana>();

That’s all folks!

Read more:

- Time for a new reality. An era of remote work has started a month ago

- 5 reasons why you will find qualified Ruby developers in Poland

- Web App Development: Why is Ruby on Rails a technology worth choosing?

- 5 reasons why you will find qualified Ruby developers in Poland

Related articles

Software Development

3 Useful HTML Tags You Might Not Know Even Existed

Nowadays, accessibility (A11y) is crucial on all stages of building custom software products. Starting from the UX/UI design part, it trespasses into advanced levels of building features in code. It provides tons of benefits for...

Jacek Ludzik
Software Development

5 examples of Ruby’s best usage

Have you ever wondered what we can do with Ruby? Well, the sky is probably the limit, but we are happy to talk about some more or less known cases where we can use this powerful language. Let me give you some examples.

Pawel Muszynski
Software Development

Maintaining a Project in PHP: 5 Mistakes to Avoid

More than one article has been written about the mistakes made during the process of running a project, but rarely does one look at the project requirements and manage the risks given the technology chosen.

Sebastian Luczak
Software Development

Why you will find qualified Ruby developers in Poland?

Real Ruby professionals are rare birds on the market. Ruby is not the most popular technology, so companies often struggle with the problem of finding developers who have both high-level skills and deep experience; oh, and by the...

Jakub
Software Development

9 Mistakes to Avoid While Programming in Java

What mistakes should be avoided while programming in Java? In the following piece we answers this question.

Rafal Sawicki
Software Development

A quick dive into Ruby 2.6. What is new?

Released quite recently, Ruby 2.6 brings a bunch of conveniences that may be worth taking a glimpse of.  What is new? Let’s give it a shot!

Patrycja Slabosz

Subscribe to our knowledge base and stay up to date on the expertise from industry.

About us

The Codest – International Tech Software Company with tech hubs in Poland.

    United Kingdom - Headquarters

  • Office 303B, 182-184 High Street North E6 2JA London, England

    Poland - Local Tech Hubs

  • Business Link High5ive, Pawia 9, 31-154 Kraków, Poland
  • Brain Embassy, Konstruktorska 11, 02-673 Warsaw, Poland
  • Aleja Grunwaldzka 472B, 80-309 Gdańsk, Poland

    The Codest

  • Home
  • About us
  • Services
  • Case studies
  • Know how
  • Careers

    Services

  • PHP development
  • Java development
  • Python development
  • Ruby on Rails development
  • React Developers
  • Vue Developers
  • TypeScript Developers
  • DevOps
  • QA Engineers

    Resources

  • What are top CTOs and CIOs Challenges? [2022 updated]
  • Facts and Myths about Cooperating with External Software Development Partner
  • From the USA to Europe: Why do American startups decide to relocate to Europe
  • Privacy policy
  • Website terms of use

Copyright © 2022 by The Codest. All rights reserved.

We use cookies on the site for marketing, analytical and statistical purposes. By continuing to use, without changing your privacy settings, our site, you consent to the storage of cookies in your browser. You can always change the cookie settings in your browser. You can find more information in our Privacy Policy.