Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggestion: Provide a way to emit interface names into transpiled code #18227

Closed
mcmatt89 opened this issue Sep 3, 2017 · 4 comments
Closed

Comments

@mcmatt89
Copy link

mcmatt89 commented Sep 3, 2017

Hello,

I would like a way to assign the name of a given interface/type into a string that can be used in a JS environment.

Please see the following example:

import { SomeInterface } from "@Core/SomeInterface";

export class SomeRepository {
    protected items: {[index: string]: any} = {};

    public getItem<T>(): T {
        const key = typeof(T); // this would be ideal
        return this.items[key];
    }

    public setItem<T>(item: any): void {
        const key = typeof(T)
        this.items[key] = item;
    }
}

const repo = new SomeRepository();

repo.setItem<SomeInterface>({});
// repo.items["SomeInterface"] = {}

It could potentially transpile down to:

export class SomeRepository {
    getItem(T) {
        const key = T;
        return this.items[key];
    }

    setItem(T, item) {
        const key = T;
        this.items[key] = item;
    }
}

const repo = new SomeRepository();
repo.setItem("SomeInterface", {});

Outside of type arguments, it may work like this:

const interfaceName = typeof(SomeInterface);

to

const interfaceName = "SomeInterface"; // ?

Thank you for your time.

@jcalz
Copy link
Contributor

jcalz commented Sep 3, 2017

As useful as such a feature may be, it is one of the stated non-goals of TypeScript to emit type information at run time (see non-goal number 5). So this suggestion will likely be declined.

What is your use case for this? Likely there's a TypeScript-friendly approach that will work for you.

@mcmatt89
Copy link
Author

mcmatt89 commented Sep 3, 2017

@jcalz

Cheers for the response - I wasn't aware of the design goals document and this issue would definitely break goal 5.

As for the use case, I have extended an Express application to provide methods for registering and retrieving services in our controllers.

Services are registered thus: app.setSvc<T>(serviceName: string): void; and retrieved with app.getSvc<T>(serviceName: string): T;. Internally, Express' own app.get/app.set methods are used to store the instantiated service object or factory function. The getSvc/setSvc methods are basically wrappers that include typings and error handling.

When you call getSvc<FooInterface>("FooService"), you are actually calling app.get("FooService") as FooInterface, and setSvc<FooInterface>("FooService", fooObj) becomes app.set("FooService", fooObj).

The suggestion would have eliminated the string argument from both method signatures (i.e. app.getSvc<T>(): T/app.setSvc<T>()).

There likely is a better way of handling it; this suggestion popped into my mind when I realised I was basically repeating the type argument as a string. I suppose we could skip the wrappers and just use the app.get("FooService") as FooInterface method instead if need be.

@jcalz
Copy link
Contributor

jcalz commented Sep 3, 2017

This conversation probably belongs in Stack Overflow, but you can manually set up a mapping from service name to service type without requiring anyone to use type assertions:

// some service interfaces:
export interface FooInterface {
  getName(index: number): string;
}
export interface BarInterface {
  getSize(metricUnits: boolean): number;
}

// here is your manual mapping
export type ServiceMappings = {
  "FooService": FooInterface;
  "BarService": BarInterface;
  // other mappings ...
  [k: string]: any; // default type, remove for strictness or change any to some other type
}

declare module 'express' {
  interface Application {
    getSvc<K extends keyof ServiceMappings>(serviceName: K): ServiceMappings[K];
    setSvc<K extends keyof ServiceMappings>(serviceName: K, service: ServiceMappings[K]): void;
  }
}
// you need to implement getSvc() and setSvc()

import * as Express from 'express'
declare const app: Express.Application

const fooService = app.getSvc("FooService"); // fooService is inferred as FooInterface
fooService.getName(2); // okay

const barService = app.getSvc("BarService"); // barService is inferred as BarInterface
barService.getSize(false); // okay

const noIdea = app.getSvc("Pancakes"); // noIdea is inferred as any
noIdea.whoKnows(); // okay because of any

@mcmatt89
Copy link
Author

mcmatt89 commented Sep 3, 2017

@jcalz

Hey - this is brilliant. It seems that there certainly was a TS-friendly way to achieve what we wanted.

Thank you for taking your time to go through this, I appreciate it.

@mcmatt89 mcmatt89 closed this as completed Sep 3, 2017
@microsoft microsoft locked and limited conversation to collaborators Jun 14, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants