I'm trying to make a generic service that will get some remote data and create some objects with it.
@Injectable()
export class tService<T> {
private _data: BehaviorSubject<T[]> = new BehaviorSubject([]);
private url = '...url...'; // URL to web api
constructor(private http: HttpClient) {
this.http.get<T[]>(this.url).subscribe(theThings => {
theThings = _.map(theThings, (theThing) => new T(theThing));
this._data.next(theThings);
});
}
}
This gives me the error T only refers to type, but is being used as a value here
. Ok, that's fine. I understand what is happening. I've seen a couple of questions asking about something similar.
For Example:
It seems that any solution that I've come across either hardcodes in the class at some point, or, adds T's class constructor in somewhere. But I can't figure out how to do it. My problem is that the class being instantiated has parameters in the constructor and I'm using Angular's DI system, so I can't (???) add parmeters to the service's constructor.
constructor(private playersService: gService<Player>, private alliesService: gService<Ally>) { }
Can anyone figure out what I should do here? Also, I'd prefer for an answer to not be some sort of 'hack', if possible. If it a choice between doing something that is barely readable and just copy and pasting the service a couple of times and changing what class it is referring to, I'll take the second option.
TypeScript fully supports generics as a way to introduce type-safety into components that accept arguments and return values whose type will be indeterminate until they are consumed later in your code.
Similar to JavaScript, to pass a function as a parameter in TypeScript, define a function expecting a parameter that will receive the callback function, then trigger the callback function inside the parent function.
A callback is a function passed as an argument to another function. This technique allows a function to call another function. A callback function can run after another function has finished.
Generics allow creating 'type variables' which can be used to create classes, functions & type aliases that don't need to explicitly define the types that they use. Generics makes it easier to write reusable code.
A class name refers to both the of the class type but also to it's constructor, that is why you can write both let x: ClassName
and new ClasName()
. A generic parameter is only a type (much like an interface for example), that is why the compiler complains that you are using it as a value (the value being expected being a constructor function). What you need to do is pass an extra parameter that will be the constructor for T
:
export class tService<T> {
private _data: BehaviorSubject<T[]> = new BehaviorSubject<T>([]);
private url = '...url...'; // URL to web api
constructor(private http: HttpClient, ctor: new (data: Partial<T>)=> T) {
this.http.get<T[]>(this.url).subscribe(theThings => {
theThings = _.map(theThings, (theThing) => new ctor(theThing));
this._data.next(theThings);
});
}
}
//Usage
class MyClass {
constructor(p: Partial<MyClass>) {
// ...
}
}
new tService(http, MyClass)
Note The parameters to the constructor signature may vary according to your use case.
Edit
You mention that you can't add arguments to the constructor, generics (and all types in general) are erased when we run the code, so you can't depend on T
at runtime, somewhere, someone will have to pass in the class constructor. you can so this in several ways, the simplest version would be to create dedicated classes for each T
instance, and then inject the specific class:
class tServiceForMyClass extends tService<MyClass> {
constructor(http: HttpClient) {
super(http, MyClass)
}
}
Or you could do your work dependent on the constructor, in an init
method that requires the constructor as a parameter:
export class tService<T> {
private _data: BehaviorSubject<T[]> = new BehaviorSubject<T>();
private url = '...url...'; // URL to web api
constructor(private http: HttpClient){}
init(ctor: new (data: Partial<T>)=> T) {
this.http.get<T[]>(this.url).subscribe(theThings => {
theThings = _.map(theThings, (theThing) => new ctor(theThing));
this._data.next(theThings);
});
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With