TypeScript has the ability to define classes as abstract. This means they cannot be instantiated directly; only nonabstract subclasses can be. Let’s take a look at what this means when it comes to constructor usage.
To dig into this, let’s create a scratchpad project to work with. We’re going to create a Node.js project and install TypeScript as a dependency.
mkdir ts-abstract-constructors cd ts-abstract-constructors npm init --yes npm install typescript @types/node --save-dev
We now have a package.json
file set up. We need to initialize a TypeScript project as well:
npx tsc --init
This will give us a tsconfig.json
file that will drive configuration of TypeScript. By default, TypeScript transpiles to an older version of JavaScript that predates classes. So we’ll update the config to target a newer version of the language that does include them:
"target": "es2020", "lib": ["es2020"],
Let’s create a TypeScript file called index.ts
. The name is not significant; we just need a file to develop in.
Finally we’ll add a script to our package.json
that compiles our TypeScript to JavaScript and then runs the JS with node:
"start": "tsc --project \".\" && node index.js"
Now we’re ready. Let’s add an abstract class with a constructor to our index.ts
file:
abstract class ViewModel { id: string; constructor(id: string) { this.id = id; } }
Consider the ViewModel
class above. Let’s say we’re building some kind of CRUD app; we’ll have different views. Each view will have a corresponding viewmodel
, which is a subclass of the ViewModel
abstract class.
The ViewModel
class has a mandatory id
parameter in the constructor. This is to ensure that every viewmodel has an id
value. If this were a real app, id
would likely be the value with which an entity was looked up in some kind of database.
Importantly, all subclasses of ViewModel
should either:
ViewModel
base class constructorNow let’s see what we can do with our abstract class.
First of all, can we instantiate our abstract class? We shouldn’t be able to do this:
const viewModel = new ViewModel('my-id'); console.log(`the id is: ${viewModel.id}`);
Sure enough, running npm start
results in the following error (which is also being reported by our editor, VS Code).
index.ts:9:19 - error TS2511: Cannot create an instance of an abstract class. const viewModel = new ViewModel('my-id');
Tremendous. However, it’s worth remembering that abstract
is a TypeScript concept. When we compile our TS, although it’s throwing a compilation error, it still transpiles an index.js
file that looks like this:
"use strict"; class ViewModel { constructor(id) { this.id = id; } } const viewModel = new ViewModel('my-id'); console.log(`the id is: ${viewModel.id}`);
As we can see, there’s no mention of abstract
; it’s just a straightforward class
. In fact, if we directly execute the file with node index.js
we can see an output of:
the id is: my-id
So the transpiled code is valid JavaScript even if the source code isn’t valid TypeScript. This all reminds us that abstract
is a TypeScript construct.
Let’s now create our first subclass of ViewModel
and attempt to instantiate it:
class NoNewConstructorViewModel extends ViewModel { } // error TS2554: Expected 1 arguments, but got 0. const viewModel1 = new NoNewConstructorViewModel(); const viewModel2 = new NoNewConstructorViewModel('my-id');
As the TypeScript compiler tells us, the second of these instantiations is legitimate because it relies on the constructor from the base class. The first is not because there is no parameterless constructor.
Having done that, let’s try subclassing and implementing a new constructor that has two parameters (to differentiate from the constructor we’re overriding):
class NewConstructorViewModel extends ViewModel { data: string; constructor(id: string, data: string) { super(id); this.data = data; } } // error TS2554: Expected 2 arguments, but got 0. const viewModel3 = new NewConstructorViewModel(); // error TS2554: Expected 2 arguments, but got 1. const viewModel4 = new NewConstructorViewModel('my-id'); const viewModel5 = new NewConstructorViewModel('my-id', 'important info');
Again, only one of the attempted instantiations is legitimate. viewModel3
is not because there is no parameterless constructor. viewModel4
is not because we have overridden the base class constructor with our new one, which has two parameters. Hence, viewModel5
is our “Goldilocks” instantiation — it’s just right!
It’s also worth noting that we’re calling super
in the NewConstructorViewModel
constructor. This invokes the constructor of the ViewModel
base (or “super”) class. TypeScript enforces that we pass the appropriate arguments (in our case a single string
).
We’ve seen that TypeScript ensures correct usage of constructors when we have an abstract class. Importantly, all subclasses of abstract classes either:
LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowToast notifications are messages that appear on the screen to provide feedback to users. When users interact with the user […]
Deno’s features and built-in TypeScript support make it appealing for developers seeking a secure and streamlined development experience.
It can be difficult to choose between types and interfaces in TypeScript, but in this post, you’ll learn which to use in specific use cases.
This tutorial demonstrates how to build, integrate, and customize a bottom navigation bar in a Flutter app.
One Reply to "TypeScript, abstract classes, and constructors"
Could we clarify the motivation for this over interfaces (or types)? I suppose having the option of running code in the constructor would be one benefit.