Ensuring external library object properties are limited in Typescript

Trying to utilize the notify function from an external library has been a bit challenging.

The declaration in the library is as follows:

// library.js
export declare const notify: {
    (args: NotificationsOptions | string): void;
    close(id: unknown): void;
};

Now the goal is to call the function with arguments that extend the original ones, like so:

// myFile.js
import { notify } from 'library';

const originalArgs = { /* object of type NotificationsOptions */ };
notify(originalArgs); // this works
notify({ ...originalArgs, additionalProperty: 'Foo' }); // results in TypeScript error message "Argument type {..., additionalProperty: string } is not assignable to parameter type NotificationsOptions | string"

While the issue is clear, finding the best practice to solve it remains a challenge.

A few potential solutions have been considered:

// myFile.js

// 1. Typecasting using "as" notation:
import { notify } from 'library';
notify({ ...originalArgs, additionalProperty: 'Foo' } as NotificationOptions); // works, but feels unclean since it's not truly of this type?

// 2. Disabling inspection with ts-ignore
// It works, but is it really the cleanest solution?

// 3. Disabling inspection by passing in as a predefined constant:
import { notify } from 'library';
const extendedArgs = { ...originalArgs, additionalProperty: 'Foo' };
notify(extendedArgs); // works, but not ideal since the check is only implicitly disabled

// 4. Redeclaring notify:
import { notify } from 'library';
declare const notify: {
  (args: NotificationOptions & { additionalProperty: string}): void;
}; // Results in an error "Import declaration conflicts with local declaration of 'notify'
notify({ ...originalArgs, additionalProperty: 'Foo' });

// 5. Avoid using import
declare const notify: {
  (args: NotificationOptions & { additionalProperty: string}): void;
}; 
notify({ ...originalArgs, additionalProperty: 'Foo' }); // No TypeScript error, but runtime error "notify is not defined"

// 6. Declare extendedNotify
// This might be the preferred solution, but figuring out how to implement it is a challenge
import { notify } from 'library';
declare const extendedNotify: {
  (args: NotificationOptions & { additionalProperty: string }): void;
};
notify({ ...originalArgs, additionalProperty: 'Foo' }) as extendedNotify; // Resolved type extendedNotify

Answer №1

Excess property checking serves as a cautionary feature, flagging potential errors rather than ensuring type safety to prevent runtime issues; TypeScript object types do not have a sealed or "exact" nature, allowing for the addition of properties without violating the type.

This implies that

NotificationsOptions & { additionalProperty: string }
can be assigned to NotificationOptions, meaning any function accepting NotificationOptions should also accept
NotificationsOptions & { additionalProperty: string }
seamlessly. Essentially, this indicates that the function notify is already of the correct type, but the desire is to suppress excess property complaints when passing in additionalProperty.


One method to achieve this is by assigning notify to a variable named extendedNotify, which has been explicitly annotated with the desired type:

const extendedNotify: (
  args: (NotificationsOptions & { additionalProperty: string })
) => void = notify; // okay

This assignment is successful because notify accepts NotificationsOptions and hence can safely handle any subtype of NotificationsOptions, including NotificationsOptions & {...}.

Subsequently, you can call extendedNotify() as needed:

extendedNotify({ ...originalArgs, additionalProperty: 'Foo' }); // okay

No excess property warnings will arise since the property is recognized within the parameter type of extendedNotify().

Accessible link to code playground

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

Converting an array of objects to an array of JSON objects in TypeScript

My dilemma lies in the data I have uploaded under the _attachments variable: https://i.sstatic.net/jnFNH.png My aim is to format this data for insertion in the following structure: "_attachments": [ { "container": "string", "fileName": "string" ...

What is the method for including a placeholder (instead of a label) in the MUI 5 DatePicker component?

I'm looking to customize the placeholder text in MUI 5's date picker. You can find the MUI 5 datepickerlink here: https://mui.com/x/react-date-pickers/date-picker/ The desired outcome:: I've tried referring to this chat, but it hasn't ...

Guide on toggling mat-checkbox according to API feedback in Angular 6

Just starting out with angular 6 and I'm attempting to toggle the mat-checkbox based on the API response. However, I seem to be having trouble. All the checkboxes are showing as checked even when the API response is false. <div class="col-sm-12" ...

