ウェブサイト検索

TypeScript - Angular4 の ngOnInit() に引数を渡すデコレーターメソッドの作成


デコレーターを使用して、Angular4のリアクティブフォームにバリデーション目的でバリデータメソッドを渡そうとしています。

コンポーネント

@AutoUnsubscribe( [] )
@Component(
    {
      selector: 'pim-mat-input',
      template: `
        <form
            autocomplete = 'off'
            [formGroup] = 'form'>

          <section
              fxLayout = 'column'
              fxLayoutAlign = 'start start'>
            <mat-form-field>
              <input
                  #input
                  matInput
                  formControlName = 'entry'>
            </mat-form-field>
          </section>

        </form>
      `,
      styles: []
    } )
export class PimMatInputComponent implements AfterViewInit, OnDestroy, OnInit {  
  protected entryCtrl: AbstractControl
  protected form: FormGroup

  constructor( private _fb: FormBuilder ) {
  }

  @validators( [ PimNgxFormValidators.notEmpty ] )
  ngOnInit() {
    this.form = this._fb.group(
        { entry: [ '', [ /*PimNgxFormValidators.notEmpty goes here*/] ] } )

    this.entryCtrl = this.form.controls[ 'entry' ]

  }

  ngOnDestroy() {
    // needed for @AutoUnsubscribe()
  }

  ngAfterViewInit(): void {
  }
}

デコレーターの試み - 未完成

export function validators( vals: Array<Function> = [] ) {
  let form: FormGroup
  return function( target: any, member: string, descriptor: PropertyDescriptor ) {

    const originalMethod = descriptor.value

    descriptor.value = function( ...args ) {

     // What should I put here to allow the argument to be used in the component
    }
    return descriptor
  }
}

デコレーターにフォーム内のエントリーコントロールに引数を渡させるにはどうすればいいですか?

乾杯

回答 #1

例えばこんなことを試してみてください

export function validators(...vals: Array<(...args: any[]) => void>) {
  let form: FormGroup
  return function <T extends object>(target: T, key: keyof T, descriptor: PropertyDescriptor) {

    const originalMethod = descriptor.value

    descriptor.value = function(...args: any[]) {
       performValidation(...args);
       originalMethod.call(target, ...args);
    }
    return descriptor;
  }
}

ジェネリックの理由は単に、メソッドがターゲットオブジェクトによって定義されていることを保証するためです。必須ではありませんが、私はそれが有益だと考えています。

メソッドが呼び出されるときに、検証用配列 vals をフレームワークに渡すのは、より複雑です。

最も簡単な方法は、バリデーター自体をクラスのフィールドに保存して利用可能にすることですが、それはデコレーターを使う目的をある意味で台無しにします。

例えば私たちは書くかもしれません

export function validators(...vals: Array<(...args: any[]) => void>) {
  return function <T extends {validators: Array<(...args: any[]) => void>}>(target: T) {
    target.validators = vals;
  }
}

そのようにして私たちのクラスは見えるでしょう

export class PimMatInputComponent {
  validators: (...vals: Array<(...args: any[]) => void> = [];

  @validators([PimNgxFormValidators.notEmpty])
  ngOnInit() {
    this.form = this._fb.group({
      entry: [ '', [this.validators]]
    });

    this.entryCtrl = this.form.controls['entry'];
  }
}

ご覧のとおり、この場合はデコレーターを使ってクラスにバリデーターを付けるだけなので、デスクリプターには触れません。しかし、これではデコレーターの使用が不必要になります。なぜなら、より読みやすく書くことができるからです。

export class PimMatInputComponent {
  validators = [PimNgxFormValidators.notEmpty];

  ngOnInit() {
    this.form = this._fb.group({
      entry: [ '', [this.validators]]
    });

    this.entryCtrl = this.form.controls['entry'];
  }
}

メソッドデコレーターは、メソッドの本体をラップするためや検証するために役立ちます。しばしばメソッドへの引数を検証するために使用できますが、実装自体にコードを注入することはできません。

例えば、メソッドに渡される引数を検証したい場合、次のようなデコレーターを書くことができます

export function validateArguments(...validators: Array<(...as: any[]) => void>) {
  return <T extends object>(target, key: keyof T, descriptor: PropertyDescriptor) => {
    const originalMethod = descriptor.value;
    descriptor.value = (...args: any[]) => {
      args.forEach(argument => {
        validators.forEach(validate => {
          validate(argument);
        });
      });

      originalMethod.call(target, ...args);
    };
  };
}

その後、任意のメソッドにそれを適用し、次のようにバリデーターを渡すことができます

export class A {
  @validateArguments(arg => {
    if (arg === undefined) {
      throw Error('required parameter not specified');
    }
  })
  m(a, b, c) {
    console.log('If we got here no arguments were undefined.');
  }
}