What distinguishes defining an interface externally versus internally within a class?

In my interpretation, here is how you can declare an interface within a class:

class A {
  a: number
  constructor() {
    this.a = 0
  }
}

And this is the method to define an interface outside of a class:

interface A {
  a: number
}
class A {
  constructor() {
    this.a = 0
  }
}

What sets these two scenarios apart? When should I opt for one over the other? It's worth noting that private and protected modifiers cannot be utilized in interfaces, so if they are needed, then the first approach must be used. However, if only public is required, there seems to be no distinguishable difference between them?

Browsing through the documentation on Classes and Everyday Types has not provided much clarity.

Answer №1

When you refer to "defining an interface inside a class," you are essentially describing the standard way of writing classes. Fields are typically declared within the class itself:

class A {
  a: number
  constructor() {
    this.a = 0
  }
}

If you compile the above code to target modern JavaScript with the correct compiler options, it will generate a JavaScript class featuring a public class field:

// JavaScript
class A {
    a; // <-- public class field declaration
    constructor() {
        this.a = 0;
    }
}

On the other hand, defining an interface outside a class is referred to as declaration merging. This process merges the interface with the class instance type, effectively "patching" it at the type level. However, any declarations within the interface do not impact the runtime behavior. For instance:

interface B {
  a: number
}
class B {
  constructor() {
    this.a = 0
  }
}

Compiling the above example using the same settings mentioned earlier will yield JavaScript without a class field declaration:

// JavaScript
class B {
    // <-- no public class field declaration
    constructor() {
        this.a = 0;
    }
}

The potential for different JavaScript outcomes from these two approaches suggests that they are not identical. Although in some cases, such as the one provided, the practical distinction may be minimal. However, there could be noticeable variations based on context:

class C { a?: number }
console.log(Object.keys(new C()).length) // 1

interface D { a?: number }
class D { }
console.log(Object.keys(new D()).length) // 0

Furthermore, there are differences in type checking between the two methods. Interface merging can introduce elements bypassing standard type checks:

class E { a: number } // compiler error
//        ~
// Property 'a' has no initializer and is not definitely 
// assigned in the constructor.
new E().a.toFixed(); // runtime error

interface F { a: number }
class F { } // no compiler error
new F().a.toFixed(); // runtime error

In the given examples, class E triggers a compiler error due to uninitialized property a, while class F does not. Therefore, E detects an issue that F overlooks.


Moreover, interfaces lack various class-specific features, like the ability to execute runtime code. Class fields initialization and method implementation are exclusive to classes and cannot be mirrored in interface merging:

class G { a: number = 1; b(): number { return 2 } };

interface H { a: number = 1, b(): number { return 2 }} // errors!

Modifiers such as private, protected, static, or abstract are not accessible in interfaces:

abstract class I {
  abstract a: number;
  private b?: number;
  static c: number;
}

interface J {
  abstract a: number; // error!
  private b?: number; // error!
  static c: number; // error!
}

Ultimately, whether to use declaration merging depends on individual preferences. Generally, it is considered an advanced technique aimed at overcoming limitations in existing code. If possible, opting for alternatives may be more straightforward.

Playground link to code

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the best way to utilize project references with multiple tsconfig files?

Let's say I have three separate projects to work on: shared frontend backend In order to use the shared project as a reference in both the frontend and the backend, I need to make a few adjustments. The backend utilizes commonjs modules while the fr ...

Tips for exporting and reusing third-party types in TypeScript

I am facing a challenge with my package, which relies on a 3rd party package API for most of its functions. How can I export the types from the 3rd party package API in my own package? For instance: React uses @types/react to define its types Let's ...

The property 'text' cannot be defined as it is undefined

xml code screenshotError screenshotI'm facing an issue where I am trying to bind a label with my code and set its text, but every time I run the code, I get an error message saying 'Cannot set property 'text' of undefined'. I have ...

The power of RXJS's combineLatest operator

