Есть еще один подход с использованием директивы.  Идея состоит в том, что мы получаем весь div с нашей директивой в нашем app.component с помощью ViewChildren, затем наш div с директивой отправляет событие и вызывает функцию нашего app.component.  Таким образом, app.component становится похожим на
<div arrow-div (event)="handler($event)>my div</div>
<div arrow-div (event)="handler($event)>my div</div>
...
Но мы можем использовать «сервис», чтобы сделать вещи более «прозрачными».
Представьте себе такую услугу, как
@Injectable({
  providedIn: 'root',
})
export class KeyBoardService {
  keyBoard:Subject<any>=new Subject<any>();
  sendMessage(message:any)
  {
    this.keyBoard.next(message)
  }
}
Наша директива может вызывать службу sendMessage при нажатии стрелки на клавиатуре, и в нашем app.component подписаться на эту службу.  а затем наш app.component похож на
<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>
Мы избегаем этого "уродливого" (event) = "handler ($ event)" в наших div !!
Ну, директива проста, используя @Hostlistener для прослушивания ключа и renderer2 для добавления атрибута tabindex (чтобы сделать div фокусируемым, нам нужно добавить tabIndex).  Так
@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;
    }
  }
}
И наш 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();
  }
}
Обратите внимание, что я использовал переменную «столбец», если мы создаем «сетку» с столбцами и строками и используем клавиши вверх и вниз для перемещения между строками.  Отправляя «элемент», избегайте того, чтобы мы сохранили «сфокусированный на div»
Вы можете увидеть пример в stackblitz.
                        
                            16.06.2019