angular - Typescript-generiske typer i en tilbakekalling
Jeg prøver å lage en generell tjeneste som vil hente noen eksterne data og opprette noen objekter med dem.
@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);
});
}
}
Dette gir meg feilen T refererer bare til type, men brukes som en verdi her. Ok, det er greit. Jeg forstår hva som skjer. Jeg har sett et par spørsmål som spør om noe lignende.
For eksempel:
- Hvordan lage et nytt objekt fra typeparameter i en generisk klasse i TypeScript?
- TypeScript returnerer objekt konstruert fra generisk
- Hent konstruktør/instans fra generisk type i TypeScript
Det virker som om enhver løsning jeg har kommet over enten hardkoder klassen på et tidspunkt, eller legger til T sin klassekonstruktør et sted. Men jeg kan ikke finne ut hvordan jeg skal gjøre det. Problemet mitt er at klassen som instansieres har parametere i konstruktøren, og jeg bruker Angulars DI-system, så jeg kan ikke (???) legge til parametere til tjenestens konstruktør.
constructor(private playersService: gService<Player>, private alliesService: gService<Ally>) { }
Kan noen finne ut hva jeg bør gjøre her? Jeg vil også helst at svaret ikke skal være en slags 'løsning', om mulig. Hvis det er et valg mellom å gjøre noe som knapt er lesbart og bare kopiere og lime tjenesten et par ganger og endre hvilken klasse det refererer til, vil jeg ta det andre alternativet.
Svar #1
Et klassenavn refererer både til typen av klassen, men også til konstruktøren, derfor kan du skrive både let x: ClassName og new ClassName(). En generisk parameter er kun en type (akkurat som et grensesnitt for eksempel), derfor klager kompilatoren på at du bruker den som en verdi (verdien som forventes er en konstruksjonsfunksjon). Det du trenger å gjøre er å sende inn en ekstra parameter som vil være konstruktøren 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)
Merk Parameterne til konstruktørsignaturen kan variere avhengig av ditt bruksområde.
Rediger
Du nevner at du ikke kan legge til argumenter til konstruktøren, generics (og alle typer generelt) blir slettet når vi kjører koden, så du kan ikke stole på T under kjøretid, et sted må noen sende inn klassekonstruktøren. Du kan gjøre dette på flere måter, den enkleste versjonen vil være å lage dedikerte klasser for hver T-instans, og deretter injisere den spesifikke klassen:
class tServiceForMyClass extends tService<MyClass> {
constructor(http: HttpClient) {
super(http, MyClass)
}
}
Eller du kunne gjøre arbeidet ditt avhengig av konstruktøren, i en init-metode som krever konstruktøren som en 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);
});
}
}
