Is it possible to create an object by utilizing a generic type?

Currently, I am dealing with a public RESTful API that provides objects containing URL fields leading to additional information. I wanted to encapsulate these fields within an object indicating their purpose so I devised the following structure:

class APIResource<T> {
    url: string;
}

In this setup, T specifies the expected return from the url. Although not utilized by TypeScript, T serves as a reference for what to anticipate. While my work revolves around Angular2, it's important to note that this concept is not Angular-exclusive. My approach involves an API class where I can request a URL and specify a type for conversion:

class Person {
  name: string;
  age: number;

  constructor(obj: any) {
    // all Constructable<T> classes will
    // share a constructor structured in 
    // the same way
    Object.assign(this, obj);
  }

  print() {
    console.log(this.name + ' is ' + this.age);
  }
}

interface Constructable<T> {
  new (obj: any): T;
}

public get<T>(url: string, type: Constructable<T>): Observable<T> {
  return this.http.get(url).map(result => {
    return new type(result.json());
  });
}

The process entails making an AJAX call to retrieve JSON data from the API, then passing it through the designated constructor before returning the object. To implement this, you would execute the following:

var person: Person;
api.get('/person/1/', Person).subscribe(p => person = p);

While this works when a specific constructor is provided, my goal is to create a

getResource<T>(resource: APIResource<T>): Observable<T>
function to enable operations like the one below:

var res: APIResource<Person> = { url: '/person/1/' };
var person: Person;
api.getResource(res).subscribe(p => person = p);

Unfortunately, my attempts thus far have been unsuccessful. I am facing challenges accessing the type T from APIResource to integrate it into my existing method. Various modifications to APIResource were attempted:

class APIResource<T> {
  url: string;

  getType(): T {
    return T;
  }
}

And also:

class APIResource<T extends Constructable<T>> {
  url: string;

  fetch(): T {
    var obj: any = {};
    return new T(obj);
  }
}

However, neither of these adjustments proved successful. I understand that TypeScript loses the type information once it transitions to JavaScript. Instead of moving the type around, I believe manipulating the constructor might be feasible, although challenging with generics.

Is there a solution to making this work effectively with generics?

Note: Currently utilizing typescript 1.8.7

Answer №1

Below is a TypeScript implementation using generics for maintaining strong typing:

interface Constructable<T> {
    ctor: new (...args) => T;
    url: string;
}

class APIResource<T> implements Constructable<T> {
    constructor(
        public ctor: new (...args) => T,
        public url: string
    ) {}
}

class Person {
    name: string;
    age: number;

    constructor(obj: any) {
        //Object.assign(this, obj); // requires ES6
        for (var key in obj) this[key] = obj[key];
    }

    print() {
        console.log(`${this.name} is ${this.age}`);
    }
}

function getResource<T>(apiResource: APIResource<T>): T {
    console.log(`resource url: "${apiResource.url}"`);

    // pretend this came back from an ajax request    
    let obj: any = { name: 'Arthur', age: 42 };
    console.log(`obj instanceof Person? ${obj instanceof Person}`); // false

    return new apiResource.ctor(obj);
}

const res = new APIResource(Person, '/person/1/');
let person = getResource(res);

console.log(`person instanceof Person? ${person instanceof Person}`); // true
person.print();

https://i.sstatic.net/ICYyC.png

The code has been simplified to run in a single TypeScript file without Angular 2 and rxjs dependencies. Extending it to handle HTTP calls in Angular 2 is simple (split into multiple maps for clarity):

getResource<T>(apiResource: APIResource<T>): Observable<T> {
    return this.http.get(apiResource.url)
        .map(result => result.json())
        .map(obj => new apiResource.ctor(obj));
}

You can then consume the strongly-typed result using subscribe, as usual.

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

Obtain the object literal string with additional decorative strings surrounding it

In my current Typescript code, I have an object literal structured like this: const MyNamesStrings = { a: { b: "hello", c: "bye" } d: { e: "qwerty" } } However, I am looking for a way to wrap these strings with add ...

Do const generics similar to Rust exist in TypeScript?

