Search code examples
angularkeyboard-eventsarrows

Focus does not work when pressing ArrowDown and ArrowUp with HostListener


I am looking for a way to focus/highlight on each div element with ArrowUp and ArrowDown keys. With tabindex as 0 in the div element, the first value in the drop down gets a highlighted border but is still not selected and not sure how to use tabIndex to iterate through all the values with arrowdown and arrowup. I have the below component with the html file. Any help would be appreciated.

HTML

<div class="input">
<input type="text" #input [value]="value" >
  <div *ngIf="values">
    <div *ngFor="let value of values; let i = index">
      <div #item tabindex="i">{{value}}</div>
    </div>
  </div>

TS

  @HostListener('window:keydown', ['$event']) keyEvent(event: KeyboardEvent) {
  if (event.key == 'ArrowDown') {
    this.item.nativeElement.focus();
  } else if (event.key == 'ArrowUp') {
     this.item.nativeElement.focus();
  } else {
    //update
  }
}

Solution

  • in this SO you has a method, in this another SO can serve as "inspiration"

    Well, Using the last. the idea is to has a directive that has three variables prev,self and next

    @Directive({
      selector: "[next-tab]"
    })
    export class NextTabDirective {
      self: any;
      next: any;
      prev: any;
    
      @HostListener("keydown.arrowup", ["$event"])
      onUp(event: KeyboardEvent) {
        if (this.prev && this.prev.focus) {
          this.prev.focus();
          this.prev.select();
          event.preventDefault();
          return false;
        }
      }
    
      @HostListener("keydown.arrowdown", ["$event"])
      onDown(event: KeyboardEvent) {
        if (this.next && this.next.focus) {
          this.next.focus();
          this.next.select();
          event.preventDefault();
          return false;
        }
      }
    
      constructor(private control: ElementRef) {
        this.self = control.nativeElement;
      }
    }
    

    Well, we are going to create a new directive that take account the inner "next-tab"

    export class ManageArrowDirective implements AfterViewInit {
      @ContentChildren(NextTabDirective) controls: QueryList<NextTabDirective>;
    
      ngAfterViewInit() {
        const controls=this.controls.toArray()
        controls.forEach((x, index) => {
          x.prev = index ? controls[index - 1].self : null;
          x.next =
            index < controls.length - 1 ? controls[index + 1].self : null;
        });
        this.controls.changes.subscribe(ctrls => {
          const controls=ctrls.toArray();
          controls.forEach((x, index) => {
            x.prev = index ? controls[index - 1].self : null;
            x.next =
              index < controls.length - 1 ? controls[index + 1].self : null;
          });
        });
      }
    }
    

    You use

    <div mannage-arrow>
       <input next-tab ...>
       <input next-tab ...>
    </div>
    

    The directive "NextTabDirective" can be applied to ngControls, so we can control if it's disabled or not

    Here we go!

    First inject in constructor of our directive the ngControl as public

      constructor(private control: ElementRef,@Optional()public ngControl:NgControl) {
        this.self = control.nativeElement;
      }
    

    And then, implements a few complex directive to take accont can be controls disabled

    export class ManageControlDirective implements AfterViewInit {
      @ContentChildren(NgControlDirective,{descendants:true}) controls: QueryList<NgControlDirective>;
    
      ngAfterViewInit() {
        this.rearrange();
        this.controls.changes.subscribe(ctrls => {
          this.rearrange(ctrls.toArray())
        });
      }
      rearrange(controls:NgControlDirective[]=null)
      {
        setTimeout(()=>{
        controls=controls||this.controls.toArray()
          controls.forEach((x, index) => {
            x.prev = this.getPrevControlEnabled(index,controls)
            x.next = this.getNextControlEnabled(index,controls)
          });
    
        })
    
      }
      getPrevControlEnabled(index,controls:NgControlDirective[]){
        if (index)
            return controls[index-1].ngControl.enabled?controls[index-1].self:this.getPrevControlEnabled(index-1,controls)
        return null;
      }
      getNextControlEnabled(index,controls:NgControlDirective[]){
        if (index<controls.length-1)
            return controls[index+1].ngControl.enabled?controls[index+1].self:this.getNextControlEnabled(index+1,controls)
        return null;
      }
    }
    

    See a example of both examples in this stackbliz