My TypeScript Software Design & Architecture book just prelaunched! Check out solidbook.io.
Close

All about TypeScript Static Members | TypeScript OOP

Jul 7th, 2019 / 6 min read / Share / Edit on GitHub
In this blog post, I explain the static keyword and when you might want to make attributes and methods a member of the class, rather than an instance of the class.

In Object-Oriented Programming, we write a lot of classes.

Classes contain properties (methods and attributes) which hold variables and operations.

Every time we define the properties of a class, they are said to belong to either:

  • an instance of the class (an object created via constructor) OR
  • the class itself (we call this a class member)

What do we mean by that?

How can properties belong to only the instance vs. only the class?

When we choose to use or omit the static keyword, it changes who the properties belong to.

Let's look at regular usage without the static keyword.

Regular usage (properties belong to the instance)

Normally, when we define properties on a class, the only time they can be accessed is after we've created an instance of that class or if we use this to refer to the properties that will eventually reside on an instance of the object.

Take this early example from White Label.

type Genre = 'rock' | 'pop' | 'electronic' | 'rap'

class Vinyl {
  public title: string;
  public artist: string;
  public genres: Genre[];

  constructor (title: string, artist: string, genres: Genre[]) {
    this.title = title;
    this.artist = artist;
    this.genres = genres;
  } 

  public printSummary (): void {
    console.log(`${this.title} is an album by ${this.artist}`);
  }
}

const vinyl = new Vinyl('Goo', 'Sonic Youth', ['rock']);
console.log(vinyl.title)    // 'Goo'
console.log(vinyl.artist)   // 'Sonic Youth'
console.log(vinyl.genres)   // ['rock']
vinyl.printSummary();	      // 'Goo is an album by Sonic Youth'

Each of the methods (printSummary(): void) and attributes (title, artist, genres) on the Vinyl class are said to belong to an instance of the class.

In the example, we were only able to access the properties title, artist and genres directly from the object after it was created.

console.log(vinyl.title)    // This is valid!

Also note that when we use printSummary(): void, we can access title and artist using the this keyword:

class Vinyl {
  ...
  public printSummary (): void {
    console.log(`${this.title} is an album by ${this.artist}`);
  }
}

That works because at this point, the resulting object / instance of Vinyl owns those properties.

If we check out TypeScript Playground, we can look at the compiled JavaScript for this code sample:

"use strict";
class Vinyl {
  constructor(title, artist, genres) {
    this.title = title;
    this.artist = artist;
    this.genres = genres;
  }
  printSummary() {
    console.log(`${this.title} is an album by ${this.artist}`);
  }
}

const vinyl = new Vinyl('Goo', 'Sonic Youth', ['rock']);
console.log(vinyl.title); // 'Goo'
console.log(vinyl.artist); // 'Sonic Youth'
console.log(vinyl.genres); // ['rock']
vinyl.printSummary(); // 'Goo is an album by Sonic Youth'

The resulting JavaScript looks nearly identical.

Let's talk a bit about what happens when the properties are owned by the class.

Static properties (properties belong to the class)

When we use the static keyword on properties we define on a class, they belong to the class itself.

That means that we cannot access those properties from an instance of the class.

We can only access the properties directly by referencing the class itself.

To demonstrate, let's add a counter NUM_VINYL_CREATED that increments the number of times that a Vinyl was created.

type Genre = 'rock' | 'pop' | 'electronic' | 'rap'

class Vinyl {
  public title: string;
  public artist: string;
  public genres: Genre[];
  public static NUM_VINYL_CREATED: number = 0;

  constructor (title: string, artist: string, genres: Genre[]) {
    this.title = title;
    this.artist = artist;
    this.genres = genres;

	  Vinyl.NUM_VINYL_CREATED++;        // increment number of vinyl created
    console.log(Vinyl.NUM_VINYL_CREATED)  
  } 

  public printSummary (): void { 
    console.log(`${this.title} is an album by ${this.artist}`);
  }
}

let goo = new Vinyl ('Goo', 'Sonic Youth', ['rock']);
// prints out 0

let daydream = new Vinyl ('Daydream Nation', 'Sonic Youth', ['rock']);
// prints out 1

