Pass class as generic parameter Typescript - javascript

im trying to instance a class passed as parameter to another, I have this in one file, ImportedClass.ts:
export default class ImportedClass {
public constructor(something: any) {
}
public async exampleMethod() {
return "hey";
}
}
And this in another, InstanceClass.ts:
interface GenericInterface<T> {
new(something: any): T;
}
export default class InstanceClass <T> {
private c: GenericInterface<T>;
public constructor(c: T) {
}
async work() {
const instanceTry = new this.c("hello");
instanceTry.exampleMethod();
}
}
And this in another, ClassCaller.ts: <--EDITED-->
import ImportedClass from './ImportedClass';
import ImportedClass from './InstanceClass';
const simulator = new InstanceClass <ImportedClass>(ImportedClass);
Then when I call it like this:
simulator.work();
It throw this error:
error TS2339: Property 'exampleMethod' does not exist on type 'T'.
Any help is welcome, thanks.

If T must have a method named exampleMethod you must include this in the constraint for T on Simulator to be able to use it inside Simulator:
export class ImportedClass {
public constructor(something: any) {
}
public async exampleMethod() {
return "hey";
}
}
interface GenericInterface<T> {
new(something: any): T;
}
export class Simulator<T extends { exampleMethod(): Promise<string> }> {
public constructor(private c: GenericInterface<T>) {
}
async work() {
const instanceTry = new this.c("hello");
await instanceTry.exampleMethod();
}
}
const simulator = new Simulator(ImportedClass);
simulator.work()
Playground link
There were other small issues that needed to be fixed to make the snippet above work, but that is the mai issue.

Related

TypeScript - declare type of a React component class constructor with a certain static method

I got this to work with regular classes, including classes that extend the target class as you can see below:
interface ALike {
a: () => boolean
}
interface ALikeConstructor {
new (): ALike
aStatic: () => boolean
}
function acceptsALike(ctr: ALikeConstructor) { }
class A {
public a() {
return true;
}
public static aStatic(): boolean {
return true;
}
}
class AB extends A {
public b() {
return true;
}
}
class ANoStatic {
public a() {
return true;
}
}
acceptsALike(A); // ok
acceptsALike(AB); // ok
acceptsALike(ANoStatic); // ok (error shows)
Playground
However, for whatever reason it does not work with React components and TypeScript does not give much of an explanation.
import React from 'react'
type Props = {
test: true
}
interface CLike extends React.Component<Props> {
c(): boolean
}
interface CLikeConstructor {
new(): CLike
cStatic(): boolean
}
class C extends React.Component<Props> implements CLike {
public c() {
return true;
}
public static cStatic() {
return true;
}
}
function acceptsCLike(ctr: CLikeConstructor) { }
acceptsCLike(C); // Argument of type 'typeof C' is not assingable to parameter of type 'CLikeConstructor' - no further explanation.
Playground
I understand that something about React.Component makes it incompatible, but what is it and is there a way to work around it?
The problem is that your constructor signature (in CLikeConstructor) describes a constructor with no arguments. The react class has a constructor with one argument (the props).
You can defined the constructor signature to not care about the number of args:
interface CLikeConstructor {
new(...a: any[]): CLike
cStatic(): boolean
}
Playground Link
You could also make it accept just one param (the props) like this: new(props: any): CLike

turn static method of class into variable typescript

Hi all so currently I am making a service for my application/library i am creating and so far this is the best way I can figure out how to create a service. Ideally I would like to change this method into a variable for easier reading
import { NavigationRoute, NavigationParams } from 'react-navigation';
import { NavigationStackProp } from 'react-navigation-stack';
type Navigation =
| NavigationStackProp<NavigationRoute<NavigationParams>, NavigationParams>
| undefined;
export default class NavigationService {
private static _navigator: Navigation;
public static setNavigator(navigatorRef: Navigation) {
this._navigator = navigatorRef;
}
public static navigate = (): Navigation => {
// TODO: look into how to make this a variable
return NavigationService._navigator;
};
}
currently i have this
IconPress: () => NavigationService.navigate()?.openDrawer()
but would love for it to read like this
IconPress: () => NavigationService.navigate?.openDrawer()
This is called a getter:
export default class NavigationService {
public static get navigate(): Navigation {
return NavigationService._navigator;
}
}

How to automatically load files that contain specific decorators in a node project

I have created a decorator, in Project A (the main library) and would like to have all of those decorators automatically loaded when the app starts in Project B (the project using Project A). Is there anyway of doing this?
index.ts looks like this:
export function MyDecorator<T extends Controller>() {
return (target: new () => T) => {
// Do stuff with the decorator
}
}
const server = http.createServer((req, res) => {
})
server.listen(8080)
Is there something that I can do to automatically execute #MyDecorator() on all classes in Project B without Project B having to do so?
MyClass1.ts
import { MyDecorator } from 'project-a'
#MyDecorator()
export class ProjectBClass1 {}
MyClass2.ts
import { MyDecorator } from 'project-a'
#MyDecorator()
export class ProjectBClass2 {}
I assume you mean creating instances by load .
Also I'm not sure if that is an elegant solution but here is my suggestion:
Create a class that has a static method:
class ControllerCreator {
private static constrollerInstances: any = []
private static controllerConstructors : any = [];
static registerControllerClass(ctor: any) {
ControllerCreator.controllerConstructors.push(ctor);
}
static createInstances() {
ControllerCreator.controllerConstructors.forEach(
ctor => constrollerInstances.push(new ctor()) // pushing them to static array to not lose
)
}
}
In your decorator you should register your controller constructor:
export function MyDecorator<T extends Controller>() {
return (target: new () => T) => {
// Do stuff with the decorator
class newClass extends target {
// ...
}
ControllerCreator.registerControllerClass(newClass);
}
}
And finally at some point you should call:
ControllerCreator.createInstances();

