Дженерики машинописного текста в обратном вызове

Я пытаюсь создать общий сервис, который будет получать некоторые удаленные данные и создавать с их помощью некоторые объекты.

@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>) { }

Кто-нибудь может понять, что мне здесь делать? Кроме того, я бы предпочел, чтобы ответ не был чем-то вроде «взлома», если это возможно. Если это выбор между тем, чтобы сделать что-то, что трудно читается, и просто скопировать и вставить службу пару раз и изменить, к какому классу она относится, я выберу второй вариант.

Передача конструктора является правильным способом, но вы правы, что это неудобно (хотя вполне возможно) делать это с абстракцией Angular DI. Рассматривали ли вы замену универсального класса универсальным методом?

Aluan Haddad 05.04.2018 07:47

@AluanHaddad Не совсем, как бы это выглядело?

Shane 05.04.2018 07:50

Вроде как class S { get<T>(C: new (x: Thing) => T): Promise<T> {...} }. Но теперь я вижу, что вы используете сам конструктор для запуска инициализации асинхронного процесса, который затем изменяет состояние уровня приложения. То есть ваша служба работает как побочный эффект внедрения службы (и, таким образом, создания экземпляра) чем-то, что в ней нуждалось ...

Aluan Haddad 05.04.2018 07:53

@AluanHaddad Хммм, да, наверное, не стоит делать асинхронные вещи в конструкторе. Читая между строк, я думаю, вы говорите, что я сделал пару ошибок в дизайне, из-за которых у меня возникла проблема?

Shane 05.04.2018 07:56

Ну ... да, я предполагаю, что ты мог бы это сделать. Трудно сказать, поскольку у вас может быть вариант использования, который я не рассматривал. Что меня раздражает, так это то, что общий параметр имел бы смысл для класса обслуживания только в том случае, если бы был создан ровно один экземпляр для каждого отдельного параметра типа, используемого с ним. Это синглтон или в приложении несколько экземпляров?

Aluan Haddad 05.04.2018 07:59

@AluanHaddad IIRC, система DI создает по одному экземпляру для каждого типа, используемого во всем приложении.

Shane 05.04.2018 08:41

Это зависит от того, как это предусмотрено. Вы можете предоставлять услуги на уровне компонентов, приложений и другими способами (модули ленивых общих функций имеют разные области действия).

Aluan Haddad 05.04.2018 08:47
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Angular и React для вашего проекта веб-разработки?
Angular и React для вашего проекта веб-разработки?
Когда дело доходит до веб-разработки, выбор правильного front-end фреймворка имеет решающее значение. Angular и React - два самых популярных...
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Мы провели Twitter Space, обсудив несколько проблем, связанных с последними дополнениями в Angular. Также прошла Angular Tiny Conf с 25 докладами.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
Мое недавнее углубление в Angular
Мое недавнее углубление в Angular
Недавно я провел некоторое время, изучая фреймворк Angular, и я хотел поделиться своим опытом со всеми вами. Как человек, который любит глубоко...
Освоение Observables и Subjects в Rxjs:
Освоение Observables и Subjects в Rxjs:
Давайте начнем с основ и постепенно перейдем к более продвинутым концепциям в RxJS в Angular
8
7
536
1

Ответы 1

Имя класса относится как к типу класса, так и к его конструктору, поэтому вы можете писать как 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 05.04.2018 08:43

@Shane кому-то нужно будет передать конструктор для T. Я не уверен, кто Angular DI имеет дело с дженериками, но поскольку дженерики - это просто конструкция типа компиляции, я думаю, что они просто создают новый tService, и им плевать на T.

Titian Cernicova-Dragomir 05.04.2018 08:46

@Shane добавил несколько параметров, но вам нужно будет каким-то образом передать конструктор для T, машинописный текст не будет знать, что делать с new T

Titian Cernicova-Dragomir 05.04.2018 08:56

Другие вопросы по теме