Skip to content

Instantly share code, notes, and snippets.

@JanMalch
Created January 30, 2019 16:50
Show Gist options
  • Save JanMalch/104de9ec29786469969aef759dcfcf37 to your computer and use it in GitHub Desktop.
Save JanMalch/104de9ec29786469969aef759dcfcf37 to your computer and use it in GitHub Desktop.
Angular directive for input value as RxJS observable
import {Directive, ElementRef, Input, OnDestroy} from "@angular/core";
import {fromEvent, Observable, Subject} from "rxjs";
import {debounceTime, distinctUntilChanged, map, startWith, takeUntil} from "rxjs/operators";
/**
* An input directive that makes it easier to get the input's value in an Observable.
* These directive acts a one-way for output. You can't set the input's value.
* @example
* <input rxjsInput>
*
* ViewChild(RxJsInputDirective) myInput$: RxJsInputDirective<string>;
* myInput$.asObservable('start observable with me!').subscribe(console.log);
* // the start value won't be set as the actual elements value.
*/
@Directive({
// tslint:disable-next-line:directive-selector
selector: 'input[rxjsInput]'
})
export class RxJsInputDirective<T extends string | number | Date> implements OnDestroy {
@Input() debounce = 0;
private readonly onDestroy$ = new Subject();
private readonly observable: Observable<any>;
public readonly el: HTMLInputElement;
public readonly type: string;
public readonly getMappedValue: () => T;
constructor(elRef: ElementRef<HTMLInputElement>) {
this.el = elRef.nativeElement;
this.type = this.el.getAttribute('type') || 'text';
switch (this.type) {
case 'number': this.getMappedValue = () => this.el.valueAsNumber as any; break;
case 'date': this.getMappedValue = () => this.el.valueAsDate as any; break;
default: this.getMappedValue = () => this.el.value as any;
}
this.observable = fromEvent(this.el, 'input');
}
/**
* Returns an observable that emits the input's value.
* Automatically maps to correct type based on element's type attribute.
* Completes when directive is destroyed.
* @param startWithValue Optional value to start the observable with. Doesn't set it as the inputs value
*/
asObservable(startWithValue?: T): Observable<T> {
const base = this.observable.pipe(
takeUntil(this.onDestroy$),
debounceTime(this.debounce),
map(() => this.getMappedValue()),
distinctUntilChanged()
);
return startWithValue === undefined ? base : base.pipe(startWith(startWithValue)) as any;
}
ngOnDestroy() {
this.onDestroy$.next();
this.onDestroy$.complete();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment