Reputation: 5997
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?
Upvotes: 2
Views: 9340
Reputation: 16292
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.
Upvotes: 7