opf/openproject

View on GitHub
frontend/src/app/spot/components/checkbox/checkbox.component.ts

Summary

Maintainability
A
3 hrs
Test Coverage
import {
  Component,
  ElementRef,
  EventEmitter,
  forwardRef,
  HostBinding,
  Input,
  Output,
  ViewChild,
  ChangeDetectionStrategy,
  ChangeDetectorRef,
} from '@angular/core';
import {
  ControlValueAccessor,
  NG_VALUE_ACCESSOR,
} from '@angular/forms';

export type SpotCheckboxState = true|false|null;

@Component({
  selector: 'spot-checkbox',
  templateUrl: './checkbox.component.html',
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => SpotCheckboxComponent),
    multi: true,
  }],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SpotCheckboxComponent implements ControlValueAccessor {
  @HostBinding('class.spot-checkbox') public className = true;

  @ViewChild('input') public input:ElementRef;

  /**
   * The tabindex for the underlying HTML input
   */
  @Input() tabindex = 0;

  /**
   * Whether the input should be disabled
   */
  @Input() disabled = false;

  /**
   * The name of the input. Will be autogenerated if not given,
   * but especially useful to provide in a hybrid Rails <-> Angular context
   * where a submit of a form is handled without JS.
   */
  @Input() name = `spot-checkbox-${+(new Date())}`;

  /**
   * If you're not using Angular Reactive Forms (Which you should be using!)
   * then you can manually set the checked state via this input.
   */
  @Input() public checked = false;

  /**
   * Emits when the checked state changes.
   */
  @Output() checkedChange = new EventEmitter<boolean>();

  constructor(
    readonly cdRef:ChangeDetectorRef,
  ) {}

  onStateChange():void {
    const value = (this.input.nativeElement as HTMLInputElement).checked;
    this.checkedChange.emit(value);
    this.onChange(value);
    this.onTouched(value);
  }

  writeValue(value:SpotCheckboxState):void {
    // This is set in a timeout because the initial value is set before the template is ready,
    // which causes the input nativeElement to not be available yet.
    setTimeout(() => {
      const input = this.input.nativeElement as HTMLInputElement;
      input.indeterminate = value === null;

      this.checked = !!value;
      this.cdRef.detectChanges();
    });
  }

  onChange = (_:SpotCheckboxState):void => {};

  onTouched = (_:SpotCheckboxState):void => {};

  registerOnChange(fn:(_:SpotCheckboxState) => void):void {
    this.onChange = fn;
  }

  registerOnTouched(fn:(_:SpotCheckboxState) => void):void {
    this.onTouched = fn;
  }
}