When merging interfaces and classes, Typescript does not verify property initialization

When creating a class like the following:

class Dog {
    a: string;
    b: string;
    c: string;
}

The TypeScript compiler will throw an error stating that properties a, b, and c are not initialized. However, if we take a different approach like this:

interface Animal {
    a: string;
    b: string;
}

interface Dog extends Animal {
    c: string;
}

class Dog {
    constructor() {}
}

We notice that the compiler does not raise any concerns about uninitialized properties. One might expect the warning to be consistent with the first snippet where properties were not initialized in the merged interface. But why does the compiler behave differently in this case?

Answer №1

My interpretation of the query is as follows:

When merging an interface declaration into the instance side of a class declaration, why doesn't the compiler issue a warning when the properties declared in the interface are not initialized in the class? This happens even when the --strictPropertyInitialization compiler option is turned on.

Here's the answer:

The purpose of declaration merging into a class is to extend an existing class externally. Generally, if you add a new property or method to the class interface from outside, you should also initialize that property or method externally. You can start with the original class declaration:

class Dog {
    constructor() { }
}    

Then, externally augment the interface and implement the added properties like so:

interface Animal {
    a: string;
    b: string;
}
interface Dog extends Animal {
    c: string;
}

// implementation
Dog.prototype.a = "defaultA";
Dog.prototype.b = "defaultB";
Dog.prototype.c = "defaultC";

If you follow this approach, everything will function as expected:

const d = new Dog();
console.log(d.b); // "defaultB"
d.b = "newB";
console.log(d.b); // "newB"

On the other hand, the --strictPropertyInitialization compiler option is specifically designed to ensure that properties declared within the class itself are properly initialized. This serves as a separate use case from declaration merging; any properties that must be initialized within the class body should be declared there as well.


That sums up the response to the initial question. It appears that there is a deeper requirement to create a class constructor from an interface without re-declaring the properties in the class. In such cases, using declaration merging might not provide the desired solution (at least in TypeScript 4.9).

There are alternative methods available; for example, I sometimes utilize what I refer to as an "assigning constructor" factory that needs to be implemented only once:

function AssignCtor<T extends object>(): new (init: T) => T {
    return class { constructor(init: any) { Object.assign(this, init); } } as any;

You can then employ this factory to generate class constructors like this:

interface IDog {
    a: string;
    b: string;
    c: string;
}
class Dog extends AssignCtor<IDog>() {
    bark() {
        console.log("I SAY " + this.a + " " + this.b + " " + this.c + "!")
    }
};

const d = new Dog({ a: "a", b: "b", c: "c" });
d.bark(); // I SAY a b c!

This may or may not address your specific requirements, and it goes beyond the scope of the original question. However, the key point is that exploring options other than declaration merging might be more beneficial in addressing your particular situation.

Playground link to code

Answer №2

Just like jcalz pointed out, TypeScript does not currently validate initialized properties with merged declarations. This means the only way to guarantee property initialization is by declaring them directly within the class as shown below:

class Cat implements Animal {
    x: string;
    y: string;
    z: string;

    constructor() {
        this.x = 'x';
        this.y = 'y';
        this.z = 'z';
    }
}

Despite this workaround, I believe there should be a better solution and have raised an issue on the TypeScript repository. You can view it here: https://github.com/microsoft/TypeScript/issues/52279

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

Angular's counterpart to IWebProxy

When using C#, I am able to: public static IWebProxy GetWebProxy() { var proxyUrl = Environment.GetEnvironmentVariable("HTTPS_PROXY"); if (!string.IsNullOrEmpty(proxyUrl)) { var proxy = new WebProxy { Address = new Ur ...

Enhance a subject's behavior by overriding the .next method using a decorator

Currently, I am working on an Angular application where I have numerous Subjects, BehaviorSubjects, and ReplaySubjects as properties in various services. I am attempting to create a TypeScript decorator that can be added to some of these Subjects to enhanc ...

Adonisjs latest version (v5) model creation command malfunctioning

Using Adonisjs v5 The controller command works fine with: node ace make:controller Posts However, the new model creation command is not working: node ace:make model Post When running the make model command, an error occurs: An error message stating &ap ...

Is it possible for me to use ts files just like I use js files in the same manner?

So I recently stumbled upon TypeScript and found it intriguing, especially since I enjoy adding annotations in my code. The only downside is that I would have to change all my .js files to .ts files in order to fully utilize TypeScript's capabilities. ...

Combining the namespace and variable declarations into a single statement

Currently, I am facing an issue with creating a declaration file for the third-party library called node-tap. The main challenge lies in properly declaring types for the library. // node_modules/a/index.js function A() { /* ... */ } module.exports = new A ...

Excluding a common attribute from a combined type of objects can lead to issues when accessing non-common attributes (TypeScript)

In the process of developing a wrapper function, I am passing a refs property into a send function. The Event type used to construct my state machine is defined as an intersection between a base interface { refs: NodeRefs } and a union of possible event ob ...

Unable to pass a component property to a styled Material-UI Button

I have customized a MUI Button: const SecondaryButton = styled(Button)<ButtonProps>(({ theme }) => ({ ... })); export default SecondaryButton; When I try to use it like this: <label htmlFor="contained-button-file"> <input ...

Mastering the integration of NestJS with Redis for microservices

Currently, I am diving into the world of nestjs microservices. I am curious, what commands are available for me to use? const pattern = { cmd: 'get' }; this.client.send<any>(pattern, data) Additionally, how do I go about retrieving data ...

Creating Angular UI states with parameters in TypeScript

My Understanding In my experience with TypeScript and angular's ui state, I have utilized "type assertion" through the UI-Router definitely typed library. By injecting $state into my code as shown below: function myCtrl($state: ng.ui.IStateService){ ...

What about combining a fat arrow function with a nested decorator?

Trying to implement a fat arrow function with a nestjs decorator in a controller. Can it be done in the following way : @Controller() export class AppController { @Get() findAll = (): string => 'This is coming from a fat arrow !'; } Wh ...

Searching for data based on specific keywords in Angular 2, rather than using a wildcard search, can be done by utilizing the key-in

My dropdown contains 100 values, and I am currently able to search for these values based on key input using wild search. However, I would like the dropdown to display values based on the specific alphabet that I enter first. HTML: <div class="col- ...

Error Message: Angular 5 - Unable to bind to 'ngModel' as it is not recognized as a valid property of 'input'

I am currently working on an Angular 5 online tutorial using Visual Studio Code and have the following versions: Angular CLI: 7.0.6 Node: 10.7.0 Angular: 7.0.4, Despite not encountering any errors in Visual Studio Code, I am receiving an error in ...

What is the best way to anticipate a formal announcement?

Hey there! I'm trying to set options for my Datatable and add a new field in my objects, but I need to await the completion of these dtOptions. How can I achieve this in the ngOnInit lifecycle hook? export class MyDashboardComponent implements OnInit ...

Tailoring Aurelia for .cshtml integration

I stumbled upon an informative article detailing the integration of Razor partials (cshtml) with aurelia. Despite my efforts, I encountered difficulty in getting the code to execute properly and was informed by Rob Eisenberg's comment that Convention ...

Updating an item in the redux state is triggering a never-ending loop, leading to a browser

EXPECTED OUTCOME: My goal is to modify a value in my redux state ISSUE: I am encountering an issue where there is an infinite loop or the browser gets locked down. Despite consulting this Stack Overflow post and the official documentation, I am struggling ...

Struggling to locate a declaration file for the 'cloudinary-react' module? Consider running `npm i --save-dev @types/cloudinary-react` or exploring other options to resolve this issue

Currently, I am working with Typescript in React. Strangely, when I try to import the following: import { Image } from 'cloudinary-react'; I encounter this error: Could not find a declaration file for module 'cloudinary-react'. ' ...

Error occurs because the declaration for `exports` is missing in the compiled TypeScript code

I am currently venturing into the world of typescript and I've encountered a problem while attempting to run my application. An error is popping up, stating ReferenceError: exports is not defined The code I have written is rather straightforward: / ...

Creating a personal TypeScript language service extension in Visual Studio Code

Currently, I am working on developing a TSserver plugin in VSCode and struggling to get the server to load my plugin. I have experimented with setting the path in tsconfig.json to both a local path and a path to node_modules, but it still isn't worki ...

Unable to locate module: Issue: Unable to locate '@angular/cdk/tree' or '@angular/material/tree'

Currently utilizing Angular 5 and attempting to create a tree view that resembles a table layout. https://stackblitz.com/edit/angular-hhkrr1?file=main.ts Encountering errors while trying to import: import {NestedTreeControl} from '@angular/cdk/tree ...

Opt for a library exclusively designed for TypeScript

I am attempting to develop and utilize a TypeScript-only library without a "dist" folder containing compiled ".js" files. Here is the structure of my simple library: src/test/index.ts: export const test = 42; src/index.ts: import {test} from "./test"; ...