Skip to content

Commit 8026906

Browse files
luisgabrielbenlesh
authored andcommitted
feat(pluck): add pluck operator
Pretty similar to RxJS 4 implementation. Closes #1134
1 parent c18c42e commit 8026906

File tree

7 files changed

+200
-1
lines changed

7 files changed

+200
-1
lines changed

MIGRATION.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@ enabling "composite" subscription behavior.
9090
|`onErrorResumeNext`|`-`|
9191
|`pausable`|`-`|
9292
|`pausableBuffered`|`-`|
93-
|`pluck`|`-`|
9493
|`shareReplay`|`-`|
9594
|`shareValue`|`-`|
9695
|`selectConcatObserver` or `concatMapObserver`|`-`|

spec/operators/pluck-spec.js

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
/* globals describe, it, expect, hot, cold, expectObservable, expectSubscriptions */
2+
var Rx = require('../../dist/cjs/Rx');
3+
var Observable = Rx.Observable;
4+
5+
describe('Observable.prototype.pluck()', function () {
6+
it('should work for one object', function () {
7+
var a = cold('--x--|', {x: {prop: 42}});
8+
var asubs = '^ !';
9+
var expected = '--y--|';
10+
11+
var r = a.pluck('prop');
12+
expectObservable(r).toBe(expected, {y: 42});
13+
expectSubscriptions(a.subscriptions).toBe(asubs);
14+
});
15+
16+
it('should work for multiple objects', function () {
17+
var inputs = {
18+
a: {prop: '1'},
19+
b: {prop: '2'},
20+
c: {prop: '3'},
21+
d: {prop: '4'},
22+
e: {prop: '5'},
23+
};
24+
var a = cold('--a-b--c-d---e-|', inputs);
25+
var asubs = '^ !';
26+
var expected = '--1-2--3-4---5-|';
27+
28+
var r = a.pluck('prop');
29+
expectObservable(r).toBe(expected);
30+
expectSubscriptions(a.subscriptions).toBe(asubs);
31+
});
32+
33+
it('should work with deep nested properties', function () {
34+
var inputs = {
35+
a: {a: {b: {c: '1'}}},
36+
b: {a: {b: {c: '2'}}},
37+
c: {a: {b: {c: '3'}}},
38+
d: {a: {b: {c: '4'}}},
39+
e: {a: {b: {c: '5'}}},
40+
};
41+
var a = cold('--a-b--c-d---e-|', inputs);
42+
var asubs = '^ !';
43+
var expected = '--1-2--3-4---5-|';
44+
45+
var r = a.pluck('a', 'b', 'c');
46+
expectObservable(r).toBe(expected);
47+
expectSubscriptions(a.subscriptions).toBe(asubs);
48+
});
49+
50+
it('should work with edge cases of deep nested properties', function () {
51+
var inputs = {
52+
a: {a: {b: {c: 1}}},
53+
b: {a: {b: 2}},
54+
c: {a: {c: {c: 3}}},
55+
d: {},
56+
e: {a: {b: {c: 5}}},
57+
};
58+
var a = cold('--a-b--c-d---e-|', inputs);
59+
var asubs = '^ !';
60+
var expected = '--r-x--y-z---w-|';
61+
var values = {r: 1, x: undefined, y: undefined, z: undefined, w: 5};
62+
63+
var r = a.pluck('a', 'b', 'c');
64+
expectObservable(r).toBe(expected, values);
65+
expectSubscriptions(a.subscriptions).toBe(asubs);
66+
});
67+
68+
it('should throw an error if not property is passed', function () {
69+
expect(function () {
70+
Observable.of({prop: 1}, {prop: 2}).pluck();
71+
}).toThrow(new Error('List of properties cannot be empty.'));
72+
});
73+
74+
it('should propagate errors from observable that emits only errors', function () {
75+
var a = cold('#');
76+
var asubs = '(^!)';
77+
var expected = '#';
78+
79+
var r = a.pluck('whatever');
80+
expectObservable(r).toBe(expected);
81+
expectSubscriptions(a.subscriptions).toBe(asubs);
82+
});
83+
84+
it('should propagate errors from observable that emit values', function () {
85+
var a = cold('--a--b--#', {a: {prop: '1'}, b: {prop: '2'}}, 'too bad');
86+
var asubs = '^ !';
87+
var expected = '--1--2--#';
88+
89+
var r = a.pluck('prop');
90+
expectObservable(r).toBe(expected, undefined, 'too bad');
91+
expectSubscriptions(a.subscriptions).toBe(asubs);
92+
});
93+
94+
it('should not pluck an empty observable', function () {
95+
var a = cold('|');
96+
var asubs = '(^!)';
97+
var expected = '|';
98+
99+
var invoked = 0;
100+
var r = a
101+
.pluck('whatever')
102+
.do(null, null, function () {
103+
expect(invoked).toBe(0);
104+
});
105+
106+
expectObservable(r).toBe(expected);
107+
expectSubscriptions(a.subscriptions).toBe(asubs);
108+
});
109+
110+
it('should allow unsubscribing explicitly and early', function () {
111+
var a = cold('--a--b--c--|', {a: {prop: '1'}, b: {prop: '2'}});
112+
var unsub = ' ! ';
113+
var asubs = '^ ! ';
114+
var expected = '--1--2- ';
115+
116+
var r = a.pluck('prop');
117+
expectObservable(r, unsub).toBe(expected);
118+
expectSubscriptions(a.subscriptions).toBe(asubs);
119+
});
120+
121+
it('should pluck twice', function () {
122+
var inputs = {
123+
a: {a: {b: {c: '1'}}},
124+
b: {a: {b: {c: '2'}}},
125+
c: {a: {b: {c: '3'}}},
126+
d: {a: {b: {c: '4'}}},
127+
e: {a: {b: {c: '5'}}},
128+
};
129+
var a = cold('--a-b--c-d---e-|', inputs);
130+
var asubs = '^ !';
131+
var expected = '--1-2--3-4---5-|';
132+
133+
var r = a.pluck('a', 'b').pluck('c');
134+
expectObservable(r).toBe(expected);
135+
expectSubscriptions(a.subscriptions).toBe(asubs);
136+
});
137+
138+
it('should not break unsubscription chain when unsubscribed explicitly', function () {
139+
var a = cold('--a--b--c--|', {a: {prop: '1'}, b: {prop: '2'}});
140+
var unsub = ' ! ';
141+
var asubs = '^ ! ';
142+
var expected = '--1--2- ';
143+
144+
var r = a
145+
.mergeMap(function (x) { return Observable.of(x); })
146+
.pluck('prop')
147+
.mergeMap(function (x) { return Observable.of(x); });
148+
149+
expectObservable(r, unsub).toBe(expected);
150+
expectSubscriptions(a.subscriptions).toBe(asubs);
151+
});
152+
});

