Я пытаюсь создать общий сервис, который будет получать некоторые удаленные данные и создавать с их помощью некоторые объекты.
@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);
});
}
}
Это дает мне ошибку T only refers to type, but is being used as a value here. Хорошо, это нормально. Я понимаю, что происходит. Я видел пару вопросов о чем-то похожем.
Например:
Кажется, что любое решение, с которым я столкнулся, либо жестко кодируется в классе в какой-то момент, либо где-то добавляет конструктор класса T. Но я не могу понять, как это сделать. Моя проблема в том, что у создаваемого класса есть параметры в конструкторе, и я использую систему DI Angular, поэтому я не могу (???) добавлять параметры в конструктор службы.
constructor(private playersService: gService<Player>, private alliesService: gService<Ally>) { }
Кто-нибудь может понять, что мне здесь делать? Кроме того, я бы предпочел, чтобы ответ не был чем-то вроде «взлома», если это возможно. Если это выбор между тем, чтобы сделать что-то, что трудно читается, и просто скопировать и вставить службу пару раз и изменить, к какому классу она относится, я выберу второй вариант.
@AluanHaddad Не совсем, как бы это выглядело?
Вроде как class S { get<T>(C: new (x: Thing) => T): Promise<T> {...} }. Но теперь я вижу, что вы используете сам конструктор для запуска инициализации асинхронного процесса, который затем изменяет состояние уровня приложения. То есть ваша служба работает как побочный эффект внедрения службы (и, таким образом, создания экземпляра) чем-то, что в ней нуждалось ...
@AluanHaddad Хммм, да, наверное, не стоит делать асинхронные вещи в конструкторе. Читая между строк, я думаю, вы говорите, что я сделал пару ошибок в дизайне, из-за которых у меня возникла проблема?
Ну ... да, я предполагаю, что ты мог бы это сделать. Трудно сказать, поскольку у вас может быть вариант использования, который я не рассматривал. Что меня раздражает, так это то, что общий параметр имел бы смысл для класса обслуживания только в том случае, если бы был создан ровно один экземпляр для каждого отдельного параметра типа, используемого с ним. Это синглтон или в приложении несколько экземпляров?
@AluanHaddad IIRC, система DI создает по одному экземпляру для каждого типа, используемого во всем приложении.
Это зависит от того, как это предусмотрено. Вы можете предоставлять услуги на уровне компонентов, приложений и другими способами (модули ленивых общих функций имеют разные области действия).





Имя класса относится как к типу класса, так и к его конструктору, поэтому вы можете писать как let x: ClassName, так и new ClasName(). Универсальный параметр - это только тип (во многом как, например, интерфейс), поэтому компилятор жалуется, что вы используете его как значение (ожидаемое значение является функцией конструктора). Что вам нужно сделать, так это передать дополнительный параметр, который будет конструктором для 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)
Примечание Параметры сигнатуры конструктора могут различаться в зависимости от вашего варианта использования.
Редактировать
Вы упоминаете, что вы не можете добавлять аргументы в конструктор, универсальные шаблоны (и все типы в целом) стираются, когда мы запускаем код, поэтому вы не можете зависеть от T во время выполнения, где-то кто-то должен будет передать класс конструктор. вы можете сделать это несколькими способами, самый простой вариант - создать выделенные классы для каждого экземпляра T, а затем ввести конкретный класс:
class tServiceForMyClass extends tService<MyClass> {
constructor(http: HttpClient) {
super(http, MyClass)
}
}
Или вы можете выполнять свою работу в зависимости от конструктора в методе init, который требует конструктор в качестве параметра:
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);
});
}
}
Я использую Angular DI. Это не так. Когда я пытался адаптировать то, что у вас есть, у меня ничего не получалось. Can't resolve all parameters for tService: ([object Object], ?).
@Shane кому-то нужно будет передать конструктор для T. Я не уверен, кто Angular DI имеет дело с дженериками, но поскольку дженерики - это просто конструкция типа компиляции, я думаю, что они просто создают новый tService, и им плевать на T.
@Shane добавил несколько параметров, но вам нужно будет каким-то образом передать конструктор для T, машинописный текст не будет знать, что делать с new T
Передача конструктора является правильным способом, но вы правы, что это неудобно (хотя вполне возможно) делать это с абстракцией Angular DI. Рассматривали ли вы замену универсального класса универсальным методом?