Angular: I want to move focus of div from to another on arrow key buttons
keypress
does not detect the arrow keys
, instead use keydown
. In order to receive the focus and listen to the key events add attribute tabindex
to the divs.
For right arrow key, you need to check if current active element is not a last element, and change the focus to the next element.
For left arrow key, check whether current active element is not a first element and then change the focus to the previous element.
--HTML--
<div class="container">
<div tabindex="0" class="cell" (keydown)="moveCell($event)">cell 2</div>
<div tabindex="1" class="cell" (keydown)="moveCell($event)">cell 3</div>
<div tabindex="2" class="cell" (keydown)="moveCell($event)">cell 4</div>
<div tabindex="3" class="cell" (keydown)="moveCell($event)">cell 5</div>
<div tabindex="4" class="cell" (keydown)="moveCell($event)">cell 6</div>
<div tabindex="5" class="cell" (keydown)="moveCell($event)">cell 7</div>
<div tabindex="6" class="cell" (keydown)="moveCell($event)">cell 8</div>
</div>
--Component code--
length: 0;
domEles;
moveCell(e){
const activeEle = document.activeElement;
const activeEleIndex = Array.prototype.indexOf.call(this.domEles, activeEle);
if(e.key == "ArrowRight" && activeEleIndex < this.length - 1 ) {
activeEle.nextElementSibling.focus();
}
if(e.key == "ArrowLeft" && activeEleIndex > 0) {
activeEle.previousElementSibling.focus();
}
}
ngOnInit() {
this.domEles = document.querySelectorAll('.container > *');
this.length = this.domEles.length;
}
Working Code
- https://stackblitz.com/edit/angular-5qxicw.
There're another aproach using directive. Well, the idea is that we get all the div that has our directive in our app.component using ViewChildren, then our div with a directive send an event and call a function of our app.component. So the app.component becomes like
<div arrow-div (event)="handler($event)>my div</div>
<div arrow-div (event)="handler($event)>my div</div>
...
But we can use a "service" to make the things more "transparents".
Imagine a service like
@Injectable({
providedIn: 'root',
})
export class KeyBoardService {
keyBoard:Subject<any>=new Subject<any>();
sendMessage(message:any)
{
this.keyBoard.next(message)
}
}
Our directive can call to the service "sendMessage" when a key arrow is pressed, and in our app.component subscribe to this service. and then our app.component is some like
<div arrow-div >my div</div>
<div arrow-div >my div</div>
<br/>
<div arrow-div >my div</div>
<div arrow-div >my div</div>
We avoid this "ugly" (event)="handler($event)" in our divs!!
Well, the directive in simple, using @Hostlistener to listen the key, and renderer2 to add the attributte "tabindex" (to make a div focusable, we need add tabIndex). So
@Directive({
selector: '[arrow-div]',
})
export class ArrowDivDirective {
constructor(private keyboardService: KeyBoardService, public element: ElementRef, private render: Renderer2) {
this.render.setAttribute(this.element.nativeElement, "tabindex", "0")
}
@HostListener('keydown', ['$event']) onKeyUp(e) {
switch (e.keyCode) {
case 38:
this.keyboardService.sendMessage({ element: this.element, action: 'UP' })
break;
case 37:
this.keyboardService.sendMessage({ element: this.element, action: 'LEFT' })
break;
case 40:
this.keyboardService.sendMessage({ element: this.element, action: 'DOWN' })
break;
case 39:
this.keyboardService.sendMessage({ element: this.element, action: 'RIGTH' })
break;
}
}
}
And our app.component.ts
export class AppComponent implements OnInit {
columns:number=2;
@ViewChildren(ArrowDivDirective) inputs:QueryList<ArrowDivDirective>
constructor(private keyboardService:KeyBoardService){}
ngOnInit()
{
this.keyboardService.keyBoard.subscribe(res=>{
this.move(res)
})
}
move(object)
{
const inputToArray=this.inputs.toArray()
let index=inputToArray.findIndex(x=>x.element==object.element);
switch (object.action)
{
case "UP":
index-=this.columns;
break;
case "DOWN":
index+=this.columns;
break;
case "LEFT":
index--;
break;
case "RIGTH":
index++;
break;
case "RIGTH":
index++;
break;
}
if (index>=0 && index<this.inputs.length)
inputToArray[index].element.nativeElement.focus();
}
}
See that I used a variable "column" if we are making a "grid" with cols and rows and use the up and down keys to move between rows. Sending the "element" avoid we must have stored the "div focused"
You can see an example in stackblitz