Search code examples
angularangular-controlvalueaccessor

Angular ControlValueAccessor read selectionStart and selectionEnd


I have a directive in Angular that should read it's host element's input selectionStart, selectionEnd values. I do this in my custom directive:

@Directive({
  selector: '[inputBehavior]',
})
export class InputBehaviorDirective {
  public constructor(
    private el: ElementRef
  ) {}

  @HostListener('keydown', ['$event'])
  onKeyDown(event: KeyboardEvent): void {
    let { selectionStart, selectionEnd } = this.el.nativeElement as HTMLInputElement;
  }
}

host element's template looks like this:

<div class="input-field">
    <div class="container">
        <input [formControl]="formElement" />
    </div>
</div>

host's typescript file:

@UntilDestroy()
@Component({
  selector: 'input-field',
  templateUrl: './input-field.component.html',
  styleUrls: ['./input-field.component.scss'],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => InputFieldComponent),
      multi: true,
    },
  ],
})
export class InputFieldComponent implements ControlValueAccessor {
  public formElement = new FormControl();

  public writeValue(obj: number | string | undefined): void {
    this.formElement.setValue(obj?.toString());
  }

  public registerOnChange(fn: any): void { }
  public registerOnTouched(fn: any): void {}
  public setDisabledState?(isDisabled: boolean): void {}
}

i use directive like this:

<input-field
    InputBehavior
    formControlName="height"
>
</input-field>

but selectionStart and selectionEnd stay undefined. I want to apply the directive to the ControlValueAccessor host element as whole and not to the native html input element within host element's tempalte. So NOT like this:

<div class="input-field">
    <div class="container">
        <input InputBehavior 
               [formControl]="formElement" 
        />
     </div>
</div>

so my question is, how to read selectionStart and selectionEnd values of the host element by applying the directive to it?


Solution

  • You can "reach" the input using el.nativeElement.getElementsByTag('input')[0]

    @HostListener('keydown', ['$event'])
      onKeyDown(event: KeyboardEvent): void {
        const inputElement=this.el.nativeElement.getElementsByTag('input')[0]
        let { selectionStart, selectionEnd } = inputElement as HTMLInputElement;
        console.log(selectionStart, selectionEnd)
      }
    

    Another option can be change your directive in the way

    export class InputBehaviorDirective {
      public inputElement!:HTMLInputElement; //<--add a variable public
      public constructor(
        private el: ElementRef
      ) {}
    
      @HostListener('keydown', ['$event'])
      onKeyDown(event: KeyboardEvent): void {
        //use the variable inputElement
        let { selectionStart, selectionEnd } = this.inputElement as HTMLInputElement;
        console.log(selectionStart, selectionEnd)
      }
    }
    

    And your InputFieldComponent like

    The template

    <div class="input-field">
      <div class="container">
          <!--Add a template reference variable-->
          <input #myinput [formControl]="formElement" />
      </div>
    </div>
    

    In your .ts, inject in constructor the directive and use ViewChild with a setter

      @ViewChild('myinput',{static:true}) set _myInput(value:any)
      {
        if (this.behaviorDirective)
             this.behaviorDirective.inputElement=value;
      }
    
      constructor(@Host() @Optional() private behaviorDirective:InputBehaviorDirective){
      }