Upon using the combineLatest operator, I encountered an unexpected response when adjusting the interval duration of the first and second observables. Here is an example of the code: let intObs1$ = interval(1000).pipe(take(3)); let intObs2$ = interval( ...

How can time duration be accurately represented in TypeScript?

As I work on designing an interface for my personal project, I am in need of adding a field that represents the length of time taken to complete a task. What data type and format should I use in TypeScript to express this? Below is the interface I have cr ...

Strategies for effectively searching and filtering nested arrays

I'm facing a challenge with filtering an array of objects based on a nested property and a search term. Here is a sample array: let items = [ { category: 15, label: "Components", value: "a614741f-7d4b-4b33-91b7-89a0ef96a0 ...

Steps to set angular for all items in the dropdown menu:

I am currently working on implementing a dropdown feature within my Angular application. The dropdown will display a list of shops, and when a shop is selected, it will show the content related to that particular shop. I need to add a new item called "ALL ...

Encountering Errors while executing the yarn build or tsc commands

https://i.sstatic.net/JuueZ.pngWhenever I attempt to build a project or run the yarn tsc command, I encounter various types of errors. This seems to be due to them being installed in the incorrect location. But what could be causing this issue? Feel free ...

Can I modify the cookie domain for NestJS SessionModule on a per-request basis?

I am currently using NestJS with SessionModule to handle user cookies successfully. However, I have a requirement to override the domain name for certain requests. I am uncertain about how to achieve this within NestJS, as the domain setting appears to b ...

Specialized Character Formats in TypeScript

In my quest to enhance the clarity in distinguishing different types of strings within my program - such as absolute paths and relative paths, I am seeking a solution that ensures functions can only take or return specific types without errors. Consider t ...

Unable to utilize console.log and alert functions within the Next.js application

I'm currently facing a problem in my Next.js application where the console.log and alert functions are not functioning as intended. Despite checking the code, browser settings, and environment thoroughly, pinpointing the root cause of the issue remain ...

Issue with Angular 2 Observable not triggering the complete function

I've been experimenting with the hero app tutorial for Angular 2 and currently have this Component set up: import { Component, OnInit } from '@angular/core' import { Subject } from 'rxjs/Subject'; import { Hero } from "./hero"; im ...

Is it possible to spice up functions passed as @Input in Angular with curry?

I have a set of modals with similar styling but completely different functionalities that I need to use in various scenarios within my app. To make it easier for me, I want to pass the logic as input in these different scenarios. When using simple function ...

Tips on integrating Ionic 2 with Angular 2 services

I'm a beginner with Ionic 2. I came across information in the Angular 2 documentation stating that services need to be injected during application bootstrapping. However, I didn't see any mention of bootstrapping while following the Ionic 2 tuto ...

Issue with InversifyJS @multiInject: receiving an error stating "ServiceIdentifier has an ambiguous match"

Having an issue with inversifyJs while trying to implement dependency injection in my TypeScript project. Specifically, when using the @multiInject decorator, I keep receiving the error "Ambiguous match found for serviceIdentifier". I've been referenc ...

Creating Multiple Routes in a Single TypeScript and AngularJS Route File

I have built a module with TypeScript and AngularJS that includes multiple pages. I would like to use one TypeScript controller per page. How can I define multiple controllers in my routes? Currently, I have only defined one, but what if I have 6 or 7 co ...

Is it considered valid in JavaScript or TypeScript to group values using (value1 || value2) for comparisons, and if it is, what is the reasoning behind

Does anyone know if using the || operator to group values while comparing a single variable is valid in the most recent versions of JavaScript or TypeScript? If not, what could be preventing this from becoming a valid syntactic sugar feature at some point? ...

Applying a filter conditionally in a React and TypeScript application based on checkbox selection

Is there a way to apply a filter only when one of the checkboxes is clicked using react and typescript? What am I attempting to achieve? There is a table containing some data with a filter icon on the page. When the user clicks the filter icon, two check ...

"Improve your Angular ngrx workflow by utilizing the sandbox pattern to steer clear of

Currently, I'm trying to determine whether my implementation of the ngrx and sandbox pattern is effective. Here's the issue I'm facing: getFiles(userId: number, companyId: number) { this.fileService.getFiles(userId, companyId).subscribe(re ...

What is the appropriate Typescript return type to use for a $http request that only returns a successful response with no content?

I recently developed a Typescript service: class SettingsService implements ISettingsService { public info = {}; public backupInfo = {}; public userConfig = {}; public isLoaded = false; constructor( private $http: ng.IHttpSer ...