2

I have a service what can handle datas in the localstorage and I have an other service what handle the remote API querys. When I define a data model I give it the model is use the local storage or not.

Now I try to build a proxy service what can decide based on the model definition to ask datas from localstorage or from the remote API.

Right now I have this code:

export class ProxyService<T> {
  public type: new () => T;
  private emptyModel: T = new this.type();
  private localstorageService: LocalstorageDataService<T> =
    new LocalstorageDataService(this.type, this.httpClient);
  private remoteDataService: RemoteDataService<T> = new RemoteDataService(this.type, this.httpClient);

  constructor(
    private httpClient: HttpClient,
  ) { }

  getOne(id: number): Observable<T> {
    if (this.emptyModel.use_localstorage) {
      return of(this.localstorageService.findById(id));
    } else {
      return this.remoteDataService.getOne(id).pipe(map((result: T) => result));
    }
  }

  // other proxy functions are here...
}

But I get this error:

Error: Uncaught (in promise): TypeError: this.type is not a constructor
TypeError: this.type is not a constructor
    at new ProxyService (proxy.service.ts:19)

Both RemoteDataService & LocalstorageDataService use a model, so the ProxyService is the best place where I define one. Because the ProxyService is used in multiple place in the whole project with multiple models.

By the way the 19th line is this code from above:

private emptyModel: T = new this.type();

I tried to fix it in the constructor:

export class ProxyService<T> {
  // ...
  constructor(
    public type: new () => T;
    private httpClient: HttpClient,
  ) { }
  // ...

But in this case I need to define a model when I call the ProxyService, and I don't want to define a model every case when I use the ProxyService. In this case I can't use the ProxyService like the next code, but this is the way what I want to use:

export class HelperService<T> {
  constructor(
    private proxy: ProxyService<T>,
  ) { }

Any idea how can I fix this issue?

7
  • 2
    The type property of ProxyService seems to be undefined in the first snippet or how do you assign a value to it? Commented Jan 2, 2020 at 17:01
  • that's the point... how can I create a type based on generic type T? Commented Jan 2, 2020 at 17:02
  • TypeScript's type system is erased when you compile to JavaScript. At runtime there is no T. If you want to call some constructor at runtime, you need a constructor somewhere; it's not avoidable. Commented Jan 2, 2020 at 17:03
  • I want do something like this: public type: T = new T(); Commented Jan 2, 2020 at 17:04
  • 2
    Possible duplicate of Create a new object from type parameter in generic class, and frankly, many others Commented Jan 2, 2020 at 17:06

1 Answer 1

7

You seem to be confusing the TypeScript type annotations with runtime JavaScript concrete implementations.

In TypeScript you cannot do a new T(), because T is just a type annotation and has no concrete implementation.

Your type field undefined so naturally private emptyModel: T = new this.type(); will fail.

I think you were onto the right solution.

Create the Proxy class not as a singleton, but as a class.

Then create a service that exposes a factory method for returning an instance.

export class ProxyFactoryService {

    constructor(private httpClient: HttpClient) { }

    proxyFactory<T>(newable: new() => T) {
      const instance = new newable(); 
      return new ProxyService<T>(this.httpClient, instance);
    }
}

Then your ProxyService can look something like:

 export class ProxyService<T> {
   private localstorageService: LocalstorageDataService<T>;
   private remoteDataService: RemoteDataService<T>;

   constructor(private httpClient: HttpClient, private instance: T) {
     this.localstorageService = new LocalstorageDataService(this.instance, this.httpClient);
     this.remoteDataService = new RemoteDataService(this.instance, this.httpClient)
   }

   getOne(id: number): Observable<T> {
     if (this.instance.use_localstorage) { // we probably want to make sure T extends {use_localStorage: boolean}
       return of(this.localstorageService.findById(id));
     } else {
       return this.remoteDataService.getOne(id).pipe(map((result: T) => result));
    }
  }
  // other proxy functions are here...
}

To use this

constructor(private proxyService: ProxyService) {}

ngOnInit() {
  const proxy = this.proxyService.proxyFactory(SomeClass);
  proxy.getOne(...);
}

Always ask yourself the question: How would this work with annotations removed?

When TypeScript compiles all type annotations are removed -- these are just design time static type annotations.

Another way to think of it is this simplification -- we will remove the generics out and use concrete types. But in essence this boils down what you were trying to do:

const s: new () => String;
const i = new s();

So s is a constructor that when called returns a type String. This will not work because s is undefined. We know what the type it is supposed to be, but in fact it is undefined. This is all very contrived -- but I hope this helps.

If we look at the compiler output for the above, it should be more clear why this wont work.

const s;
const i = new s();

TypeScript luckily will error before we ever actually get a chance to run this code. If your IDE is setup right you should get lots of warnings and errors at design time.

I understand this can be frustrating and confusing, especially coming from languages like C# and Java where the CIL/CLR is statically type aware. In TypeScript there is no runtime, it is just compiled to JavaScript.

Static typing and type annotations are not available at runtime.

Sign up to request clarification or add additional context in comments.

2 Comments

Hi Martin, do you know how to call typesript class decorator with new newable();? I am surprised that I didn't use it.
Ok, I have find out. Metamodel is hidden under instance.constructor.

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.