1

I really enjoy the strictness of Typescript when defining classes and the types of every object used, but I've lately been confronted with something I would like to be DRYer:

I've got a class definition that use and create specific objects for let's say a ClientsDB:

class ClientsDB {
  constructor(name: string) {
    this.DB = new Database(name);
  }
  DB: Database;
  replaySubject = new ReplaySubject<Client[]>(1);
  up() {
    fromPromise(this.DB.getDocs<Client>())
      .subscribe((clients) => this.replaySubject.next(clients));
  }
  subscribe(callback: (value: Client[]) => void) {
    return this.replaySubject.subscribe(callback);
  }
}

The thing is I want to use the same type of class for a ProductsDB that would exactly be the same definition in plain JavaScript but would use different types as:

class ProductsDB {
  constructor(name: string) {
    this.DB = new Database(name);
  }
  DB: Database;
  replaySubject = new ReplaySubject<Product[]>(1);
  up() {
    fromPromise(this.DB.getDocs<Product>())
    .subscribe((products) => this.replaySubject.next(products));
  }
  subscribe(callback: (value: Product[]) => void) {
    return this.replaySubject.subscribe(callback);
  }
}

How could I get only one class definition but with using the same rigor with these types definition?

3
  • Did you check generics ? typescriptlang.org/docs/handbook/generics.html Commented May 24, 2018 at 8:33
  • As written they're not actually equivalent. Did you mean to have a line missing from the second class? .pipe(map(...)) is only in the first one. Commented May 24, 2018 at 8:56
  • @Duncan you're right, I've simplified my code for this example and removed a line only in one, I'll make an edit ! Commented May 24, 2018 at 10:28

1 Answer 1

3

You can use generics to instantiate the class for different types. At runtime generics are erased and you will have a single JS class. Since you also need to create objects of the class you will need to pass the constructor for the item as a parameter to the class:

class DBClient<T> {
    constructor(name: string, public itemCtor: new (data: any) => T) {
        this.DB = new Database(name);
    }
    DB: Database;
    replaySubject = new ReplaySubject<T[]>(1);
    up() {
        fromPromise(this.DB.getDocs<T>())
            .pipe(map(res => res.rows.map(x => new this.itemCtor(x.doc))))
            .subscribe((clients) => this.replaySubject.next(clients));
    }
    subscribe(callback: (value: T[]) => void) {
        return this.replaySubject.subscribe(callback);
    }
}

let products = new DBClient("", Product)
let clients = new DBClient("", Client)
Sign up to request clarification or add additional context in comments.

5 Comments

Thanks a bunch ! But can't you write something else than itemCtor: new (data: any) => T ?
You can write a different constructor signature... Depending on the type of argument to the constructor. But it would have to be uniform across all supported types. If you tell me what your use case is maybe I can help
I basically retrieve JSON objects from a DB that I instantiate as member of their specific classes to give them methods and getters/setters, the constructor is like Object.assign(this, DBdocument).
Then maybe you could use itemCtor: new (data: Partial<T>) => T ? Still you are probably assigning form any at some point.. passing in the constructor for the class is required through.
I found a question about this constructor thing and I'm gonna use this interface approach even if it's more line to write!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.