From 770376346c615ab46d1b62233ffcd039d8ea35d0 Mon Sep 17 00:00:00 2001 From: Mike Ryan Date: Sun, 8 May 2016 12:36:09 -0600 Subject: [PATCH] feat(SelectOperator): Add select operator --- lib/add/operator/select.ts | 10 ++++++ lib/operator/select.ts | 26 ++++++++++++++ spec/select.spec.ts | 70 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+) create mode 100644 lib/add/operator/select.ts create mode 100644 lib/operator/select.ts create mode 100644 spec/select.spec.ts diff --git a/lib/add/operator/select.ts b/lib/add/operator/select.ts new file mode 100644 index 0000000..3d76c2c --- /dev/null +++ b/lib/add/operator/select.ts @@ -0,0 +1,10 @@ +import { Observable } from 'rxjs/Observable'; +import { select, SelectSignature } from '../../operator/select'; + +Observable.prototype.select = select; + +declare module 'rxjs/Observable' { + interface Observable { + select: SelectSignature; + } +} \ No newline at end of file diff --git a/lib/operator/select.ts b/lib/operator/select.ts new file mode 100644 index 0000000..cb26d71 --- /dev/null +++ b/lib/operator/select.ts @@ -0,0 +1,26 @@ +import { pluck } from 'rxjs/operator/pluck'; +import { map } from 'rxjs/operator/map'; +import { distinctUntilChanged } from 'rxjs/operator/distinctUntilChanged'; +import { Observable } from 'rxjs/Observable'; + +export interface SelectSignature { + (...paths: string[]): Observable; + (mapFn: (state: T) => R): Observable; +} + +export function select(pathOrMapFn: any, ...paths: string[]): Observable { + let mapped$: Observable; + + if (typeof pathOrMapFn === 'string') { + mapped$ = pluck.call(this, pathOrMapFn, ...paths); + } + else if (typeof pathOrMapFn === 'function') { + mapped$ = map.call(this, pathOrMapFn); + } + else { + throw new TypeError(`Unexpected type ${ typeof pathOrMapFn } in select operator,` + + ` expected 'string' or 'function'`); + } + + return distinctUntilChanged.call(mapped$); +} \ No newline at end of file diff --git a/spec/select.spec.ts b/spec/select.spec.ts new file mode 100644 index 0000000..4864891 --- /dev/null +++ b/spec/select.spec.ts @@ -0,0 +1,70 @@ +import 'rxjs/add/observable/of'; +import { Observable } from 'rxjs/Observable'; + +import '../lib/add/operator/select'; + +const state = { + todos: { + a: { + title: 'First Todo' + }, + b: { + title: 'Second Todo' + } + }, + filter: 'all' +}; + +describe('select Operator', function() { + it('should accept a string and return that property from the object', function(done) { + Observable.of(state) + .select('filter') + .subscribe({ + next(val) { + expect(val).toBe(state.filter); + }, + error: done, + complete: done + }); + }); + + it('should return a list of strings and deeply map into the object', function(done) { + Observable.of(state) + .select('todos', 'a', 'title') + .subscribe({ + next(val) { + expect(val).toBe(state.todos.a.title); + }, + error: done, + complete: done + }); + }); + + it('should accept a map function instead of a string', function(done) { + Observable.of(state) + .select(s => s.todos.b.title) + .subscribe({ + next(val) { + expect(val).toBe(state.todos.b.title); + }, + error: done, + complete: done + }); + }); + + it('should not emit a new value if there is no change', function(done) { + let callCount = 0; + Observable.of(state, state, state) + .select('todos') + .subscribe({ + next() { + ++callCount; + }, + error: done, + complete() { + expect(callCount).toEqual(1); + done(); + } + }); + }); +}); \ No newline at end of file