Передача переменных внедренным службам для использования в качестве аргумента декоратора

#angular #inheritance #decorator

Вопрос:

У меня есть угловой проект, в котором используются сервисы, которые очень часто повторяются с типичным созданием/чтением/обновлением/удалением/списком всех записей для чтения. Я также очень часто хочу манипулировать полученными данными аналогичными способами после их получения. Поэтому я написал декораторы, которые используют apply , которые «преобразуют» данные способом, определяемым переданным им параметром (параметр всегда является классом объектов).

При обычном использовании это выглядит так:

 //rule.service.ts
@Injectable({
  providedIn: 'root'
})
export class RuleService {

  rulesUrl: string = `${Constants.wikiApiUrl}/rule/`;

  constructor(private http: HttpClient) {}

  @TransformObservable(RuleObject)
  getRule(pk: number): Observable<Rule>{
    return this.http.get<Rule>(`${this.rulesUrl}/${pk}`);
  }
}

 

Это часто случается, поэтому я хочу написать родительский сервис, который я могу расширить:

 //generic-object.service.ts

@Injectable({
  providedIn: 'root'
})
export abstract class GenericObjectService{
  baseUrl: string;

  constructor(
    private http: HttpClient,
    public objectClass: any,
  ) {}

  @TransformObservable(this.objectClass) //This line won't work
  read(pk: number): Observable<any>{
    return this.http.get(`${this.baseUrl}/${pk}`);
  }
}
 

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

Ниже также приведен «Наблюдаемый» декоратор:

 export function TransformObservable(modelClass: any){
    /**Decorator to apply transformObservableContent */
    return function(target: any, propertyKey: string, descriptor: PropertyDescriptor){

        const originalMethod = descriptor.value;
        descriptor.value = function(){
            const observable = originalMethod.apply(this, arguments);
            return transformObservableContent(observable, modelClass);
        }
        return descriptor;
    }
}
 

Ответ №1:

this Ключевое слово будет недоступно за пределами областей функций. Однако вы можете определить необходимый объект как поле/свойство в том же классе, и в аннотации декоратора вы можете просто передать строку, которую функция декоратора будет искать, или передать фактическую ссылку на объект.

В декораторе проверьте аргумент, который был передан аннотации:

 if(typeof modelClass === 'string'){
  // lookup a property in decorated class definition
  modelClass = target[modelClass]; // or, this[modelClass]
}else {
  // assume modelClass is the needed object
}
 

Это позволит вам использовать:

 @TransformObservable('propertyName');
// or
@TransformObservable(someObjectReference);
 

Различие между target и this :

target Переданная в декоратор функция-это объект, определяющий класс, в котором используется декоратор. Он будет иметь все свойства и методы, определенные в классе, и может быть использован для их извлечения, как показано выше. Однако target это не та среда выполнения this , которую вы обычно используете внутри методов класса. Это единственный метод замены ( descriptor.value ), который будет this доступен внутри него, как и оригинальный метод.

Следовательно, если вам действительно нужно this , а не только оформленное определение класса, вы должны переместить весь код внутри функции замены descriptor.value . Там у вас есть доступ ко this всем параметрам, которые передаются декоратору и фабрике декораторов.

Комментарии:

1. Ооо, итак, цель = объект, внутри которого написан декоратор. «это» = … На самом деле я не совсем уверен, какой контекст «это» внутри декоратора представляет в этот момент. Служба поддержки детей, которая начинает вызов?

2. Может быть, то, как вы отредактировали сейчас, неверно, я написал свой ответ, когда this еще был написан как target (для будущих читателей). this in TransformObservable не определен, target включает объект, который, по-видимому, является объектом, внутри которого написан декоратор, он же GenericObjectService . Однако по какой-то причине свойства GenericObjectService не отображаются в этом объекте, отображаются только функции.

3. @PhilippDoerner Обновил ответ.

4. Ах, я наконец понял, что проверка класса модели должна происходить в анонимной функции, которую я ввел в descriptor.value. Для заблудшего читателя я приведу ваше предложение, воплощенное на практике, в качестве другого решения ниже, но ваш ответ останется принятым.

5. @PhilippDoerner Правда. Вы должны переместить код внутри функции замены и выполнить проверки там, и вы можете использовать this его там.

Ответ №2:

Основываясь на ответе @S. D., я, наконец, понял, что он все это время пытался сказать: у вас есть доступ к this внутренней анонимной функции, которую вы помещаете в descriptor.value. Поэтому, чтобы мой декоратор мог правильно это сделать, я должен получить доступ modelClass изнутри к этой анонимной функции с помощью this , и я готов. Вот как выглядит решение от S. D., воплощенное в жизнь:

 export function TransformObservable(modelClass: any){
    /**Decorator to apply transformObservableContent */
    return function(target: any, propertyKey: string, descriptor: PropertyDescriptor){

        const originalMethod = descriptor.value;
        descriptor.value = function(){
            const observable = originalMethod.apply(this, arguments);

            //If the decorator argument was not a class but instead a property
            // name of a property on the object that uses the decorator that 
            // contains the class, update the modelClass variable
            const decoratorArgumentIsProperty = ["String", "string"].includes(typeof modelClass);
            if(decoratorArgumentIsTargetProperty){
                modelClass = this[modelClass];
            }

            return transformObservableContent(observable, modelClass);
        }
        return descriptor;
    }
}