Zanim zabierzemy się za reflect metadata albo inne biblioteczki, które robią to za nas, spróbujemy napisać walidację własnymi dekoratorami, bo to jedyny znany mi sposób, aby się czegoś nauczyć porządnie. Do dzieła.

Ok, nasze dekoratory:

function Atempt1(target: any, propName: string) {
  let rules = target.constructor.validatorRules;
  target.constructor.validatorRules = {...rules, rule1: 'rule1'};
  console.log(target.constructor);
}

function Atempt2(target: any, propName: string) {
  let rules = target.constructor.validatorRules;
  target.constructor.validatorRules = {...rules, rule2: 'rule2'};
  console.log(target.constructor);
}

Warto to sobie uświadomić:

  • Dekorator odpalany jest jeden raz, przy definicji klasy (nieważne, czy jakiś obiekt jest utworzony oraz ile tych obiektów utworzonych)
  • Dekorator nie ma zatem dostępu do obiektu
  • Dekorator ma dostęp do prototypu

Ok, rzućmy okiem na klasę:

class StupidClass {
  @Atempt1
  @Atempt2
  name: string;
  age: number;
  constructor(name, age){
    this.name = name;
    this.age = age;
  }
  // @enumerable(true)
  //   @deprecated
    sayHello() {
      console.log(`Hello, my name is ${this.name}!`);
    }
}

let stupidObj = new StupidClass("jim", 30);

for (const property in stupidObj) {
  console.log(`${property}: ${stupidObj[property]}`);
}

Jak to sobie odpalimy, to zobaczymy, że prototyp klasy ma pododawane te zasady. Problem jest taki, że nigdzie nie jest podpisane do jakiego property one są.

function Atempt1(target: any, propName: string) {
  if(target.constructor.validatorRules === undefined){
    target.constructor.validatorRules = {
      [propName]: {}
    };
  }
  let rules = target.constructor.validatorRules;
  target.constructor.validatorRules = {...rules, rule1: 'rule1'};
  console.log(target.constructor);
}

function Atempt2(target: any, propName: string) {
  if(target.constructor.validatorRules === undefined){
    target.constructor.validatorRules = {
      [propName]: {}
    };
  }
  let rules = target.constructor.validatorRules;
  target.constructor.validatorRules = {...rules, rule2: 'rule2'};
  console.log(target.constructor);
}

Teraz już lepiej, choć nasze rules nadal są poza tym property. Ok, kolejna próba:

function Atempt1(target: any, propName: string) {
  if(target.constructor.validatorRules === undefined){
    target.constructor.validatorRules = {
      [propName]: {}
    };
  }
  let rules = target.constructor.validatorRules;
  target.constructor.validatorRules[propName] = {...rules[propName], rule1: 'rule1'};
  console.log(target.constructor);
}

function Atempt2(target: any, propName: string) {
  if(target.constructor.validatorRules === undefined){
    target.constructor.validatorRules = {
      [propName]: {}
    };
  }
  let rules = target.constructor.validatorRules;
  target.constructor.validatorRules[propName] = {...rules[propName], rule2: 'rule2'};
  console.log(target.constructor);
}

Już lepiej, teraz wygląda to tak:

class StupidClass { constructor(name, age) }

//(...)
​​
name: Object { rule2: "rule2", rule1: "rule1" }

//(...)

}

Ok, teraz zrobimy z nich wszystkich factory:

function Atempt1(min:Number){
  return function(target: any, propName: string) {
    if(target.constructor.validatorRules === undefined){
      target.constructor.validatorRules = {
        [propName]: {}
      };
    }
    let rules = target.constructor.validatorRules;
    target.constructor.validatorRules[propName] = {...rules[propName], min: min};
    console.log(target.constructor);
  }
}


function Atempt2(max: number){
  return function(target: any, propName: string) {
    if(target.constructor.validatorRules === undefined){
      target.constructor.validatorRules = {
        [propName]: {}
      };
    }
    let rules = target.constructor.validatorRules;
    target.constructor.validatorRules[propName] = {...rules[propName], max: max};
    console.log(target.constructor);
  }
}

Musimy jeszcze podać te argumenty w klasie:

class StupidClass {
  @Atempt1(1)
  @Atempt2(100)
  name: string;
  age: number;
  constructor(name, age){
    this.name = name;
    this.age = age;
  }
  // @enumerable(true)
  //   @deprecated
    sayHello() {
      console.log(`Hello, my name is ${this.name}!`);
    }
}

I teraz mamy już:

name: Object { max: 100, min: 1 }

Oczywiście w prototypie, nie w obiekcie. Ok, niech to się w głowie ułoży, w następnej lekcji postaramy się zrobić walidator. Jak nie wyjdzie z define property to będzie może trzeba użyć proxy (zwracanego z konstruktora) albo pomyśleć nad jakimś wywaleniem validate na zewnątrz (widziałem już takie kody, ale ich przeznaczenie było inne – walidacja obiektu na żądanie, nie na zasadzie wywołującej się automatycznie).