Because the properties can only be accessed through the class itself, we can't do:

let goo = new Vinyl ('Goo', 'Sonic Youth', ['rock']);
goo.MAX_GENRES_PER_VINYL    // Error
goo.NUM_VINYL_CREATED       // Error

You might have heard of a term called Class Members. An attribute or a method is a class member because they can ONLY be accessed through the class itself; therefore, they're members of the class.

That's great and all, but when would you want to use static properties?

How to know when to use static properties

Before you add that attribute or method, as yourself:

Will this property ever need to be used by another class, without having an instance of this class?

In other words, should I need to call it on an object created by this class? If yes, then continue normally.

If no, then you might want to make a static member.

Scenarios where it could make sense to use a static property

Scenarios where it seems like it might make sense but actually leads to an anemic domain model:

  • to perform validation logic on atttributes for that class (use Value Objects instead)

To demonstrate a worthwhile scenario, let's add a static MAX_GENRES_PER_VINYL attribute to "document a constraint" that a Vinyl may only have at max 2 different types of Genres.

type Genre = 'rock' | 'pop' | 'electronic' | 'rap'

class Vinyl {
  public title: string;
  public artist: string;
  public genres: Genre[];
  public static MAX_GENRES_PER_VINYL: number = 2;

  constructor (title: string, artist: string, genres: Genre[]) {
    this.title = title;
    this.artist = artist;
    this.genres = genres;
  }

  public printSummary (): void { 
    console.log(`${this.title} is an album by ${this.artist}`);
  }
}

And then let's add an addGenre(genre: Genre): void method to enforce this business rule.

type Genre = 'rock' | 'pop' | 'electronic' | 'rap'

class Vinyl {
  public title: string;
  public artist: string;
  public genres: Genre[];
  public static MAX_GENRES_PER_VINYL: number = 2;

  constructor (title: string, artist: string, genres: Genre[]) {
    this.title = title;
    this.artist = artist;
    this.genres = genres;
  }

  public addGenre (genre: Genre): void {
    // Notice that in order to reference the value, we have go through the class
    // itself (Vinyl), not through an instance of the class (this).
    const maxLengthExceeded = this.genres.length < Vinyl.MAX_GENRES_PER_VINYL;
    const alreadyAdded = this.genres.filter((g) => g === genre).length !== 0;

    if (!maxLengthExceeded && !alreadyAdded) {
      this.genres.push(genre);
    }
  }

  public printSummary (): void { 
    console.log(`${this.title} is an album by ${this.artist}`);
  }
}


Discussion

Liked this? Sing it loud and proud 👨‍🎤.


0 Comments

Be the first to leave a comment

Submit

Stay in touch!



About the author

Khalil Stemmler

Khalil Stemmler is a Developer / Designer and co-founder of Univjobs. He frequently publishes articles about Domain-Driven Design and Advanced TypeScript & Node.js best practices for large-scale applications.



View more in TypeScript



You may also enjoy...

A few more related articles

An Introduction to Domain-Driven Design - DDD w/ TypeScript
Jul 30th, 2019 / 12 min read
Domain-Driven Design is the approach to software development which enables us to translate complex problem domains into rich, expr...
Handling Collections in Aggregates (0-to-Many, Many-to-Many) - Domain-Driven Design w/ TypeScript
Jul 25th, 2019 / 10 min read
In this article, we discuss how we can use a few CQS principles to handle unbounded 0-to-many or many-to-many collections in aggre...
Challenges in Aggregate Design #1 - Domain-Driven Design w/ TypeScript
Jul 25th, 2019 / 5 min read
In this series, we answer common questions in aggregate design. Here's an article orignating from the question, "How can a domain ...
How to Design & Persist Aggregates - Domain-Driven Design w/ TypeScript
Jul 24th, 2019 / 28 min read
In this article, you'll learn how identify the aggregate root and encapsulate a boundary around related entities. You'll also lear...

Want to be notified when new content comes out?

Join 2000+ other developers learning about Domain-Driven Design and Enterprise Node.js.

I won't spam ya. 🖖 Unsubscribe anytime.

Get updates