Struggling to create an Extension Method for Map<TKey, TValue[]> in TypeScript

As a new Angular/TypeScript user, I am really enjoying using Extension methods. They work well on standard types, but now I am facing an issue while trying to write one for the Map data structure where values are arrays. Unfortunately, it does not seem to be working as expected.

My goal with this extension method is to either initialize the array in the map and push a value into it or simply push the value if it already exists.

declare global {
    interface Map<TKey, TValue[]> {
        SetInArray(key:TKey, value:TValue):boolean;
    }
}

Map.prototype.SetInArray = function<TKey, TValue>(key:TKey, value:TValue):boolean {

    let isNew:boolean = false;

    if(this.has(key) == false){
        this.set(key, []);
        isNew = true;
    }

    let items:TValue[] = this.get(key);
    items.push(value);

    return isNew;
};

export {};

I would appreciate any advice or suggestions regarding this issue.

Thank you!

Answer №1

One issue to consider (besides the common advice not to extend native object prototypes) is that when defining a generic interface or type, the type parameter must be a plain name and cannot be based on another type. For example:

interface Foo<T, U[]> {} // error

If you need the second type parameter to be an array, you can use a generic constraint instead:

interface Foo<T, U extends any[]> {} // okay

Another challenge arises when attempting to merge declarations of generic types if altering the type parameters or constraints. You are restricted from changing predefined types like Map<K, V>, resulting in having to maintain it as is.

Introducing a new method that requires V to be an array poses unique constraints within your declaration which might seem limiting. However, utilizing a TypeScript feature known as a this parameter offers a viable solution. By adding this parameter at the start of the function or method signature, you can enforce proper usage without risking invalid calls.

An example implementation to clarify:

// no error
declare global {
  interface Map<K, V> {
    SetInArray<V>(this: Map<K, V[]>, key: K, value: V): boolean;
  }
}

Map.prototype.SetInArray = function <K, V>(this: Map<K, V[]>, key: K, value: V): boolean {
  let isNew: boolean = false;
  if (this.has(key) == false) {
    this.set(key, []);
    isNew = true;
  }
  let items: V[] = this.get(key)!;
  items.push(value);
  return isNew;
};

A demonstration follows:

const numberArrayMap = new Map<string, number[]>();
numberArrayMap.set("a", [1, 2, 3])
numberArrayMap.SetInArray("a", 4); // okay

const numberMap = new Map<string, number>();
numberMap.set("a", 4)
numberMap.SetInArray("a", 4); // error
// The 'this' context of type 'Map<string, number>' is not assignable 
// to method's 'this' of type 'Map<string, number[]>'. 
// Type 'number' is not assignable to type 'number[]'.

This behavior provides the desired functionality where you can call SetInArray() on numberArrayMap, but not on

numberMap</code due to mismatching <code>this
contexts.

Hopefully, this information proves helpful for your endeavors. Best of luck!

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

Specify the return type based on specific parameter value

I'm facing a situation where I have two definitions that are identical, but I need them to behave differently based on the value of the limit parameter. Specifically, I want the first definition to return Promise<Cursor<T>> when limit is g ...

Customizing a side menu by assigning a function to a specific option

Hello there! I am a newcomer to TypeScript and Ionic. I am trying to implement a function that clears the cart when the "Mercado" option in the side menu is clicked, but I am struggling to retrieve the page data. The code snippet below shows my attempt to ...

Encountering a ng-select2 Error with Angular version 4.1.3

I have recently installed the ng-select2 package, but I encountered an error when trying to compile my code using 'ng serve'. Node version: 8.10.0 NPM version: 6.0.0 Another list item Operating System: Windows 7 ERROR in d:/PATH-TO-PROJECT-F ...

Can you explain the process of the assignment part in the code line of Angular2?

I’ve been delving into the angular2-rxjs-chat project on GitHub to enhance my knowledge. Within the code linked here, there is a specific line of code that caught my attention: threads[message.thread.id] = threads[message.thread.id] || message.thread; ...

Is there a way to close a window in JavaScript that was opened using Ionic Capacitor?

Currently, I am trying to open a window within my Ionic app by using the code snippet below: await Browser.open({url: environment.apiUrl + `/myurl`}); However, upon completion of a certain action by the user, I want to close that same window. Unfortunate ...

