Extending Partial Interface in a Typescript Class

My goal is to create a class that inherits all the properties of an interface, without explicitly declaring those properties itself. The interface properties are added during the build process and will be available at runtime.

After coming across this helpful post, I tried using Partial<T> but encountered issues. Despite the following code compiling without errors:

interface Animal {
    name: string;
}

interface HasConstructor {
    constructor: any;
}

type OptionalAnimal = Partial<Animal> & HasConstructor;

class Dog implements OptionalAnimal {
    public constructor() {

    }
    public breed: string;
}

The name property ends up missing on instances of Dog:

var spot = new Dog();
spot.name = "Spot"; //ERROR: Property 'name' does not exist on type 'Dog'

To work around this inconsistency, I had to define another type and reference it like so:

type AnimalDog = Dog & Animal;

var spot: Animal = new Dog() as any;
spot.name = "Spot";

This workaround, however, doesn't allow for creating new instances of AnimalDog properly, necessitating casting to any to align types. As a result, I'm forced to use both AnimalDog and Dog in various sections of my code, leading to compile errors within Dog when dealing with Animal types.

Is there a way in typescript to indicate that a class implements an interface without explicitly listing every interface property?

Answer №1

A common issue arises with the Partial<T> in TypeScript - it allows you to implement members without requiring you to do so. If a member is not implemented, it will simply not be present in the class.

One way to work around this is by creating a function that returns a class which implements the interface. This returned class does not need to declare any fields, leaving them all as undefined. This approach works since the fields are optional anyway.

interface Animal {
    name: string;
}

type OptionalAnimal = Partial<Animal>;
function autoImplement<T>(): new () => T {
    return class { } as any;
}
class Dog extends autoImplement<OptionalAnimal>() {
    public constructor() {
        super();
    }
    public breed: string;
}

var spot = new Dog();

spot.name = "Spot"; // Now it works

Another method is to cast the Dog class to ensure that the returned instance possesses the members of Animal. However, these additional members will not be accessible from within the class:

interface Animal {
    name: string;
}

class _Dog {
    public constructor() {

    }
    public breed: string;
}

const Dog = _Dog as { new(): _Dog & Partial<Animal> } & typeof _Dog
type Dog = InstanceType<typeof Dog>

var spot = new Dog();

spot.name = "Spot"; 

Answer №2

In my perspective, the current design appears to have a flaw. The fact that an Animal lacks a name should still classify it as an Animal. Utilizing OptionalAnimal seems redundant since you need to verify the presence of every interface field in all classes implementing OptionalAnimal.

To incorporate optional fields in an interface, consider using a typed union with undefined. This approach provides implementation guarantees from the interface while simplifying the check for optional fields.

To enhance type narrowing, I have included a type field in the interface:

interface Animal {
  type: string;
  name: string | undefined;
}

interface HasConstructor {
  constructor: any;
}

class Dog implements Animal, HasConstructor {
  public constructor() {}
  type = "Dog";
  public breed: string;
  public name: string = undefined;
}

// usage

var animals = new Array<Animal>();

var spot = new Dog();
spot.name = "Spot";
animals.push(spot);

animals.push(new Dog());
animals.push(new Dog());
animals.push(new Dog());

var pluto = new Dog();
pluto.name = "Pluto";
animals.push(pluto);

for (let animal of animals) {
  console.log("Animal:", animal.type, animal.name);
}

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

The data remains undefined even after being initialized in the constructor

