native-input.component.ts 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. import { OnInit, OnDestroy, Input, Output, ChangeDetectorRef, EventEmitter } from '@angular/core';
  2. import { FormControl } from '@angular/forms';
  3. import { FriendlyValidationErrorsService } from './../../services/friendly-validation-errors.service';
  4. import { Subject, Subscription } from 'rxjs';
  5. import { debounceTime } from 'rxjs/operators';
  6. export abstract class NativeInputComponent implements OnInit, OnDestroy {
  7. @Input()
  8. control: FormControl;
  9. @Input()
  10. set meta(meta: StringMap<any>) {
  11. this._meta = meta;
  12. this.exposeForTemplate();
  13. }
  14. @Output()
  15. call: EventEmitter<string> = new EventEmitter<string>();
  16. readonly componentName: string;
  17. exposeMetaInTemplate: string[] = [];
  18. _meta: StringMap<any>;
  19. pendingValidation: boolean;
  20. hasFocus: boolean = false;
  21. waitForFirstChange: boolean = false;
  22. keyUp$: Subject<string> = new Subject();
  23. valueSBX: Subscription;
  24. statusSBX: Subscription;
  25. keyUpSBX: Subscription;
  26. keyUpValidationDelay: number = 2000; // Validate after 2 seconds IF the control still has focus
  27. constructor(
  28. protected valErrsService: FriendlyValidationErrorsService,
  29. protected _cdr: ChangeDetectorRef
  30. ) {}
  31. ngOnInit() {
  32. // this.control is not an instance of FormControl when it's a Checkbutton in a Checkbutton group (but is when it's a solo Checkbutton) - BUT WHY? WHY!!!
  33. // Hence the 'if' statement
  34. if (this.control instanceof FormControl) {
  35. this.valueSBX = this.control.valueChanges.subscribe(val => {
  36. if (val === null) {
  37. this.control.setValue(this._meta.default); // Reset to default value (as opposed to null) after an Angular FormGroup.reset()
  38. return;
  39. }
  40. this.markForCheck();
  41. });
  42. this.statusSBX = this.control.statusChanges.subscribe(status => {
  43. if (status === 'PENDING') {
  44. this.pendingValidation = true;
  45. } else if (this.pendingValidation) {
  46. this.pendingValidation = false;
  47. this.markForCheck();
  48. }
  49. });
  50. }
  51. this.keyUpSBX = this.keyUp$.pipe(debounceTime(this.keyUpValidationDelay)).subscribe(val => {
  52. if (this.hasFocus) {
  53. // THE ORDER OF THE NEXT THREE LINES IS IMPORTANT!
  54. // We MUST make the control as dirty before setting the value to avoid corrupting the initialValue recorded in Dynaform's makeAsyncTest utility
  55. this.control.markAsTouched();
  56. this.control.markAsDirty();
  57. this.control.setValue(val);
  58. }
  59. });
  60. }
  61. ngOnDestroy() {
  62. if (this.valueSBX) {
  63. this.valueSBX.unsubscribe();
  64. }
  65. if (this.statusSBX) {
  66. this.statusSBX.unsubscribe();
  67. }
  68. if (this.keyUpSBX) {
  69. this.keyUpSBX.unsubscribe();
  70. }
  71. }
  72. exposeForTemplate() {
  73. // Move meta variables up a level, for direct access in templates
  74. this.exposeMetaInTemplate.map(p => this[p] = this._meta[p] !== undefined ? this._meta[p] : this[p]);
  75. }
  76. markForCheck() {
  77. if (this._cdr) {
  78. this._cdr.markForCheck();
  79. }
  80. }
  81. handle(fnId: string, val: any): void {
  82. this.call.emit(fnId); // Add control's current value
  83. }
  84. handleChange(): void {
  85. if (this._meta.change) {
  86. this.handle(this._meta.change, this.control.value);
  87. }
  88. }
  89. getName(): string {
  90. return this.componentName;
  91. }
  92. getFirstFailureMsg(): string {
  93. if (!this.control.errors || this.control.errors === {}) {
  94. return '';
  95. }
  96. const key = Object.keys(this.control.errors)[0];
  97. return this._meta.valFailureMsgs[key] || this.valErrsService.getFriendly(key, this.control.errors[key]);
  98. }
  99. gainFocus(): void {
  100. this.hasFocus = true;
  101. this.waitForFirstChange = true;
  102. }
  103. loseFocus(): void {
  104. this.hasFocus = false;
  105. }
  106. handleKeyup(currentFieldValue: string): void {
  107. this.keyUp$.next(currentFieldValue);
  108. if (this.control.value && this.waitForFirstChange) {
  109. // Hide any validation errors if the control has a previous value (set after Angular's first updateOn event) and its value has changed
  110. // NOTE that this.control.value may lag behind currentFieldValue depending on the updateOn startegy chosen
  111. this.control.markAsPending();
  112. this.waitForFirstChange = false;
  113. }
  114. }
  115. }