What could be causing my component to not refresh when used as a child?

I have been experimenting with some code to track rerenders. The initial approach failed when passing <MyComponent> as a child component. it("should return the same object after parent component rerenders", async () => { jest.useF ...

Displaying search results in various Angular components

On my home page (homePageComponent), I have a search feature. When the user clicks on the search button, they are redirected to a different page called the search list page (searchListComponent). Within the searchListComponent, there is another component c ...

The function fromEvent is throwing an error stating that the property does not exist on the type 'Event'

I'm attempting to adjust the position of an element based on the cursor's location. Here is the code I am currently using: this.ngZone.runOutsideAngular(() => { fromEvent(window, 'mousemove').pipe( filter(() => this.hove ...

Use an if statement in Angular to conditionally add a title attribute with an empty value to HTML

How can I conditionally add the title attribute to a div without creating another div and using ngIf? If the permission is true, I want to include a title in my div. Here's what my current div looks like: <div (click)="goToChangelog()&quo ...

What is the reason behind Angular FormControl applying the 'disabled' attribute in the DOM but not the 'required' attribute?

After transitioning my form logic from the template to FormGroup & FormControl objects, I noticed that when a FormControl is disabled in Angular, the 'disabled' attribute for the field is automatically updated in the DOM. However, when I modi ...

Creating a CSS animation to slide a div outside of its container is

I currently have a flexbox set up with two adjacent divs labeled as DIV 1 and DIV 2. By default, both DIV 1 and DIV 2 are visible. DIV 2 has a fixed width, occupying around 40% of the container's width. DIV 1 dynamically adjusts its width to ac ...

Unveiling the Swiper Instance with getSwiper in Ionic5 and Angular

Currently, I am integrating ion-slides into my Ionic 5 project built with Angular. In accordance with the instructions provided in this documentation, I aim to retrieve the Swiper instance by utilizing the getSwiper method so that I can leverage the functi ...

After the initialization of the app, make sure to provide an InjectionToken that includes the resolved configuration

During the initialization of the application, I am looking to retrieve the configuration using a factory that will be resolved through the APP_INITIALIZER provider. export function loadConfig(): () => Promise<Config> { // return promised confi ...

Trigger a class method in an event using Angular with Typescript

I am completely new to TypeScript and Angular, and I am attempting to create a basic drawing component on a canvas. However, I have reached a point where I feel lost and confused about my code. The concept of "this" in TypeScript has been a major stumbling ...

An essential aspect of utilizing ngrx is understanding how to access the previous and current state to effectively compare them when subscribing to the store

Within my component, I am subscribing to the ngrx store for changes in a specific state. I need to implement a condition where if the current state is different from the previous state, then I should perform certain actions. Is there a way to retrieve the ...

Guide on importing SVG files dynamically from a web service and displaying them directly inline

With an extensive collection of SVG files on hand, my goal is to render them inline. Utilizing create-react-app, which comes equipped with @svgr/webpack, I am able to seamlessly add SVG inline as shown below: import { ReactComponent as SvgImage } from &apo ...

Optimizing Angular 2+ for Search Engine Crawlers

My angular 4+ web application has a unique header for each route, with all components loading through Angular code which mainly consists of JavaScript. This setup is causing Google to be unable to crawl the links effectively, impacting SEO. If I were to ad ...

I am facing an issue with TypeScript as it is preventing me from passing the prop in React and Zustand

interface ArticuloCompra { id: string; cantidad: number; titulo: string; precio: number; descuento: number; descripcion: string; imagen: string; } const enviarComprasUsuarios = ({ grupos, }: { grupos: { [key: string]: ArticuloCompra & ...

What is the reason behind Typescript flagging a potential undefined value when checking for array length using a greater than comparison but not with an

Consider the following excerpt from a React component: const AccountInformation = (props: { readonly accountData: AccountData | undefined | null }) => { const hasMultipleAccounts: boolean = props.accountData?.customerAccounts?.length === 1 ? false : t ...

The HttpClient.get('/') request with {observe: 'response'} is failing to retrieve some headers

Currently, I'm in the process of writing an API GET request by utilizing HttpClient.get(). Upon passing in the option to observe the response, I've encountered an issue where accessing the .keys() does not provide me with any headers apart from C ...