What is the best way to design functions that can return a combination of explicit types and implicit types?

When looking at the code provided below,

function system(): ISavable & ISerializable {
    return {
        num: 1, // error!

        save() {},
        load() {},

        serialize() {},
        deserialize() {},
    }
}

interface ISavable {
    save: () => void
    load: () => void
}

interface ISerializable {
    seriailze: () => void
    deserialize: () => void
}

Link to Playground →

The Issue

There is an error in returning property num: 1 as TypeScript indicates it is not specified by either ISavable or ISerializable.

To address this, one potential solution could involve creating an additional type that encompasses all the elements the function will return but I am exploring a way to maintain flexibility for unique properties within the function.

To clarify further, when you do not define the return of your function explicitly, its type is inferred and you benefit from autocomplete suggestions. I desire this functionality along with the ability to infer types and ensure specific properties are included in the return value.

Query

I am interested in typing the return of my function in such a manner where it mandates the inclusion of properties from both interfaces (ISavable and ISerializable) while still permitting the addition of extra custom properties relevant to the function.

With that in mind,

  1. Is this achievable?
  2. If so, how can it be done?

Although classes might fulfill my requirements, I am specifically seeking a solution tailored for functions.

Answer №1

Here is an alternative approach:

The concept of creating objects that fulfill a specific type while also retaining their own literal type is not groundbreaking. There is already a PR proposing the addition of a satisfies operator to TypeScript. In the meantime, we can simulate our own version of this "operator."

const satisfies = <T,>() => <S extends T>(arg: S) => arg

This generic function can be utilized within the context of system.

function system() {
    return satisfies<ISavable & ISerializable>()({
        num1: 1,
        num2: 2,

        save() {},
        load() {},

        serialize() {},
        deserialize() {},
    })
}

system().num1
system().num2

The satisfies function guarantees that the constraint is met, allowing for extra properties and making them observable in the return type.

Playground

Answer №2

Just thought I'd mention that the most elegant solution to this problem was actually discovered by the original poster, inspired in part by a response I had initially rushed to share but ultimately decided was inadequate. Interestingly enough, their solution was initially shared as a reply to my first answer, just before Tobias came through with an excellent solution.

function system() {
    const ret: ISavable & ISerializable = {
        save() {},
        load() {},

        serialize() {},
        deserialize() {},
    }

    return { num: 100, ...ret }
}

interface ISavable {
    save: () => void
    load: () => void
}

interface ISerializable {
    serialize: () => void
    deserialize: () => void
}

system().num

After being inspired by Tobias' solution, I set out to refine it further with the aim of:

  • ensuring zero impact on the resulting javascript code,
  • improving TypeScript's error messaging when the requirement for ISavable & ISerializable extension is not met.

The version I settled on is as follows:

type ExtendsP<I, T extends I> = T

function system(){
    return {
        num: 100,

        save() {},
        load() {},

        serialize() {},
        deserialize() {},
    };
}

type _ = ExtendsP<() => ISavable & ISerializable, typeof system>

interface ISavable {
    save: () => void
    load: () => void
}

interface ISerializable {
    serialize: () => void
    deserialize: () => void
}

system().num

Admittedly, it may seem a bit awkward and could potentially trigger a linter warning due to the lack of utilization of type _ (as many TypeScript linters might not be familiar with its usage convention), but I hope it offers some insight for those facing similar challenges.

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

How to Utilize an Array from Observable Object in Angular 2 with ngFor and Async Pipe

I am currently learning about the utilization of Observables in Angular 2. Here is a snippet of code from a service I am working with: import {Injectable, EventEmitter, ViewChild} from '@angular/core'; import {Observable} from "rxjs/Observab ...

Using the increment operator within a for loop in JavaScript

this code snippet causes an endless loop for (let i = 0; ++i;) { console.log(i) } the one that follows doesn't even run, why is that? for (let i = 0; i++;) { console.log(i) } I want a thorough understanding of this concept ...

Issue during deployment: The type 'MiniCssExtractPlugin' cannot be assigned to the parameter type 'Plugin'

I'm working on deploying a Typescript / React project and have completed the necessary steps so far: Created a deployment branch Installed gh-pages for running the deployed application Added a deploy command as a script in the package.j ...

What is the generic type that can be used for the arguments in

One function I've been working on is called time const time = <T>(fn: (...args: any[]) => Promise<T>, ...args: any[]): Promise<T> => { return new Promise(async (resolve, reject) => { const timer = setTimeout(() => r ...

Refreshing the page does not trigger Angular callHooks

Encountering an issue with the proper invocation of F5 button or directive call URL from the address bar resulting in ngOnInit not being triggered correctly. Using a debugger to analyze the situation, it was noticed that callHook is not initiated after ngO ...

Adding Components Dynamically to Angular Parent Dashboard: A Step-by-Step Guide

I have a dynamic dashboard of cards that I created using the ng generate @angular/material:material-dashboard command. The cards in the dashboard are structured like this: <div class="grid-container"> <h1 class="mat-h1">Dashboard</h1> ...

Typescript Code Coverage with karma-jasmine and istanbul: A complete guide

I am attempting to calculate the Code Coverage for my typescript Code in karma framework using Istanbul. In the karma.conf file, typescript files are added and through karma typescript-preprocessor we are able to conduct unit testing and code coverage of t ...

Enhance Angular Forms: Style Readonly Fields in Grey and Validate Data Retrieval using Typescript

Is there a way to disable a form and make it greyed out, while still being able to access the values? 1/ Disabling controls with this.roleForm.controls['id'].disable() in Typescript does not allow me to retrieve the values of Id, Name, and Descr ...

The guidelines specified in the root `.eslintrc.json` file of an NX workspace do not carry over to the project-level `.eslintrc.json` file

In my main .eslintrc.json file, I have set up some rules. This file contains: { "root": true, "ignorePatterns": ["**/*"], "plugins": ["@nrwl/nx", "react", "@typescript-eslint", &qu ...

Tips for embedding HTML/CSS snippets in backticks when using TypeScript with AngularJS

Does anyone else experience the issue of their Angular 2 templates showing up as gray text in Visual Studio Code? I'm unable to use autocomplete or see my CSS properly. Is this a settings problem or is there a plugin that can solve this? BTW, I am us ...

How to retrieve the parent activated route in Angular 2

My route structure includes parent and child routes as shown below: { path: 'dashboard', children: [{ path: '', canActivate: [CanActivateAuthGuard], component: DashboardComponent }, { path: & ...

Encountered an issue while using OpenAPI 3.1 with openapi-generator-cli typescript-fetch. Error: JsonParseException - The token 'openapi' was not recognized, expected JSON String

I am interested in creating a TypeScript-fetch client using openapi-generator-cli. The specifications were produced by Stoplight following the OpenAPI 3.1 format. However, when I execute the command openapi-generator-cli generate -i resources/openapi/Attri ...

How to iterate through properties declared in an Interface in Angular 12?

Before Angular 12, this functioned properly: export interface Content { categories: string[] concepts: Topic[] formulas: Topic[] guides: Topic[] } //this.content is of type Content ['formulas', 'concepts'].forEach(c =&g ...

Encountering issue with FullCalendar and Angular 11: Error reading property '__k' of null

I am currently utilizing the Full Calendar plugin with Angular 11 but have encountered an error message stating "Cannot read property '__k' of null". It appears to be occurring when the calendar.render() function is called, and I'm strugglin ...

Visual Studio - Error TS1005 'Unexpected token'

After spending nearly 5 hours scouring the internet for a solution, I am still unable to resolve this persistent issue. The responses I've found so far do not seem to address the specific problem I'm facing. Although I have upgraded the tsc vers ...

What could be the reason for receiving an HttpErrorResponse when making a GET request that returns byte data

When using these headers, the API returns byte data as a response. let headers = { headers: new HttpHeaders({ 'Content-Type': 'application/octet-stream', 'responseType':'arraybuffer' as 'js ...

The error message displayed by Create React App states: "You cannot utilize JSX without the '--jsx' flag."

I need help with overcoming this particular issue in a TypeScript based React application: Encountering an error stating "Cannot use JSX unless the '--jsx' flag is provided" ...

Sentry: Easily upload source maps from a nested directory structure

I am currently developing a NextJs/ReactJs application using Typescript and I am facing an issue with uploading sourcemaps to Sentry artefacts. Unlike traditional builds, the output folder structure of this app mirrors the NextJs pages structure, creating ...

The export 'ChartObject' is not available in highcharts

Trying to integrate highcharts into my Angular 4 project has been a bit challenging as I keep encountering the following error: highcharts has no exported member 'ChartObject' I have experimented with different options such as angular-highchart ...

Is there a way to combine multiple array objects by comparing just one distinct element?

Is there a way to combine these two arrays into one? array1 = [ { image: 'image1', title: 'title1' }, { image: 'image2', title: 'title2' }, { image: 'image3', title: 'title3' }, ]; array2 = ...