src/Observable.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ export class Observable<T> implements CoreOperators<T> {
240240
multicast: (subjectOrSubjectFactory: Subject<T>|(() => Subject<T>)) => ConnectableObservable<T>;
241241
observeOn: (scheduler: Scheduler, delay?: number) => Observable<T>;
242242
partition: (predicate: (x: T) => boolean) => Observable<T>[];
243+
pluck: (...properties: string[]) => Observable<any>;
243244
publish: () => ConnectableObservable<T>;
244245
publishBehavior: (value: any) => ConnectableObservable<T>;
245246
publishReplay: (bufferSize?: number, windowTime?: number, scheduler?: Scheduler) => ConnectableObservable<T>;

src/Rx.KitchenSink.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ import './add/operator/multicast';
9393
import './add/operator/observeOn';
9494
import './add/operator/pairwise';
9595
import './add/operator/partition';
96+
import './add/operator/pluck';
9697
import './add/operator/publish';
9798
import './add/operator/publishBehavior';
9899
import './add/operator/publishReplay';

src/Rx.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ import './add/operator/mergeMapTo';
6868
import './add/operator/multicast';
6969
import './add/operator/observeOn';
7070
import './add/operator/partition';
71+
import './add/operator/pluck';
7172
import './add/operator/publish';
7273
import './add/operator/publishBehavior';
7374
import './add/operator/publishReplay';

src/add/operator/pluck.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/**
2+
* Everything in this file is generated by the 'tools/generate-operator-patches.ts' script.
3+
* Any manual edits to this file will be lost next time the script is run.
4+
**/
5+
import {Observable} from '../../Observable';
6+
import {pluck} from '../../operator/pluck';
7+
8+
Observable.prototype.pluck = pluck;
9+
10+
export var _void: void;

src/operator/pluck.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import {Observable} from '../Observable';
2+
import {map} from './map';
3+
4+
/**
5+
* Retrieves the value of a specified nested property from all elements in
6+
* the Observable sequence. If a property can't be resolved, it will return
7+
* `undefined` for that value.
8+
*
9+
* @param {...args} properties the nested properties to pluck
10+
* @returns {Observable} Returns a new Observable sequence of property values
11+
*/
12+
export function pluck(...properties: string[]): Observable<any> {
13+
const length = properties.length;
14+
if (length === 0) {
15+
throw new Error('List of properties cannot be empty.');
16+
}
17+
return map.call(this, plucker(properties, length));
18+
}
19+
20+
function plucker(props: string[], length: number): (x: string) => any {
21+
const mapper = (x: string) => {
22+
let currentProp = x;
23+
for (let i = 0; i < length; i++) {
24+
const p = currentProp[props[i]];
25+
if (typeof p !== 'undefined') {
26+
currentProp = p;
27+
} else {
28+
return undefined;
29+
}
30+
}
31+
return currentProp;
32+
};
33+
34+
return mapper;
35+
}

0 commit comments

Comments
 (0)