1

I have an angular webapp using angular material. In my HTML I have two ButtonToggles that you can toggle with a simple click event handler which I handle myself. However, there should also be a way to toggle the buttons with a keyboard shortcut. I have a keypress event listener that correctly intercepts the keypress, but I have no idea how I can toggle the associated button because I can't pass it in to the event handler.

Here is my html

  <mat-button-toggle-group [multiple]="schema.multi">
        <mat-button-toggle *ngFor="let label of schema.labels"
                           (click)="toggleAnnotation(label, localButton)"
                           [value]="label.name"
                           #localButton
                           [style.background-color]="localButton.checked == true ? label.backgroundColor : 'ghostwhite'">
          {{label.name}} ({{label.shortcut}})
        </mat-button-toggle>
  </mat-button-toggle-group>
</div>

And the related typescript:

import {Component, EventEmitter, HostListener, Input, OnChanges, Output, SimpleChanges} from '@angular/core';
import {Label} from '../sequence/models/label';
import {TagAssignment} from '../../../models/tag-assignment';
import {MatButtonToggle, MatButtonToggleGroup} from "@angular/material/button-toggle";


export class CategoricalTaggerSchema {
  multi: boolean; // can the user select multiple tags at once
  prompt: string; // message to display before showing the tagger document
  labels: Label[]; // categories to select from
}

@Component({
  selector: 'app-categorical',
  templateUrl: './categorical-tagger.component.html',
  styleUrls: ['./categorical-tagger.component.css']
})
export class CategoricalTaggerComponent implements OnChanges {
  @Input() config: TagAssignment = new TagAssignment(); // default to some value
  @Output() valueChange = new EventEmitter<string>();
  @Output() validChange = new EventEmitter<boolean>();
  @Input() disabled = false;

  schema: CategoricalTaggerSchema = {multi: false, prompt: '', labels: []};
  annotations = new Set<string>(); // much simpler then sequence tagging, just a list of named labels

  constructor() {}

  ngOnChanges(changes: SimpleChanges): void {
    if (changes.hasOwnProperty('config')) {
      this.schema = JSON.parse(this.config.tag_schema);
    }
  }

  toggleAnnotation(label: Label, localButton) {
    if (!this.disabled) {
      if (this.annotations.has(label.name)) {
        this.annotations.delete(label.name);
        localButton.checked = false;
      } else { // creating new annotation
        if (!this.schema.multi) { // only one annotation allowed
          this.annotations.clear();

        }
        this.annotations.add(label.name);
      }
    }
    this.emitChanges();
    console.log(this.annotations);
  }

  emitChanges() {
    this.valueChange.emit(this.getValue());
    this.validChange.emit(this.isValid());
  }

  @HostListener('document:keypress', ['$event'])
  handleKeyboardEvent(event: KeyboardEvent) {
    // detect keypress for shortcut
    for (const label of this.schema.labels) {
      if (label.shortcut === event.key) {
        this.toggleAnnotation(label, null);
        break;
      }
    }
  }

  getValue(): string {
    return JSON.stringify(Array.from(this.annotations));
  }

  isValid(): boolean {
    return (this.annotations.size > 0);
  }

  reset(): void {
    this.annotations.clear();
  }
}

The only thing I can think of is somehow fire a function from the HTML on component load that adds all the toggle buttons to an array or map which the TS has access to, and search them up by shortcut when I need them, but this seems like a hacky solution.

EDIT: I've tried using ViewChild, but since I can't initialize the ids dynamically (angular viewChild for dynamic elements inside ngFor) i cannot access the components to modify their checked state.

rocks6
  • 152
  • 1
  • 10

0 Answers0