creating a decorator method that passes an argument to angular4 ngOnInit()

1k Views Asked by At

I am attempting to pass a validator method into an angular4 reactive form for validation purposeses using a decorator.

component

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

decorator attempt - incomplete

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
  }
}

How do I get the decorator to pass its argument to the entry control in the form?

Cheers

1

There are 1 best solutions below

2
On

Try something like

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;
  }
}

The reason for the generic is simply to ensure that the method is defined by the target object. It is not necessary but I find it valuable.

To pass the validation array, vals, to the framework when the method is called is more complex.

The simplest way is to store the validators themselves on a field of the class so they are available but that sort of defeats the purpose of using a decorator.

For example we might write

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

Such that our class would look like

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'];
  }
}

As you can see, we do not touch the descriptor in this case since we are simply using the decorator to attach validators to the class. But that makes decorator use superfluous since we could more readably write

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

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

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

Method decorators are useful for validating for wrapping a method body. Often they can be used to validate arguments to the method but they cannot inject code into the implementation itself.

For example, if we wish to validate the arguments passed to a method we could write a decorator like

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);
    };
  };
}

We could then apply it to an arbitrary method and pass in validators as follows

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.');
  }
}