Within TypeScript, literals are considered types. By implementing const-generics, I would have the ability to utilize the value of the literal within the type it belongs to. For example: class PreciseCurrency<const EXCHANGE_RATE: number> { amount ...

How to stop a loop of method calls that return a Promise<any> in TypeScript

My current issue involves a loop in which a method is called, and the method returns an object of type Promise<any>. I need to break the loop if the response from the method is correct. However, using the break statement does not stop the loop as exp ...

When an import is included, a Typescript self-executing function will fail to run

Looking at this Typescript code: (()=> { console.log('called boot'); // 'called boot' })(); The resulting JavaScript is: (function () { console.log('called boot'); })(); define("StockMarketService", ["require", "exp ...

Comparing TypeScript's `return;` with `return undefined;`: which is better?

I encountered a strange behavior that is puzzling to me, and I'm not sure if there's a logical explanation for it. In TypeScript, the following code works perfectly: type UndefinedFunction = () => undefined; let uf: UndefinedFunction = funct ...

Angular Material 2: Sidenav does not come with a backdrop

I'm encountering an issue with the SideNav component while developing a website using Angular 2. The SideNav has 3 modes, none of which seem to affect what happens when I open it. I am trying to make the backdrop display after opening. Even though t ...

Discovering the power of chaining operators in RxJS 6 by encapsulating .map within

I am in the process of updating my Observable code from RXJS 5 to version 6. import { Injectable } from '@angular/core'; import { Observable } from 'rxjs' import { AppConfig } from '../config/app-config'; import { Xapi } from ...

After importing this variable into index.ts, how is it possible for it to possess a function named `listen`?

Running a Github repository that I stumbled upon. Regarding the line import server from './server' - how does this API recognize that the server object has a method called listen? When examining the server.ts file in the same directory, there is ...

TS2688 Error: TypeScript Build Fails to Locate Type Definition File for 'mocha' Following Update

After updating my TypeScript to the latest version, I keep encountering the following error: Cannot find type definition file for 'mocha'. tsconfig.json { "compilerOptions": { "emitDecoratorMetadata": true, "experimentalDecorators ...

Troubleshooting Problems with Angular 6 Inheritance

After creating a base class named AncientWisdom and multiple subclasses representing different aspects of ancient wisdom, I encountered an issue in Angular. When the end value triggers the logic for exceeding the maximum unlocks, all subclasses inheriting ...

A guide on implementing getStaticProps using TypeScript in Next.js

Why am I consistently receiving undefined results when attempting to retrieve all posts from my backend? What could be causing this issue? import { AppContext } from '@/helpers/Helpers' import axios from 'axios' import { GetStaticProps} ...

Discovering the object and its parent within a series of nested arrays

Is there a way to locate an object and its parent object within a nested array of unknown size using either lodash or native JavaScript? The structure of the array could resemble something like this: name: 'Submodule122'</p> I have been ...

Tips on resolving the error message "Property ... is not present on type 'IntrinsicAttributes & ...' in NextJS"

In my nextjs application, I have a Navbar component that accepts menu items as props: <Navbar navitems={navigationItems} /> The navigationItems prop is an array of objects. Within the Navbar component, I have defined the following: export interface ...

Deliver transcluded data to the descendant element of a hierarchical roster

I understand that there have been similar questions asked before, but my situation is slightly different. I am currently constructing a nested list and I want to include custom HTML content in each grandchild element alongside some common HTML. The problem ...

Exploring the elements within the ContentChildren directive in Angular

Presenting my component: import { Component, OnInit, ContentChildren, QueryList } from '@angular/core'; import { IconBoxComponent } from '../icon-box/icon-box.component'; @Component({ selector: 'app-three-icon-box', temp ...

Utilizing TypeORM in a Node.js Project

Recently, I was exploring different ORM options for my server application and came across TypeORM. I'm curious to know the best approach to organize a small project using it. While browsing through the official documentation, I found a repository that ...

Is there a preferred method in RxJS for handling snapshotChanges()?

Currently, my code includes an auth.service.ts, crud.service.ts, and a components.ts. Although it functions correctly, it is a hodgepodge of various tutorials and documentation. I am seeking advice on how to streamline the code by centralizing all logic ...

An easy way to insert a horizontal line between your text

Currently, I have two text responses from my backend and I'm considering how to format them as shown in the design below. Is it possible to automatically add a horizontal line to separate the texts if there are two or more broadcasts instead of displa ...

The language in the React Native app remains unchanged despite utilizing i18next.changeLanguage

I am currently attempting to integrate the i18next library into my React Native app in order to facilitate language changes, but I have encountered difficulties with translation. image description here I have created an i18n.tsx file. import i18next from & ...

Error: TypeScript is unable to find the property URL within the specified type. Can you help

Currently, I am honing my skills in TypeScript and encountering a problem. I am a bit confused about how to define the type for the URL when I am setting the mix here. Obviously, the URL is supposed to be a string, and this specific scenario is within the ...