crystalfinch
9/17/2018 - 9:07 PM

Accessible Angular Input Component

Accessible Angular Input Component

import { environment } from '../environments/environment';

export class Logger {

  static log(msg: string | object): void {
    if (!environment.production) {
      console.log(msg);
    }
  }

  static error(msg: string): void {
    if (!environment.production) {
      console.error(msg);
    }
  }
}
<form>
  <input-field labelText="First Name">
    <input inputRef id="firstName" type="text" required />
  </input-field>
  <button type="submit">Submit</button>
</form>
<label *ngIf="showLabel()" [for]="input.element.id" [textContent]="labelText"></label>
<ng-content></ng-content>
import { AfterContentInit, AfterViewInit, Component, ContentChild, Input } from '@angular/core';
import { InputRefDirective } from './input-ref.directive';
import { Logger } from './app-logger';

@Component({
  selector: 'input-field',
  templateUrl: './input-field.component.html',
})
export class InputFieldComponent implements AfterViewInit, AfterContentInit {

  @Input() labelText: string;
  @Input() labelHidden = false;
  @ContentChild(InputRefDirective) input: InputRefDirective;

  constructor() {}

  ngAfterContentInit() {
    if (!this.input) {
      Logger.error('Input required in input-field component. If input is present, add inputRef directive to element.');
    }
  }

  ngAfterViewInit() {
    if (!this.input.element.id) {
      Logger.error('An input with a unique ID is required in input-field component:');
      Logger.log(this.input);
    }

    if (!this.labelText) {
      Logger.error('labelText input required in input-field component:');
      Logger.log(this.input);
    }

    if (this.input && this.labelText && this.labelHidden) {
      this.input.element.setAttribute('aria-label', this.labelText);
    }
  }

  showLabel() {
    return this.labelText && !this.labelHidden;
  }
}
import { Directive, ElementRef, HostListener, OnInit } from '@angular/core';
import { NgControl } from '@angular/forms';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';

@Directive({
  selector: '[inputRef]'
})
export class InputRefDirective implements OnInit {

  element: HTMLInputElement;
  focus = false;
  focusSubject = new BehaviorSubject<boolean>(null);
  keyUpSubject = new BehaviorSubject<KeyboardEvent>(null);

  constructor( private el: ElementRef, public control: NgControl ) { }

  ngOnInit() {
    this.element = this.el.nativeElement;
    this.focusSubject.asObservable().subscribe((value) => {
      this.focus = value;
    });
  }

  hasError() {
    return this.control.errors;
  }

  @HostListener('focus')
  handleFocus() {
    this.focusSubject.next(true);
  }

  @HostListener('blur')
  handleBlur() {
    this.focusSubject.next(false);
  }

  @HostListener('keyup', ['$event'])
  handleKeyUp(event: KeyboardEvent) {
    this.keyUpSubject.next(event);
  }
}