I am new to typescript and not that familiar.
I was reading this blog on web and trying to comprehend the code
Here the author have created a simple route
// /lib/routes/crmRoutes.ts
import {Request, Response} from "express";
export class Routes {
public routes(app): void {
app.route('/')
.get((req: Request, res: Response) => {
res.status(200).send({
message: 'GET request successfulll!!!!'
})
})
}
}
Which is he is using like this in a kind of main-entry point file
// /lib/app.ts
import * as express from "express";
import * as bodyParser from "body-parser";
import { Routes } from "./routes/crmRoutes";
class App {
public app: express.Application;
public routePrv: Routes = new Routes();
constructor() {
this.app = express();
this.config();
this.routePrv.routes(this.app);
}
private config(): void{
this.app.use(bodyParser.json());
this.app.use(bodyParser.urlencoded({ extended: false }));
}
}
From the definition I read on web, Classes are also like interface in typescript, so this line
public routePrv: Routes = new Routes();
Here, Routes is an interface for routePrv:? And from my vague understanding, interface usually looks like this
interface Person {
name: string;
age: number;
}
So my question is that, How can a class be an interface and how will our interface look like in case of
public routePrv: Routes = new Routes();
Can someone please explain the same?
You are confusing Interfaces and Types.
An Interface (interface MyInterface {}) is used to express what an Object has to look like. After typescript transpilation, interfaces are removed. They are more like "hints" for typescript. An example of its usage is this:
interface MyInterface {
myProp:number;
}
const myConst = {myProp: 0}; // implicitly implements MyInterface
const myConst2:MyInterface = {myProp: 0}; // Explicitly tells typescript the type of "myConst2" should be "MyInterface"
const myConst2:MyInterface = {}; // Error, does not implement "MyInterface" correctly
function myFunc(input:MyInterface) {/* Do something */}
myFunc(myConst); // Works
myFunc(myConst2); // Works
myFunc({}); // Fails
Classes (class MyClass {}) boil down to Constructor Functions. They are a way of constructing an Object. Since they describe how to construct an Object and thus describe the constructed Object's shape, they can also be used to describe the type of an Object. Since Classes are Constructor Functions, they do something during runtime. Hence, only their usages as types are removed.
// You can explicitly implement the interface or explicitly, doesn't matter for its usage.
// Explicitly just tells typescript that this class MUST implement the interface
class MyClass /* implements MyInterface */ {
constructor(public myProp?:number = 0) {}
}
const myClassConst = new MyClass(); // works
const myClassConst2:MyClass = new MyClass(); // works
const myClassConst3:MyInterface = new MyClass(); // works
myFunc(myClassConst); // works
myFunc(myClassConst2); // works
myFunc(myClassConst3); // works
function myFunc2(input:MyClass) { /* Do something */ }
myFunc2(myClassConst); // works
myFunc2(myClassConst2); // works
myFunc2(myClassConst3); // works
As per Request, another example of what does not work:
class MyOtherClass {
constructor(public myProp:number) {}
}
const myOthClassConst = new MyOtherClass(); // works
const myOthClassConst2:MyClass = new MyOtherClass(); // fails, type mismatch
const myOthClassConst3:MyInterface = new MyOtherClass(); // fails, type mismatch
function myFunc3(input:MyOtherClass) {}
myFunc3(myOthClassConst2); // works
myFunc3(myClassConst); // fails, type mismatch
Edit: Rephrased the explanation due to ambigious meaning (thanks to #Bergi for pointing that out).
Related
I'm trying to convert this package to TypeScript without any breaking changes. I have the following code in TypeScript.
// DocumentCarrier.ts
/* export */ class DocumentCarrier {
internalObject: {};
model: Model;
save: (this: DocumentCarrier) => void;
constructor(model: Model, object: {}) {
this.internalObject = object;
this.model = model;
}
}
DocumentCarrier.prototype.save = function(this: DocumentCarrier): void {
console.log(`Saved document ${JSON.stringify(this.model)} to ${this.model.myName}`);
};
// Model.ts
// import {DocumentCarrier} from "./DocumentCarrier.ts";
/* export */class Model {
myName: string;
Document: typeof DocumentCarrier;
get: (id: number) => void;
constructor(name: string) {
this.myName = name;
const self: Model = this;
class Document extends DocumentCarrier {
static Model: Model;
constructor(object: {}) {
super(self, object);
}
}
Document.Model = self;
Object.keys(Object.getPrototypeOf(this)).forEach((key) => {
Document[key] = this[key].bind(this);
});
this.Document = Document;
return this.Document as any;
}
}
Model.prototype.get = function(id: number): void {
console.log(`Retrieving item with id = ${id}`);
}
// Usage
// index.ts
// import {Model} from "./Model.ts";
const User = new Model("User");
const user = new User({"id": 5, "name": "Bob"});
user.save(); // "Saved document {"id": 5, "name": "Bob"} to User"
console.log(User.Model.myName); // "User"
// console.log(User.myName); // "User" // This option would be even better, but isn't supported in the existing code
User.get(5); // "Retrieving item with id = 5"
In the Usage section (very bottom of the code example above) I'm getting multiple errors in TypeScript. But running that code in a JavaScript file, works perfectly. So I know it's working and the code is accurate.
I think the biggest problem of what I'm trying to do is return this.Document as any. TypeScript is interpreting that as casting this.Document to a Model instance, when in reality it's not.
My question is this. In TypeScript how can I set it up where you can run new MyClassInstance() and have it return an instance of a different class? That has a bidirectional reference from MyClassInstance and the different class. In short, how do I get the following code working?
It's important that any solution works with the Usage section, and no modifications are made to that section. Except for the User.Model.myName vs User.myName section, which would be preferred as User.myName, but in the existing version functions as User.Model.myName.
For easy use, I also created a TypeScript Playground.
I'm going to interpret this question strictly as "how can I give typings to the existing code so that the compiler understands the code in the Usage section?" That is, the answer should not touch the emitted JavaScript, but instead should only alter type definitions, annotations, and assertions.
Aside: the more general question "how should I implement a class whose instances are themselves class constructors" is one I won't attempt to address, since from my research the best answer here is "don't try to do that" since it plays poorly with the prototypical inheritance model in JS. I'd instead lean strongly toward having a non-constructible class instance hold a property which is the constructor of the new class. Something like this Playground code. You'd be a lot happier in the long run, I expect.
Back to the typings: the main problem here is that TypeScript has no way to specify that a class constructor returns a type other than the class being defined. This is either intentional (see microsoft/TypeScript#11588 or a missing feature (see microsoft/TypeScript#27594) but in any case it's not part of the language.
What we can do here is to use declaration merging. When you write class Model {} you introduce both a class constructor object named Model and an interface type named Model. That interface can be merged into, adding methods and properties that the compiler doesn't already know about. In your case you could do this:
interface Model {
new(object: {}): DocumentCarrier;
Model: Model;
}
This lets the compiler know that Model instances, in addition to having the properties/methods declared in the class, also has a Model property whose type is Model, and, importantly, a constructor signature. That's enough to get the following code to compile without error:
const User = new Model("User");
const user = new User({ "id": 5, "name": "Bob" });
user.save(); // "Saved document {"id": 5, "name": "Bob"} to User"
console.log(User.Model.myName); // "User"
User.get(5); // "Retrieving item with id = 5"
The compiler does think that User.myName exists, which it doesn't at runtime, but that's already a problem with the existing code so I'm not touching that here. It's possible to change the typings further so that the compiler knows that User.Model.myName exists and that User.myName does not exist, but that becomes quite complicated as it requires you to split Model's interface into multiple types that you carefully assign to the right values. So for now I'm ignoring it.
The only other change I'd make here would be to give different typings to the implementation of Model, like this:
class Model {
myName: string;
Document: Model;
get!: (id: number) => void;
constructor(name: string) {
this.myName = name;
const self: Model = this;
class Document extends DocumentCarrier {
static Model: Model;
constructor(object: {}) {
super(self, object);
}
}
Document.Model = self;
(Object.keys(Object.getPrototypeOf(this)) as
Array<keyof typeof DocumentCarrier>).forEach((key) => {
Document[key] = this[key].bind(this);
});
this.Document = Document as Model;
return this.Document;
}
}
The only thing the compiler won't be able to verify in the above is that the Document class is a valid Model, so we use the assertion Document as Model. Other than that I just put a few assertions (get is definitely assigned, and Object.keys() will return an array of keys of the DocumentCarrier constructor) so that you don't need to turn off the --strict compiler flag.
Okay, hope that helps. Good luck!
Playground link to code
After roaming a bit, I got something.
Typescript complains about your solution because, even if you are returning a class Document internally in Model constructor, the compiler expects a Model instance, which is not constructable.
So, we need to make Model constructable. In fact, the same as making a function which returns instances of something.
First, let's declare your preovious DocumentCarrier class. Now, DocumentCarrier will have two properties, model and name (this was your previously keyed myName from Model class).
class DocumentCarrier {
name: string = ``;
constructor(public model: {}) { }
save = () => console.log(`Saved document ${JSON.stringify(this.model)} to ${this.name}`)
}
After that, we need that function declaration that returns an instance model of type DocumentCarrier.
const Model = (name: string) => {
return class extends DocumentCarrier {
name: string = name;
constructor(model: any) {
super(model);
}
}
}
The function takes a string parameter and returns a constructor of type DocumentCarrier which takes an any object on its constructor and passes to the DocumentCarrier constructor.
We can call Model like this.
const User = Model('User');
const user = new User({id: 5, name: 'Bob'});
user.save(); // Saved document {"id":5,"name":"Bob"} to User
The call to Model is the only change made. Now Model call does not need a new keyword.
On the other hand, name in DocumentCarrier can be accessed from the last instance constructed.
In addition, this solution could be a bit more generic by defining generic types.
type Constructor<T> = new (...args: any[]) => T;
The type Constructor constraints a constructor function of any type T.
Now, we need to rewrite the Model function:
const Model = <T extends Constructor<{}>>(Type: T, name: string) => {
return class extends Type {
name: string = name;
constructor(...args: any[]) {
super(...args);
}
}
}
Model now expects the same string as before but an additional parameter which is the type we want to instantiate, so
const User = Model(DocumentCarrier, 'User');
const user = new User({id: 5, name: 'Bob'});
user.save(); // Saved document {"id":5,"name":"Bob"} to User
Even more, since we are fulfilling a property name that belongs to the instance we are creating inside the Model function, we should constraint the input type to expect that name property.
type Constructor<T> = new (...args: any[]) => T;
interface DynamicallyConstructed {
name: string;
}
class DocumentCarrier implements DynamicallyConstructed {
name: string = ``;
constructor(public model: {}) { }
save = () => console.log(`Saved document ${JSON.stringify(this.model)} to ${this.name}`)
}
const Model = <T extends Constructor<DynamicallyConstructed>>(Type: T, name: string) => {
return class extends Type {
name: string = name;
constructor(...args: any[]) {
super(...args);
}
}
}
const User = Model(DocumentCarrier, 'User');
const user = new User({id: 5, name: 'Bob'});
user.save();
Hope this helps a bit.
Please comment any issue.
Here is the next JavaScript class structure:
// data.service.ts
export class DataService {
public url = environment.url;
constructor(
private uri: string,
private httpClient: HttpClient,
) { }
getAll() {}
getOne(id: number) {}
create(data: any) {}
// etc...
}
Next is the general data model what can use the DataService's methods to communicate the server:
// Model.model.ts
import './data.service';
export class Model extends DataService {
all() {}
get() {
// parse and make some basic validation on the
// DataService.getOne() JSON result
}
// etc...
}
And finally I create a specific data model based on Model.model.ts:
// User.model.ts
import './Model.model.ts';
export class User extends Model {
id: number;
name: string;
email: string;
init() {
// make specific validation on Model.get() result
}
}
If I use the User class in my code, I can call the DataService's getAll() function directly if I want. But this is not a good thing, because in this case I miss the built-in validations.
How can I block the method inheritance on a class?
I'm looking for something like PHP's static method. The child class can use the methods, but his child can't.
I want something like this:
const dataService = new DataService();
dataService.getAll(); // void
const model = new Model();
model.getAll(); // undefined
model.all(); // void
const user = new User();
user.getAll(); // undefined
user.all(); // void
Is there any way to do this?
You can prevent it to be built when you call it by adding private keyword to the function private getAll() {}. But private is a TypeScript feature, not Javascript, so if you force it to be built, it is still callable. There'll be no way to totally prevent it this moment.
So if you want it to be prevented in TypeScript, just add private keyword there. But it is not buildable, not return undefined as you expect. Otherwise, just replace the function with an undefined-returned function on children classes
With your code as shown and use case as stated, the only way to get the behavior you want is not to make Model extend DataService at all. There is a subsitution principle which says that if Model extends DataService, then someone should be able to treat a Model instance exactly as they would treat a DataService instance. Indeed, if a Model is a special type of DataService, and someone asks for a DataService instance, you should be able to give them a Model. It's no fair for you to tell them that they can't call the getAll() method on it. So the inheritance tree can't work the way you have it. Instead you could do something like this:
// ultimate parent class of both DataService and Model/User
class BaseDataService {
getOne(id: number) { }
create(data: any) { }
// etc...
}
// subclass with getAll()
class DataService extends BaseDataService {
getAll() {}
}
// subclass without getAll()
class Model extends BaseDataService {
all() { }
get() { }
// etc...
}
class User extends Model {
id!: number;
name!: string;
email!: string;
init() { }
}
const dataService = new DataService();
dataService.getAll(); // void
const model = new Model();
model.getAll(); // error
model.all(); // okay
const user = new User();
user.getAll(); // error
user.all(); // okay
That works exactly as you've specified. Perfect, right?
Well, I get the sinking feeling that you will try this and get upset that you cannot call this.getAll() inside the implementation of Model or User... probably inside the suspiciously-empty body of the all() method in Model. And already I'm getting the urge to defensively point to the Minimum, Complete, and Verifiable Example article, since the question as stated doesn't seem to require this.
If you do require that, you still can't break the substitution principle. Instead I'd suggest making getAll() a protected method, and expose an all() method on DataService:
class DataService {
getOne(id: number) { }
create(data: any) { }
protected getAll() { }
all() {
this.getAll();
}
// etc...
}
class Model extends DataService {
all() {
this.getAll();
}
get() { }
// etc...
}
class User extends Model {
id!: number;
name!: string;
email!: string;
init() { }
}
const dataService = new DataService();
dataService.getAll(); // error
dataService.all(); // okay
const model = new Model();
model.getAll(); // error
model.all(); // okay
const user = new User();
user.getAll(); // error
user.all(); // okay
and live with the fact that getAll() is a purely internal method never meant to see the light of day.
Okay, hope one of those helps; good luck!
Consider the following code:
import redis = require('redis'); //Has ambient declaration from DT
import bluebird = require('bluebird'); //Has ambient declaration from DT
bluebird.promisifyAll((<any>redis).RedisClient.prototype);
bluebird.promisifyAll((<any>redis).Multi.prototype);
const client = redis.createClient();
client.getAsync('foo').then(function(res) {
console.log(res);
});
getAsync will error out because it's created on the fly and not defined in any .d.ts file. So what is the proper way to handle this?
Also, even though I have the .d.ts files loaded for redis, I still need to cast redis to any to be used for promisifyAll. Otherwise, it will spill out error:
Property 'RedisClient' does not exist on type 'typeof "redis"'
Is typing it to any the only easy way to go?
I'm solving this by declaration merging the setAsync & getAsync methods. I added the following code in my own custom .d.ts file.
declare module "redis" {
export interface RedisClient extends NodeJS.EventEmitter {
setAsync(key:string, value:string): Promise<void>;
getAsync(key:string): Promise<string>;
}
}
Another way to do it which requires less code is to extend the Redis object like so:
import { promisify } from 'util';
import { ClientOpts, RedisClient } from 'redis';
class AsyncRedis extends RedisClient {
public readonly getAsync = promisify(this.get).bind(this);
public readonly setAsync = promisify(this.set).bind(this);
public readonly quitAsync = promisify(this.quit).bind(this);
public readonly rpushAsync: (list: string, item: string) => Promise<number> = promisify(
this.rpush
).bind(this);
public readonly blpopAsync: (
list: string,
timeout: number
) => Promise<[string, string]> = promisify(this.blpop).bind(this);
public readonly flushdbAsync = promisify(this.flushdb).bind(this);
}
Notice that not all method signatures overwrite correctly, so you have to help typescript a little.
Now you can just use this enhanced class by creating it with your options, for example:
new AsyncRedis({
host: process.env.REDIS_HOST || '127.0.0.1',
password: process.env.REDIS_PASSWORD || 'whatever',
});
Just adding to Dave's answer, in my needs, I has to add in Multi for atomic operations.
declare module 'redis' {
export interface RedisClient extends NodeJS.EventEmitter {
execAsync(...args: any[]): Promise<any>;
hgetallAsync(...args: any[]): Promise<any>;
// add other methods here
}
export interface Multi extends Commands<Multi> {
execAsync(...args: any[]): Promise<any>;
// add other methods here
}
}
This solution works fine for me:
import { promisifyAll } from 'bluebird'; // import here works only if #types/bluebird is installed
import redis, { RedisClient, Multi } from 'redis'; // import here works only if #types/redis is installed
// Convert Redis client API to use promises, to make it usable with async/await syntax
const MultiAsync: any = promisifyAll(Multi.prototype);
const RedisClientAsync: any = promisifyAll(RedisClient.prototype);
const redisAsync = { ...redis, Multi: MultiAsync, RedisClient: RedisClientAsync };
const client: typeof RedisClientAsync = redisAsync.createClient();
// now you can use client async methods, i.e. client.getAsync, client.hgetAsync, client.hsetAsync, client.expireAsync...
Consider the following code:
import redis = require('redis'); //Has ambient declaration from DT
import bluebird = require('bluebird'); //Has ambient declaration from DT
bluebird.promisifyAll((<any>redis).RedisClient.prototype);
bluebird.promisifyAll((<any>redis).Multi.prototype);
const client = redis.createClient();
client.getAsync('foo').then(function(res) {
console.log(res);
});
getAsync will error out because it's created on the fly and not defined in any .d.ts file. So what is the proper way to handle this?
Also, even though I have the .d.ts files loaded for redis, I still need to cast redis to any to be used for promisifyAll. Otherwise, it will spill out error:
Property 'RedisClient' does not exist on type 'typeof "redis"'
Is typing it to any the only easy way to go?
I'm solving this by declaration merging the setAsync & getAsync methods. I added the following code in my own custom .d.ts file.
declare module "redis" {
export interface RedisClient extends NodeJS.EventEmitter {
setAsync(key:string, value:string): Promise<void>;
getAsync(key:string): Promise<string>;
}
}
Another way to do it which requires less code is to extend the Redis object like so:
import { promisify } from 'util';
import { ClientOpts, RedisClient } from 'redis';
class AsyncRedis extends RedisClient {
public readonly getAsync = promisify(this.get).bind(this);
public readonly setAsync = promisify(this.set).bind(this);
public readonly quitAsync = promisify(this.quit).bind(this);
public readonly rpushAsync: (list: string, item: string) => Promise<number> = promisify(
this.rpush
).bind(this);
public readonly blpopAsync: (
list: string,
timeout: number
) => Promise<[string, string]> = promisify(this.blpop).bind(this);
public readonly flushdbAsync = promisify(this.flushdb).bind(this);
}
Notice that not all method signatures overwrite correctly, so you have to help typescript a little.
Now you can just use this enhanced class by creating it with your options, for example:
new AsyncRedis({
host: process.env.REDIS_HOST || '127.0.0.1',
password: process.env.REDIS_PASSWORD || 'whatever',
});
Just adding to Dave's answer, in my needs, I has to add in Multi for atomic operations.
declare module 'redis' {
export interface RedisClient extends NodeJS.EventEmitter {
execAsync(...args: any[]): Promise<any>;
hgetallAsync(...args: any[]): Promise<any>;
// add other methods here
}
export interface Multi extends Commands<Multi> {
execAsync(...args: any[]): Promise<any>;
// add other methods here
}
}
This solution works fine for me:
import { promisifyAll } from 'bluebird'; // import here works only if #types/bluebird is installed
import redis, { RedisClient, Multi } from 'redis'; // import here works only if #types/redis is installed
// Convert Redis client API to use promises, to make it usable with async/await syntax
const MultiAsync: any = promisifyAll(Multi.prototype);
const RedisClientAsync: any = promisifyAll(RedisClient.prototype);
const redisAsync = { ...redis, Multi: MultiAsync, RedisClient: RedisClientAsync };
const client: typeof RedisClientAsync = redisAsync.createClient();
// now you can use client async methods, i.e. client.getAsync, client.hgetAsync, client.hsetAsync, client.expireAsync...
I am trying to define an API using TypeScript such that it can work like this:
// Create new user (Working)
var user : IUser = new Api.User({ firstName: "John", lastName: "Smith" });
// Delete existing user (Working)
Api.User.Delete(1);
// Load existing user (Unsure how to implement)
var user = Api.User(123);
My TypeScript:
module Api
{
export class User
{
constructor(id : number)
constructor(user : IUser)
constructor(user? : any)
{
// ...
}
static Delete(id : number) {}
}
}
I am not sure how to have a static method Api.User() i.e. not use new. I don't know what to call this type of construct, which makes it difficult to research. :(
I did try adding an unnamed static to the User class, but this isn't right.
static (id : number)
{
// ...
}
Option 1: export a function directly on the Api module
You can export a function on the Api module to retrieve/create a User instance:
module Api
{
export class User
{
}
export function GetUser(id: number):User {
return new User();
}
// or a slightly different syntax (which generates different JavaScript):
export var Delete = (id: number) => {
};
}
You can't have the class named User and the function also be User, so I've changed it to GetUser in the example.
You could then call:
Api.GetUser(1234)
or
Api.Delete(1234);
Option 2: Using an interface
You could also approach this by using an interface if you wanted to limit the ability of
calling code from being able to instantiate instances of the inner class by using an interface instead. Below I've created a simple ISuperUser interface and and implementation of the class called SuperUserImpl. As the SuperUserImpl isn't exported, it's not publicly creatable. This is nice in that you could use simple Api.SuperUser(2345) to return new instances of a class that implements the ISuperUser interface.
module Api {
export interface ISuperUser {
id: Number;
name: String;
}
class SuperUserImpl implements ISuperUser
{
constructor(public id: Number) {
}
public name: String;
}
export var SuperUser = (id:Number):ISuperUser => {
return new SuperUserImpl(id);
}
}
var su : Api.ISuperUser = Api.SuperUser(5432);
alert(su.id);
Option 3: JavaScript and instanceof
There's a trick that is often used in JavaScript class constructor wherein the function/constructor for a new object checks to see whether it is of the right type (was the function called or created), and if not created, returns a new instance:
if (!(this instanceof User)) {
return new User(id);
}
While correct, TypeScript when trying to call a constructor with that will cause a compiler warning. This will work, but with the compiler warning:
constructor(id: Number) {
if (!(this instanceof User)) {
return new User(id);
}
}
And later calling:
var u: Api.User = Api.User(543);
A compiler warning suggests, "did you forget to use 'new'?" It does produce valid JavaScript, just with a warning. I'd probably go with the static-like method approach to avoid the TypeScript compiler warning.