My goal is to extract queryParams from a URL and leverage that information to resolve data in the following manner: data = { searchValue: null || undefined }; constructor(private http: HttpClient, private route: ActivatedRoute) { route.queryParams.su ...

Accepting undefined in rest parameter of typescript

I'm struggling with an exercise involving Function parameters: The maximum function below has the wrong type. To allow undefined in the rest arguments, you need to update the type of the rest parameter. Fortunately, you don't have to change the ...

What is the best way to extract a specific property from an object?

Consider the following function: getNewColor(): {} { return colors.red; } Along with this object: colors: any = { red: {primary: '#ad2121',secondary: '#FAE3E3'}, blue: {primary: '#1e90ff',secondary: '#D1E8FF&apos ...

Unable to exclude modules from ng-build in Angular CLI, the feature is not functioning as intended

I am managing an Angular CLI project that consists of two identical apps. However, one app requires the exclusion of a specific module in its build process. Key Details: Angular CLI Version: 1.7.4 Angular Version: 5.2.10 In the angular-cli.json ...

What is the best way to define a function that accepts an object with a specific key, while also allowing for any additional keys to be passed

Typescript allows us to define an interface for an object that must have a key and can also allow additional keys: interface ObjectWithTrace { trace: string; [index: string]: any } const traced: ObjectWithTrace = { trace: 'x', foo: 'bar ...

Receiving a conduit from the fuel supplier with only limited information on hand

I am designing a service that will utilize pipes as templates. In order to accomplish this, I require access to the registered pipes. The final code structure should resemble the following: @Injectable() class MyService { constructor(private injector ...

What is the correct way to utilize a class variable within the function() method?

Class Variable Name: addPointY Utilizing "addPointY" in a Function: setInterval(function () { var y = Math.round(Math.random() * 100); series.addPoint(this.addPointY, true, true); }, 3000); I am tasked with finding a solution for this. It is a c ...

Leverage Async Await for Setting Response Data in TypeScript

Currently, I am making multiple API requests with different data and storing all the responses in an array. Then, I am using .map to map the response array to my original array to update the data. However, it seems like the process is not working correctly ...

Extending a Svelte component with a P5JS class: A step-by-step guide

As a newcomer to SO, I haven't asked many questions before, so please bear with me if I don't entirely follow the guidelines. I'll do my best to explain the issue at hand. In my project, I am utilizing Sveltekit (create-svelte) and P5JS (p5 ...

I'm encountering an issue where my query parameter is not being accepted by the tRPC query request

I encountered an issue when I tried sending a request to http://localhost:5000/trpc/test?val=teststring using the minimal reproducible example below. The response message received was "Invalid input: undefined," indicating that the value 'val' is ...

Angular: Implementing a Dark and Light Mode Toggle with Bootstrap 4

Looking for suggestions on the most effective way to incorporate dark mode and light mode into my bootstrap 4 (scss) angular application. Since the Angular cli compiles scss files, I'm not keen on the traditional method of using separate css files for ...

Web performance issues noticed with Angular 8 and Webpack 3.7 rendering speed

My web application is currently taking 35 seconds to render or at least 1.15 seconds from the initial Webpack start. I've made efforts to optimize Webpack, which has improved the launch speed, but the majority of time is consumed after loading main.j ...

Discovering the current page using ons-navigator in Onsen UI

I am currently attempting to determine the page I am on while using the popPage() function with Onsen UI's ons-navigator. I have tried the following methods, but they always return false regardless: this.navigator.nativeElement.popPage().then((page: a ...

What is the process for invoking instance methods dynamically in TypeScript?

There is an object in my possession and the goal is to dynamically invoke a method on it. It would be ideal to have typechecking, although that may prove to be unattainable. Unfortunately, the current situation is that I cannot even get it to compile: ...

Unexpected token { in Fuse-Box when using Typescript

Here's the beginning of my fuse.ts file import { CSSPluginOptions } from 'fuse-box/plugins/stylesheet/CSSplugin'; import { argv } from 'yargs'; import * as path from 'path'; import { CSSPlugin, CSSResourcePlugin, Env ...

Angular - Enabling the next screen button only after completing multiple selections

Currently, I'm working on a screen where users can select multiple options from a table. The requirement is that they must select at least 3 options before they can proceed. However, I am facing difficulties in implementing this functionality and unsu ...

Error encountered when updating Angular CLI

I am currently attempting to update my Angular project from version 4 to version 6. After numerous failed attempts to upgrade, I decided to uninstall and reinstall the Angular CLI using 'npm uninstall -g angular-cli' followed by a reinstallation. ...

Ensuring TypeScript object types are safe by requiring all keys to be within an array of values

When working with Typescript ^3.8, we have an interface defined as follows: interface IEndpoint { method: 'get'|'put'|'post'|'patch'|'delete', path: string } Additionally, there is a constant declared like ...

Failed to execute start script 'ng serve' in npm start

Let me make it clear: I'm brand new to AngularJS and pretty much any Web Technology. I consider myself a novice in the realm of Web Development. I attempted to install AngularJS, and truth be told, after numerous "Mysterious Button Clicks", I might ...

Sending error messages from server to client (leveraging Express and Backbone)

I'm struggling with passing server error messages to a client after thrashing around for a while. Here's what I have on the server side (simplified): export function get(req: express.ExpressServerRequest, res: express.ExpressServerResponse) { ...