29
votes

So I'm trying to make a directive that can manipulate a FormControl.

It seems that if I use the long syntax for declaring form controls in the template instead, I can pass the control to a directive to do stuff with it as a direct @Input() bind; i.e.: With the following template:

<form [formGroup]="myForm">
    <input type="text" id="myText" [formControl]="myForm.controls['myText']" my-directive>
</form>

And the following component logic:

@Component({
    // Properties go here.
})
class MyComponent {
    myForm: FormGroup;

    constructor(fb: FormBuilder) {
        // Constructor logic...
    }

    ngOnInit() {
        this.myForm = this.fb.group({
            "myText": [""]
        });
    }
}

The directive would look like:

@Directive({
    selector: "[my-directive]"
})
class MyDirective {
    Input() formControl: FormControl;
}

But if I were using the formControlName syntax in the template instead:

<form [formGroup]="myForm">
    <input type="text" id="myText" formControlName="myText" my-directive>
</form>

How would I reference the (implicitly?) made FormControl in the directive?

2
It's unclear what you're asking. You can apply a directive to a reactive input with either method of template binding, formControlName="someName" or [formControl]="myForm.controls['someName']" near as I can tell it doesn't make a difference. If you post your directive and why its not working for you then perhaps you can get an answer.silentsod
As far as I know, the formControlName syntax doesn't place the FormControl instance on the element, so without an explicit binding, I don't know how to access it. I've edited the question, hopefully it's more clear.Thomas Tran

2 Answers

59
votes

If you utilize NgControl, ElementRef, HostListener and constructor injection we can have a directive applicable to form controls from reactive forms in either formControlName or [formControl] guise and even template driven forms:

import { Directive, ElementRef, HostListener } from "@angular/core";
import { NgControl } from "@angular/forms";

@Directive({
  selector: '[my-directive]'
})
export class MyDirective {
  constructor(private el: ElementRef, private control : NgControl) { }

  @HostListener('input',['$event']) onEvent($event){
    let valueToTransform = this.el.nativeElement.value;
    // do something with the valueToTransform
    this.control.control.setValue(valueToTransform);
  }
}

Here's an applicable demo

6
votes

@silentsod answer would work flawlessly.

1. if you need to handle multiple events like key-press up/down or any other events, then you can go with below approach.
2. Also, it's better to define events in the directive itself.

import { Directive, ElementRef} from "@angular/core";
import { NgControl } from "@angular/forms";

@Directive({
  selector: '[my-directive]',
  host: {
   '(input)':'onEvent($event)',
   '(keydown.backspace)': 'onEvent($event, true)'
})
export class MyDirective {
  constructor(private el: ElementRef, private control : NgControl) { }

  public onEvent($event, someEvent){
    let valueToTransform = this.el.nativeElement.value;
    // do something with the valueToTransform
    if(someEvent) {
    //do something 
    }
    this.control.control.setValue(valueToTransform);
  }
}

In the Html

<form [formGroup]="myForm">
<input type="text" id="myText" formControlName="myText" my-directive>
</form>