What is the reason for property x not appearing on type Y despite module augmentation efforts?

I've been experimenting with modules and came across a scenario that I'm struggling to fully understand. This is how I have everything set up:

import MyTesterImpl from "./MyTesterImpl";

declare module "./MyTesterImpl" {
  interface MyTesterImpl {
    augmented: boolean;
  }
}

const main = async (): Promise<void> => {
  let testy: MyTesterImpl = {
    basicA: "hey",
    basicB: "there"
  } as MyTesterImpl;

  testy.augmented = true;
};

main();

MyTestImpl is defined as follows:

export default class MyTesterImpl {
  public basicA: string;
  public basicB: string;

  constructor(a: string, b: string) {
    this.basicA = a;
    this.basicB = b;
  }

  showStuff() {
    console.log(`${this.basicA} ${this.basicB}`);
  }
}

I am using the following tsconfig setup:

{
  "compilerOptions": {
    "target": "es2017",
    "module": "commonjs",
    "lib": ["dom", "es6", "es2017", "esnext.asynciterable"],
    "skipLibCheck": true,
    "sourceMap": true,
    "outDir": "./dist",
    "moduleResolution": "node",
    "declaration": false,
    "removeComments": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "noImplicitThis": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "resolveJsonModule": true,
    "baseUrl": ".",
  },
  "exclude": ["node_modules", "**/*.d.ts", "**/*.spec.ts"],
  "include": ["./**/*.ts"]
}

Despite adding module augmentation to append the augmented field to the MyTesterImpl class, the compiler keeps throwing the error:

Property 'augmented' does not exist on type 'MyTesterImpl'.

You can access a reproducible example of the issue here.

I've spent hours trying to figure out why the error persists. I believe that I should be able to perform module augmentation between a class and an interface with the same name, so I don't think that's the problem. Also, I double-checked the class file path when declaring the module for augmentation.

Everything seems correct to me; what am I overlooking?

Answer №1

This problem arises from a bug in TypeScript where classes exported as default cannot be easily merged; more details can be found on the official TypeScript documentation or this GitHub issue. Unfortunately, the GitHub issue has been open for quite some time now and it is still listed as pending, so there may not be a quick fix available.

An interesting workaround suggested in the discussions involves re-exporting the class as a named export and then merging into that new export. Here's an example:

// ./MyTesterImplRepackaged.ts
import MyTesterImpl from "./MyTesterImpl";
export { MyTesterImpl };

Following that, you would modify your code like this:

// ./index.ts
import { MyTesterImpl } from "./MyTesterImplRepackaged";

declare module "./MyTesterImplRepackaged" {
  interface MyTesterImpl {
    augmented: boolean;
  }
}

const main = async (): Promise<void> => {
  let testy: MyTesterImpl = {
    basicA: "hey",
    basicB: "there"
  } as MyTesterImpl;

  testy.augmented = true; // this should work without issues
};

While this workaround might not be the most elegant solution, it appears to be effective, especially for scenarios similar to the one presented in your example code.

You can also explore this CodeSandbox link for a live demonstration.

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

Having difficulty retrieving keys from a JSON object returned by a mongoose findOne() query

Recently, I've come across a strange issue. I used mongoose to search for a document in my mongoDB using model.findOne() with the following code: Model.findOne({ ID: ID }).then(existingDoc => { console.log(existingDoc ); res. ...

What is the best way to convert an Angular object into a string using a for-loop and display it using $sce in AngularJS?

Currently, I am rendering a block of HTML and directives using $sce.trustAsHtml. To achieve this, I utilized a directive called compile-template which enables the use of ng-directives like ng-click and ng-disabled. While it is possible for me to pass sta ...

Displaying or concealing an HTML element based on a JavaScript XHR request function

I have a single HTML element that I need to display and hide using jQuery within a pure JavaScript XHR request method. Currently, the element always remains visible without hiding when the request is complete or successful. On error, I would like it to be ...

How to troubleshoot Django's Jquery datatable aoData not functioning issue

Getting Started with Datatable Initial Data Setup object_list=[{'username': u'Paul', 'phone_number': u'9910087044', 'user_group__name': '', 'manager_name': ' ', 'role&ap ...

Tips for scaling and compressing a canvas image using only pure javascript and HTML5 techniques

I am currently working on resizing and rotating a camera picture on the client side without using any special libraries. I have successfully completed most of the task, but I encounter an offset of 320 pixels when rotating the image. The source of this off ...

Transitioning in Vue.js can be triggered by changing a value up or down

My current transition block component is set up like this: <div v-if="!surveyResultIsReady" class="vh-md-40 position-relative" > <transition name="custom-classes-transition" enter-active-class="animated slideInRight" ...

What steps should I follow to utilize my spotify api token effectively?

Currently, I am working on developing a random playlist generator using the Spotify API. However, when I retrieve information from their server, I receive a 401 error code. I have managed to obtain an access token by following a tutorial. My main concern ...

How can complex POST parameters be structured in a RESTful API?

For my current project, I am working on developing an application utilizing node.js along with express. This application involves a feature that allows users to subscribe to lists of items. Each subscription includes specific options such as minimum scores ...

Capture a unique error and return a personalized response

Often, I encounter a common issue where I throw a custom error from the middlewares or services. My goal is to catch this custom error and send back a well-formatted response like the example below. { "error" : { "status":422, "message ...

Is there a way to merge two typed Joi schemas together?

I have two different interfaces, where the second one is an extension of the first: interface Core { id: string; } interface User extends Core { firstName: string; } To ensure validation, I utilize Joi schemas. For the Core interface, it's easy ...

Encountering a 404 error while trying to refresh the page in a React App hosted on Her

After deploying a React App on Heroku, I encountered a frustrating issue: every time a page is refreshed, a 404 error appears, such as: Cannot GET /create In my search for a solution, I came across a related issue: Question about 404 with React Router ...

What is the process for incorporating a new method into an object that already exists?

class Person { constructor(name, age) { this.name = name; this.age = age; } sayName() { alert(this.name); } } // Create three instances of the Person class with some made-up data const p1 = new Person('Alice', 25); const p ...

Encountered an issue while trying to install using the command npm install react router dom

For a project I'm working on, every time I attempt to use this command, an error message appears and the installation fails. I've tried multiple commands with no success. Here is the specific error message: npm ERR! code 1 npm ERR! path C:\ ...

Choose items in a particular order based on the class name of their category

How can I dynamically select and manipulate groups of elements? My intention is to select and modify the msgpvtstyleme elements, then select the msgpvtstyle elements separately and work with them as well. The ultimate goal is to apply classes to these grou ...

Tips for modifying the appearance of an anchor <a> element within a table cell with jQuery

One issue I am facing involves changing the style of a specific element within a table cell using jQuery. The change needs to be based on the value in another cell. Below is the table structure: <table id="table"> ...

Exploring the wonders of Lightbox when dealing with content loaded via AJAX

Using Bootstrap 5 along with the Lightbox library from , I encountered an issue. The Lightbox feature functions properly on regular page loads, but fails to load on content loaded via AJAX. I attempted to resolve this by implementing the following code: ...

How to dynamically change the position of an input field within a form using JavaScript and jQuery

I have a form displayed on my website that looks like this: input a input b input c I am wondering if it is achievable to rearrange the order of inputs using jQuery, like so: input a input c input b However, it is important that the data is still ...

Angular, manipulating components through class references instead of creating or destroying them

I am exploring ways to move an angular component, and I understand that it can be achieved through construction and destruction. For example, you can refer to this link: https://stackblitz.com/edit/angular-t3rxb3?file=src%2Fapp%2Fapp.component.html Howeve ...

AngularJS: Display the last four characters of a string and substitute the rest with 'X'

I am attempting to change the characters with X and make it look something like this XXXXXT123 This is what I have tried: var sno = 'TEST123'; alert(sno.slice(0,3).replaceWith('X')); However, I encountered an error in the console ...

Creating a fixed-size set in React with randomly ordered elements

I am currently working on a project where I need to retrieve a random array from a .json file using React. My goal is to create 16 cards, each displaying a number that appears twice. To ensure uniqueness, I am utilizing a Set, but I am facing an issue with ...