Last active
September 24, 2020 16:30
-
-
Save cbejensen/00db1f64ba1c6caa12ea807ad8ac4717 to your computer and use it in GitHub Desktop.
Angular scan directive
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Directive, EventEmitter, HostListener, Input, OnInit, OnDestroy, Optional, Output, Self } from '@angular/core'; | |
import { NgControl } from '@angular/forms'; | |
import { Subject } from 'rxjs'; | |
import { debounceTime, filter, map, pairwise, startWith, takeUntil } from 'rxjs/operators'; | |
/** | |
* Detects when an input is coming from a scanner (or being pasted). | |
*/ | |
@Directive({ | |
selector: '[appScan]', | |
}) | |
export class ScanDirective implements OnInit, OnDestroy { | |
/** | |
* The minimum amount of character additions within the set `debounceTime` for a change to be considered as coming | |
* from a scanner (or paste). | |
*/ | |
// tslint:disable-next-line:no-input-rename | |
@Input('appScanThreshold') threshold = 10; | |
/** | |
* How long to wait between each input change before checking the amount of characters entered. If many characters are | |
* entered in a short amount of time, it's probably a scanner (or was pasted). | |
*/ | |
// tslint:disable-next-line:no-input-rename | |
@Input('appScanDebounceTime') debounceTime = 100; | |
/** | |
* Emits the value of an input change that appears to be from a scanner. | |
*/ | |
@Output() scan = new EventEmitter(); | |
private _destroyed = new Subject(); | |
private _valueChanges = new Subject<string>(); | |
constructor(@Self() @Optional() public ngControl?: NgControl) { | |
super(); | |
} | |
ngOnInit(): void { | |
const control = this.ngControl?.control; | |
// If there's an Angular form control, subscribe to its value stream. Otherwise, assume the host element is a | |
// native form element and subscribe to our own Observable that will emit when the host element emits an `input` | |
// event. | |
(control?.valueChanges || this._valueChanges) | |
.pipe( | |
debounceTime(this.debounceTime), | |
// Use `startWith` so the first value gets through `pairwise`, which requires 2 values before emitting. | |
startWith(''), | |
pairwise(), | |
// Only consider it a valid scan/paste if the input was empty before this and the new value's char count | |
// exceeds the threshold. | |
filter(([oldVal, newVal]) => !oldVal && newVal?.length >= this.threshold), | |
map(([oldVal, newVal]) => newVal), | |
takeUntil(this._destroyed) | |
) | |
.subscribe(val => this.scan.emit(val)); | |
} | |
ngOnDestroy() { | |
this._destroyed.next(); | |
this._destroyed.complete(); | |
} | |
/** | |
* Only effective if this directive is on an element that is not attached to an Angular form control. | |
*/ | |
@HostListener('input', ['$event']) | |
setValue = (e: Event) => { | |
const { value } = e.target as HTMLInputElement; | |
this._valueChanges.next(value); | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment