diff --git a/spec/Observable-spec.ts b/spec/Observable-spec.ts index 9093ed8ae2..8923d4a584 100644 --- a/spec/Observable-spec.ts +++ b/spec/Observable-spec.ts @@ -831,3 +831,21 @@ describe('Observable.lift', () => { }); }); }); + +/** @test {Observable} */ +describe('Observable.op', () => { + it('should apply the provided operator and args to the given stream', () => { + const { map, filter } = Rx.Observable.prototype; + const e1 = cold('-a-b-c-|', { a: 1, b: 2, c: 3 }); + const expected = '---b-c-|'; + + const result = e1 + .op(filter, i => i > 1) + .op(map, i => i * 10); + + expectObservable(result).toBe(expected, { + b: 20, + c: 30 + }); + }); +}); diff --git a/src/Observable.ts b/src/Observable.ts index 0aa4b4cef3..8fe8011b37 100644 --- a/src/Observable.ts +++ b/src/Observable.ts @@ -71,6 +71,52 @@ export class Observable implements Subscribable { return observable; } + /** + * Allows you to apply an operator to a given stream, without it needing to be + * on the Observable prototype. This is useful whenever you don't want to + * unintentionally leak implementation details or otherwise rely on the fact + * that you're using a particular operator. + * + * It takes the provided operator function and any arbitrary arguments + * then internally just calls `operator.apply(this, args)`. + * + * This is solves a similar problem as the TC39 proposed [bind operator syntax](https://github.com/tc39/proposal-bind-operator) + * e.g. `stream::map(i => i + 1)` but that's stage-0 and not supported by + * TypeScript. + * + * Example 1: in your unit tests, if you were to patch additional operators + * onto the Observable prototype then the application you're testing could + * unknowingly be relying on those operators without it importing them itself. + * Your tests would pass because the operator is available in your tests, but + * when you run the app standalone it errors because the operator wasn't + * imported! + * + * Example 2: you're writing a library that itself uses RxJS. If you patch + * operators onto the prototype, then consumers of your library could + * accidentally depend on the fact that the operators you use are available. + * + * @example Apply several operators without patching the Observable prototype + * import { of } from 'rxjs/observable/of'; + * import { filter } from 'rxjs/operator/filter'; + * import { map } from 'rxjs/operator/map'; + * + * of(1, 2, 3) + * .op(filter, i => i > 1) + * .op(map, i => i * 10) + * .subscribe(x => console.log(x)); + * // 20..30 + * + * @method op + * @param {Function} operator The operator function you wish to apply. + * @param {...any} args (optional) A spread of any arguments you want to apply + * to the operator when we apply it to your stream. + * @return {Observable} An Observable that comes from the result of applying + * the provided operator to the source. + */ + op(operator: Function, ...args: Array): Observable { + return operator.apply(this, args); + } + /** * Registers handlers for handling emitted values, error and completions from the observable, and * executes the observable's subscriber function, which will take action to set up the underlying data stream