The error "Property 'push' does not exist on type '() => void'" occurs with Angular2 and Typescript arrays

What is the method to initialize an empty array in TypeScript? array: any[]; //To add an item to the array when there is a change updateArray(){ this.array.push('item'); } Error TS2339: Property 'push' does not exist on type &a ...

Having trouble with Angular 2's Output/emit() function not functioning properly

Struggling to understand why I am unable to send or receive some data. The toggleNavigation() function is triggering, but unsure if the .emit() method is actually functioning as intended. My end goal is to collapse and expand the navigation menu, but for ...

How to extract a value from a BehaviorSubject within an Angular 6 guard

I have chosen this approach because I have another guard responsible for validating the user based on a token that was previously stored. This is how it was handled in previous versions of rxjs, but now with the latest version you can no longer use map on ...

What is causing my React-Testing Library queries to not work at all?

In my current project, I am using Jest along with Testing-Library to create UI unit tests. One issue that I encountered was that the components were not rendering on the DOM. After some investigation, I found that the main culprit was a component called & ...

Trigger a dispatched action within an NGRX selector

I want to ensure that the data in the store is both loaded and matches the router parameters. Since the router serves as the "source of truth," I plan on sending an action to fetch the data if it hasn't been loaded yet. Is it acceptable to perform the ...

Discover the highest value within an array of objects, along with any numerical object attributes that have a value greater than zero

Considering an array of objects structured as follows: [{ "202201": { "WO": 900, "WS": 0, "SY": 0.915, "LY": 0.98, "CT": 75 }, "202202" ...

How can I make the snackbar open multiple times in a row?

Check out this codesandbox I created to show an issue. When you click the button, a MUI snackbar opens. However, if you close it and try to reopen it, nothing happens. Do you think the problem is related to how I'm using hooks? Explore the sandbox h ...

Create a TypeScript class object with specified constructor arguments

I've been working on a function that is supposed to execute the init method of a class and then return an instance of that class. However, I'm running into issues with maintaining the constructor and class types. This is what I have tried so far ...

show additional worth on the console

Just starting out with JavaScript. Trying to display additional values in the console. Uncertain about how to access add-ons. Can anyone help me troubleshoot? Here is my code snippet below: https://jsfiddle.net/6f8upe80/ private sports: any = { ...

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 ...

Issue with `rxjs/Subject.d.ts`: The class `Subject<T>` is incorrectly extending the base class `Observable<T>`

I found a sample template code in this helpful tutorial and followed these two steps to get started - npm install // everything went smoothly, created node_modules folder with all dependencies npm start // encountered an error as shown below- node_mo ...

The ngOnInit lifecycle hook is not triggered by the Angular routerLink

In the component.ts file, you will find the ngOnInit function as shown below: ngOnInit() { this.locationService.getLocation().subscribe( locations => { this.locations = locations; }); } <a [routerLink]="['/locations-list&apo ...

What are the steps to update your profile picture using Angular?

In my Angular 6 application, I am implementing an image upload feature with the following code: Html: <img [src]="url ? url : 'https://www.w3schools.com/howto/img_avatar.png'"> <br/> <input type='file' (change)="onSelec ...

Mastering Typescript Inversify: The Ultimate Guide to Binding Interfaces with Type Parameters

I am trying to figure out how to bind an interface with a type parameter, but I am unsure of the correct way to do it. Here is the Interface: ... export interface ITestHelper<Entity extends ObjectLiteral> { doSomething(builder: SelectQueryBuilder& ...

Angular is patiently awaiting the completion of the subscription

Currently, I am in the process of developing a test application using Angular. The challenge arises when I attempt to retrieve data through a Get request and then return a value based on that data. The code snippet below outlines the scenario: public getN ...

Converting an array into an object using Typescript and Angular

I have a service that connects to a backend API and receives data in the form of comma-separated lines of text. These lines represent attributes in a TypeScript class I've defined called TopTalker: export class TopTalker { constructor( pu ...

Template specific requirements in Angular

When I have a child component app-page-header, here's what I want to achieve: If the value of headerOptions.headerTitle is not 'Idle Disposition', then set [titleData] to equal 'headerOptions.headerTitle'. However, if headerOptions ...