From b2725e7234aec7c5be1a9060625b143430ff2a47 Mon Sep 17 00:00:00 2001 From: Ben Lesh Date: Tue, 25 Jul 2017 11:26:42 -0700 Subject: [PATCH] feat(distinctUntilChanged): add higher-order lettable version of distinctUntilChanged --- src/operator/distinctUntilChanged.ts | 71 +--------------- src/operators/distinctUntilChanged.ts | 117 ++++++++++++++++++++++++++ src/operators/index.ts | 1 + 3 files changed, 121 insertions(+), 68 deletions(-) create mode 100644 src/operators/distinctUntilChanged.ts diff --git a/src/operator/distinctUntilChanged.ts b/src/operator/distinctUntilChanged.ts index 525067fea2..a418be492d 100644 --- a/src/operator/distinctUntilChanged.ts +++ b/src/operator/distinctUntilChanged.ts @@ -1,9 +1,6 @@ -import { Operator } from '../Operator'; -import { Subscriber } from '../Subscriber'; -import { tryCatch } from '../util/tryCatch'; -import { errorObject } from '../util/errorObject'; + import { Observable } from '../Observable'; -import { TeardownLogic } from '../Subscription'; +import { distinctUntilChanged as higherOrder } from '../operators'; /* tslint:disable:max-line-length */ export function distinctUntilChanged(this: Observable, compare?: (x: T, y: T) => boolean): Observable; @@ -50,67 +47,5 @@ export function distinctUntilChanged(this: Observable, compare: (x: K, * @owner Observable */ export function distinctUntilChanged(this: Observable, compare?: (x: K, y: K) => boolean, keySelector?: (x: T) => K): Observable { - return this.lift(new DistinctUntilChangedOperator(compare, keySelector)); -} - -class DistinctUntilChangedOperator implements Operator { - constructor(private compare: (x: K, y: K) => boolean, - private keySelector: (x: T) => K) { - } - - call(subscriber: Subscriber, source: any): TeardownLogic { - return source.subscribe(new DistinctUntilChangedSubscriber(subscriber, this.compare, this.keySelector)); - } -} - -/** - * We need this JSDoc comment for affecting ESDoc. - * @ignore - * @extends {Ignored} - */ -class DistinctUntilChangedSubscriber extends Subscriber { - private key: K; - private hasKey: boolean = false; - - constructor(destination: Subscriber, - compare: (x: K, y: K) => boolean, - private keySelector: (x: T) => K) { - super(destination); - if (typeof compare === 'function') { - this.compare = compare; - } - } - - private compare(x: any, y: any): boolean { - return x === y; - } - - protected _next(value: T): void { - - const keySelector = this.keySelector; - let key: any = value; - - if (keySelector) { - key = tryCatch(this.keySelector)(value); - if (key === errorObject) { - return this.destination.error(errorObject.e); - } - } - - let result: any = false; - - if (this.hasKey) { - result = tryCatch(this.compare)(this.key, key); - if (result === errorObject) { - return this.destination.error(errorObject.e); - } - } else { - this.hasKey = true; - } - - if (Boolean(result) === false) { - this.key = key; - this.destination.next(value); - } - } + return higherOrder(compare, keySelector)(this); } diff --git a/src/operators/distinctUntilChanged.ts b/src/operators/distinctUntilChanged.ts new file mode 100644 index 0000000000..7226a0700e --- /dev/null +++ b/src/operators/distinctUntilChanged.ts @@ -0,0 +1,117 @@ +import { Operator } from '../Operator'; +import { Subscriber } from '../Subscriber'; +import { tryCatch } from '../util/tryCatch'; +import { errorObject } from '../util/errorObject'; +import { Observable } from '../Observable'; +import { TeardownLogic } from '../Subscription'; +import { MonoTypeOperatorFunction } from '../interfaces'; + +/* tslint:disable:max-line-length */ +export function distinctUntilChanged(compare?: (x: T, y: T) => boolean): MonoTypeOperatorFunction; +export function distinctUntilChanged(compare: (x: K, y: K) => boolean, keySelector: (x: T) => K): MonoTypeOperatorFunction; +/* tslint:enable:max-line-length */ + +/** + * Returns an Observable that emits all items emitted by the source Observable that are distinct by comparison from the previous item. + * + * If a comparator function is provided, then it will be called for each item to test for whether or not that value should be emitted. + * + * If a comparator function is not provided, an equality check is used by default. + * + * @example A simple example with numbers + * Observable.of(1, 1, 2, 2, 2, 1, 1, 2, 3, 3, 4) + * .distinctUntilChanged() + * .subscribe(x => console.log(x)); // 1, 2, 1, 2, 3, 4 + * + * @example An example using a compare function + * interface Person { + * age: number, + * name: string + * } + * + * Observable.of( + * { age: 4, name: 'Foo'}, + * { age: 7, name: 'Bar'}, + * { age: 5, name: 'Foo'}) + * { age: 6, name: 'Foo'}) + * .distinctUntilChanged((p: Person, q: Person) => p.name === q.name) + * .subscribe(x => console.log(x)); + * + * // displays: + * // { age: 4, name: 'Foo' } + * // { age: 7, name: 'Bar' } + * // { age: 5, name: 'Foo' } + * + * @see {@link distinct} + * @see {@link distinctUntilKeyChanged} + * + * @param {function} [compare] Optional comparison function called to test if an item is distinct from the previous item in the source. + * @return {Observable} An Observable that emits items from the source Observable with distinct values. + * @method distinctUntilChanged + * @owner Observable + */ +export function distinctUntilChanged(compare?: (x: K, y: K) => boolean, keySelector?: (x: T) => K): MonoTypeOperatorFunction { + return (source: Observable) => source.lift(new DistinctUntilChangedOperator(compare, keySelector)); +} + +class DistinctUntilChangedOperator implements Operator { + constructor(private compare: (x: K, y: K) => boolean, + private keySelector: (x: T) => K) { + } + + call(subscriber: Subscriber, source: any): TeardownLogic { + return source.subscribe(new DistinctUntilChangedSubscriber(subscriber, this.compare, this.keySelector)); + } +} + +/** + * We need this JSDoc comment for affecting ESDoc. + * @ignore + * @extends {Ignored} + */ +class DistinctUntilChangedSubscriber extends Subscriber { + private key: K; + private hasKey: boolean = false; + + constructor(destination: Subscriber, + compare: (x: K, y: K) => boolean, + private keySelector: (x: T) => K) { + super(destination); + if (typeof compare === 'function') { + this.compare = compare; + } + } + + private compare(x: any, y: any): boolean { + return x === y; + } + + protected _next(value: T): void { + + const keySelector = this.keySelector; + let key: any = value; + + if (keySelector) { + key = tryCatch(this.keySelector)(value); + if (key === errorObject) { + return this.destination.error(errorObject.e); + } + } + + let result: any = false; + + if (this.hasKey) { + result = tryCatch(this.compare)(this.key, key); + if (result === errorObject) { + return this.destination.error(errorObject.e); + } + } else { + this.hasKey = true; + } + + if (Boolean(result) === false) { + this.key = key; + this.destination.next(value); + } + } +} diff --git a/src/operators/index.ts b/src/operators/index.ts index 2f2355cefa..14a749df4b 100644 --- a/src/operators/index.ts +++ b/src/operators/index.ts @@ -17,6 +17,7 @@ export { defaultIfEmpty } from './defaultIfEmpty'; export { delay } from './delay'; export { delayWhen } from './delayWhen'; export { dematerialize } from './dematerialize'; +export { distinctUntilChanged } from './distinctUntilChanged'; export { filter } from './filter'; export { ignoreElements } from './ignoreElements'; export { map } from './map';