TypeScript class that utilizes internal functions to implement another class

In my journey of exploration, I decided to try implementing a class within another class in TypeScript.

Here is the code snippet I came up with and tested on the Playground link:

class A {
    private f() { console.log("f"); }
    public g() { console.log("G"); }
}

class B implements A {
    public g() { console.log("g"); }
}

Upon testing, I encountered the error:

Class 'B' incorrectly implements class 'A' --- property 'f' is missing in type 'B'
. This error also suggested that I should use extends instead of implements.

In an attempt to rectify this, I tried introducing a private field named f, as using public resulted in different access modifiers, as seen in this Playground link.

However, this led to a new error:

Class 'B' incorrectly implements class 'A'. Types have separate declarations of a private property 'f'
, which left me baffled.

  • Why is the presence of private members crucial in this context? Does it mean that if I use different data structures to implement the same algorithm, I would have to declare similarly named private properties to satisfy type checking?
  • Why does the error persist even when f is implemented as a private function?

Although I would not typically approach tasks this way, I am intrigued by the workings of TypeScript in this scenario.

Thank you for any insights!

Answer №1

The GitHub issue Microsoft/TypeScript#18499 raises the importance of private members in determining compatibility. The main reason being: class private members are visible to other instances of the same class.

There is an insightful comment by @RyanCavanaugh in response to this issue:

Allowing private fields to be missing would lead to major problems, not just a minor soundness issue. For example:

class Identity {
  private id: string = "secret agent";
  public sameAs(other: Identity) {
    return this.id.toLowerCase() === other.id.toLowerCase();
  }
}
    
class MockIdentity implements Identity {
  public sameAs(other: Identity) { return false; }
}

MockIdentity is a public-compatible version of Identity, but attempting to use it as one will result in a crash in sameAs when a non-mocked copy interacts with a mocked copy.

Illustrating the potential failure:

const identity = new Identity();
const mockIdentity = new MockIdentity();
identity.sameAs(mockIdentity); // boom!

Therefore, there are valid reasons for this constraint.


To work around this limitation, you can extract only the public properties of a class using a mapped type like this:

type PublicPart<T> = {[K in keyof T]: T[K]}

Then, have B implement PublicPart<A> instead of A:

class A {
    private f() { console.log("f"); }
    public g() { console.log("G"); }
}

// works    
class B implements PublicPart<A> {
    public g() { console.log("g"); }
}

Hopefully, this explanation is beneficial. Best of luck!

Answer №2

The current Typescript-supported solution is as follows:

class X {
    private x() { console.log("x"); }
    public y() { console.log("Y"); }
}

class Y implements Pick<X, keyof X> {
    public y() { console.log("y"); }
}

Explanation: The use of keyof X in this example ensures that only the public properties (and methods) of X are returned. The Pick function then narrows down the scope of X to only its public properties and their respective type.

Answer №4

The reason behind this lies in the visibility of private members being restricted to the type rather than the instance. This means that all objects of type T can access the private members of other objects of the same type.

In nominatively typed languages, this is not a problem since all instances of T inherit the implementation of T. However, in structurally typed languages like TypeScript, we cannot assume that all instances fulfilling T have the same implementation as the class declaring type T.

As a result, private members must be included in the public contract of the type to prevent an object of the structural type T from calling a non-existent private member of another object with the same structural type.

Having private members as part of the public type contract is not ideal and could have been avoided by scoping privates to the instance rather than the type.

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

Creating web components with lit-element, leveraging rollup, postcss, and the tailwind framework for packaging

I have been attempting to package a functional web component that was developed using the lit-element/lit-html with the tailwind framework utilizing the postcss plugin from the rollup packager. Upon conducting a rollup, I discovered the compiled js and ht ...

What is the best way to extract information from a button and populate a form in AngularCLI?

I am currently attempting to enhance my Angular App by using a button to transfer information to a form upon clicking, rather than the traditional radio buttons or select dropdowns. My objective is to incorporate HTML content into the button (such as Mat-I ...

Using Typescript and webpack to detect variables that are defined in the browser but not in Node environment

My goal is to create a package that can be used on both servers and clients with minimal modifications required. Some libraries are available in Node but not in a browser, while others are accessible in a browser but not in Node. For instance, when utili ...

Bringing in the RangeValue type from Ant Design package

Currently working on updating the DatePicker component in Ant Design to use date-fns instead of Moment.js based on the provided documentation, which appears to be functioning correctly. The suggested steps include: import dateFnsGenerateConfig from ' ...

Title remains consistent | Angular 4

Struggling to change the document title on a specific route. The route is initially set with a default title. { path: 'artikel/:id/:slug', component: ArticleComponent, data: {title: 'Article', routeType: RouteType.ARTICLE, des ...

Order of execution for Angular 2 components

import { Component, OnInit } from '@angular/core'; import { FormGroup, FormControl, Validators, FormBuilder } from '@angular/forms'; import {Router, ActivatedRoute, Params} from '@angular/router'; import { Country } from &ap ...

Tips for sending data returned asynchronously from an Observable in Angular from a Child component to its Parent

I am facing a challenge passing Async data from child to parent component. When I try to access console.log(this.values) within the ngAfterViewInit() method in the parent component's HTML page load, it returns an empty value. However, upon clicking th ...

Unable to locate module src/ in Node.js TypeScript

I'm encountering issues with non-relative imports in my node.js / typescript application. Here is my tsconfig: { "compilerOptions": { "target": "es6", "module": "commonjs", "lib": ["dom", "es6", "es2017", "esnext.asynciterable"], "s ...

Deep Dive into TypeScript String Literal Types

Trying to find a solution for implementing TSDocs with a string literal type declaration in TypeScript. For instance: type InputType = /** Comment should also appear for num1 */ 'num1' | /** Would like the TSDoc to be visible for num2 as well ...

How to send Multipart form data with a string payload

Many suggestions in regards to this issue recommend utilizing some form of FormData within nodejs for building a multipart form. However, I am seeking to achieve the same result without relying on the FormData library. Instead, I aim to use only request h ...

What is the proper way to call document methods, like getElementByID, in a tsx file?

I am currently in the process of converting plain JavaScript files to TypeScript within a React application. However, I am facing an error with document when using methods like document.getElementById("login-username"). Can you guide me on how to referen ...

Using TypeScript: Functions incorporating properties

Recently, I made an interesting discovery in JavaScript: function foo() { console.log("FOO"); } foo.bar = "FOOBAR"; foo(); // logs "FOO" console.log(foo.bar); // "FOOBAR" This got me thinking: How would I repres ...

What is the reason for TypeScript not providing warnings for unrealistic conditions involving 'typeof' and 'in'?

The recent updates in version 4.9 highlighted the enhanced narrowing with 'in'. Intrigued by this, I decided to experiment with their example in a coding playground. Surprisingly, I discovered that seemingly impossible conditions involving typeof ...

The state object in Redux Toolkit remains static, resulting in the error message "Uncaught TypeError: Cannot assign to read only property 'property-name' of object '#<Object>'"

I'm facing an issue where I am trying to update the data of a state object in Redux Toolkit but it's not changing, and instead throwing an error that says Uncaught TypeError: Cannot assign to read only property 'status' of object ' ...

Best practices for safely handling dynamic keys in Typescript

I am searching for a secure method to create keyed objects in this manner: interface Types { RED: 'RED'; BLUE: 'BLUE'; GREEN: 'GREEN'; } type TypeKeys = keyof Types; const COLORS: Types = { RED: 'RED', B ...

Creating OpenAPI/Swagger documentation from TypeScript code

I am in search of a solution to automatically create OpenAPI/Swagger API definitions based on my Node.JS/Express.JS/Typescript code. It would be perfect if I could simply add annotations to my Express Typescript base controllers and have the OpenAPI/Swagg ...

Can the tiles in a grid-list be organized in a specific order?

I am facing an issue with a class named 'scenario' that has properties such as 'id', 'name', and 'number' among others. In the HTML, scenarios are displayed in this format: <mat-grid-list [cols]="breakpoint" r ...

Set up an event listener for when geolocation permission is approved

My Setup: I've written some basic code snippet below: const onSuccess = () => { console.log('success'); } const onError = () => { console.log('error'); } navigator.geolocation.getCurrentPosition(onSuccess, onError) ...

Creating a nested/child route structure within the Angular 2 router

Looking to implement nested routing for mypage/param1/1/param2/2 format in the URL. The first parameter is represented by 1 and the second one by 2. It's imperative that there are always two parameters, otherwise an error should be displayed. However, ...

incorporating my unique typographic styles into the MUI framework

I'm currently working on customizing the typography for my TypeScript Next.js project. Unfortunately, I am facing difficulties in configuring my code properly, which is causing it to not work as expected. Can someone kindly provide assistance or guida ...