How can I declare type of an ES6 module in Typescript?

I have a generic interface for a repository like this.
export interface IBaseRepository<T> {
create(model: T): T;
edit(model: T): T;
read(): T
}
I'm trying to implement this repository interface in a functional module like this (since it's basically a stateless singleton).
// user-repository.ts
export function create(model: IUser): IUser { ...code }
export function edit(model: IUser): IUser { ...code }
export function read(): IUser { ...code }
Is there any way to ensure that IBaseRepository is implemented in UserRepository. Or do I have to always implement the repository as a class and export an instantiated singleton?
You can bundle all functions into an object and have that exported.
Something like this:
import { IBaseRepository } from './master';
// user-repository.ts
export function create(model: string): string { return ''; }
export function edit(model: string): string { return ''; }
export function read(): string { return ''; }
export const repository: IBaseRepository<string> = { create, edit, read };
And use it like you would use anything else that module exports:
import { repository } from './repo';
repository.create('test');
Or use the export default to export the functions directly if you want to have that type for the default module export.
import { IBaseRepository } from './master';
// user-repository.ts
export function create(model: string): string { return ''; }
export function edit(model: string): string { return ''; }
export function read(): string { return ''; }
export default { create, edit, read } as IBaseRepository<string>;
And import the module to get it or import the functions separately :
import repo, { create } from './repo';
repo.create('test');
You can still import each function independently of course.
Note use types according to your example I just tried to keep things simpler.
How about this?
interface IFoo {
foo(): boolean;
bar();
}
namespace Foo {
export function foo() {
return 42;
}
}
declare type assert<T, K extends T> = {};
declare const check1: assert<IFoo, typeof Foo>;
We get a nice error on the check1:
Type 'typeof Foo' does not satisfy the constraint 'IFoo'.
Types of property 'foo' are incompatible.
Type '() => number' is not assignable to type '() => boolean'.
Type 'number' is not assignable to type 'boolean'.
My example has an inline module, but it should be similar to assert an external module using a import * as Foo from './foo';

Typescript: Inject generic & get ES6 module name

I am trying to build a generic repository using:
Typescript
ES6
Angular 1.x
But I can't figure out how I should inject the Entity and then get its module name.
The reason why i want to get the name:
Is because i follow a naming convention where a file called order-count.ts should render the URL '/order/count'
Is this solvable with Typescript/Javascript?
Here is what i have:
order-module.ts
import {App} from '../../App';
import {OrderService} from './order-service';
const module: ng.IModule = App.module('app.order', []);
module.service('orderService', OrderService);
order-service.ts
import {CrudService} from '../../shared/services/crud-service'
import {OrderCount} from '../order/entities/order-count';
export class OrderService {
// #ngInject
constructor(private crudService: CrudService<OrderCount>) {
this.crudService = crudService;
}
getOrders() {
var promise = this.crudService.getAll();
promise.then(response => {
console.log(response, 'success');
}, error => {
console.log(error, 'failed');
});
}
}
order-count.ts
import {Entity} from '../../../shared/models/entity';
export class OrderCount extends Entity {
storeId: string;
storeName: string;
}
entity.ts
export interface IEntity {
id: number;
}
entity.ts
import {IEntity} from '../../module/contracts/entities/entity';
export class Entity implements IEntity {
new() { }
id: number;
}
crud-service.ts
'use strict';
import { Entity } from '../models/entity';
import { EndpointService } from './endpointService';
export class CrudService<TEntity extends Entity> {
private baseCallPath: string;
private entity: { new (): Entity };
// #ngInject
constructor(private endpointService: EndpointService, private $http: ng.IHttpService) {
this.baseCallPath = new this.entity().constructor.name.replace('-', '/');
}
getAll(): ng.IHttpPromise<any> {
return this.handleResponse(
this.$http.get(this.endpointService.getUrl(this.baseCallPath)),
'getAll'
);
}
handleResponse(promise: ng.IHttpPromise<any>, callerMethodName: string): ng.IHttpPromise<any> {
return promise.success((data: any) => {
Array.prototype.push.apply(this.baseCallPath, data);
}).error((reason: any) => {
console.log(this.baseCallPath + callerMethodName, 'ERROR', reason);
});
}
}
endpoint-service.ts
export class EndpointService {
private baseUri: string = 'http://localhost:3000/api/';
getUrl(moduleName: string): string {
return this.baseUri + moduleName;
}
}
This link may be helpful in order to implement a generic repository with Typescript
Regarding the usage of class name as a value you may check this relevant question.
The good thing it can be retrieved and used as Foo.name or this.constructor.name. The bad thing is that it isn't available in every browser and should be polyfilled. Another bad thing is that minified function won't save its original name.
Wouldn't it be great to annotate function with Foo.name = 'Foo' on its definition and stick to pre-made property? Not really. Function.name is originally non-configurable, so it is read-only in a plethora of browsers.
If you don't plan to avoid minimization at all, or you're not too fond of configuring minifier to preserve class names (a solution faulty by design), don't use Function.name for anything like that.
The typical case for extendable ES6/TS class in Angular is
export class Foo {
static _name = 'Foo';
}
export default angular.module('app.foo', [])
.factory('Foo', Foo)
// if DRY is a must,
// .factory(Foo._name, Foo)
.name;
import { Foo } from './foo';
export class Bar extends Foo {
static _name = 'Bar';
}
export default angular.module('app.bar', []).factory('Bar', Bar).name;
import moduleFoo from './foo';
import moduleBar from './bar';
angular.module('app', [moduleFoo, moduleBar]);
So exports for Angular modules and classes should go hand in hand, they are not interchangeable.

Categories

Resources