groupBy.js 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. "use strict";
  2. var __extends = (this && this.__extends) || function (d, b) {
  3. for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
  4. function __() { this.constructor = d; }
  5. d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
  6. };
  7. var Subscriber_1 = require('../Subscriber');
  8. var Subscription_1 = require('../Subscription');
  9. var Observable_1 = require('../Observable');
  10. var Subject_1 = require('../Subject');
  11. var Map_1 = require('../util/Map');
  12. var FastMap_1 = require('../util/FastMap');
  13. /* tslint:enable:max-line-length */
  14. /**
  15. * Groups the items emitted by an Observable according to a specified criterion,
  16. * and emits these grouped items as `GroupedObservables`, one
  17. * {@link GroupedObservable} per group.
  18. *
  19. * <img src="./img/groupBy.png" width="100%">
  20. *
  21. * @example <caption>Group objects by id and return as array</caption>
  22. * Observable.of<Obj>({id: 1, name: 'aze1'},
  23. * {id: 2, name: 'sf2'},
  24. * {id: 2, name: 'dg2'},
  25. * {id: 1, name: 'erg1'},
  26. * {id: 1, name: 'df1'},
  27. * {id: 2, name: 'sfqfb2'},
  28. * {id: 3, name: 'qfs3'},
  29. * {id: 2, name: 'qsgqsfg2'}
  30. * )
  31. * .groupBy(p => p.id)
  32. * .flatMap( (group$) => group$.reduce((acc, cur) => [...acc, cur], []))
  33. * .subscribe(p => console.log(p));
  34. *
  35. * // displays:
  36. * // [ { id: 1, name: 'aze1' },
  37. * // { id: 1, name: 'erg1' },
  38. * // { id: 1, name: 'df1' } ]
  39. * //
  40. * // [ { id: 2, name: 'sf2' },
  41. * // { id: 2, name: 'dg2' },
  42. * // { id: 2, name: 'sfqfb2' },
  43. * // { id: 2, name: 'qsgqsfg2' } ]
  44. * //
  45. * // [ { id: 3, name: 'qfs3' } ]
  46. *
  47. * @example <caption>Pivot data on the id field</caption>
  48. * Observable.of<Obj>({id: 1, name: 'aze1'},
  49. * {id: 2, name: 'sf2'},
  50. * {id: 2, name: 'dg2'},
  51. * {id: 1, name: 'erg1'},
  52. * {id: 1, name: 'df1'},
  53. * {id: 2, name: 'sfqfb2'},
  54. * {id: 3, name: 'qfs1'},
  55. * {id: 2, name: 'qsgqsfg2'}
  56. * )
  57. * .groupBy(p => p.id, p => p.name)
  58. * .flatMap( (group$) => group$.reduce((acc, cur) => [...acc, cur], ["" + group$.key]))
  59. * .map(arr => ({'id': parseInt(arr[0]), 'values': arr.slice(1)}))
  60. * .subscribe(p => console.log(p));
  61. *
  62. * // displays:
  63. * // { id: 1, values: [ 'aze1', 'erg1', 'df1' ] }
  64. * // { id: 2, values: [ 'sf2', 'dg2', 'sfqfb2', 'qsgqsfg2' ] }
  65. * // { id: 3, values: [ 'qfs1' ] }
  66. *
  67. * @param {function(value: T): K} keySelector A function that extracts the key
  68. * for each item.
  69. * @param {function(value: T): R} [elementSelector] A function that extracts the
  70. * return element for each item.
  71. * @param {function(grouped: GroupedObservable<K,R>): Observable<any>} [durationSelector]
  72. * A function that returns an Observable to determine how long each group should
  73. * exist.
  74. * @return {Observable<GroupedObservable<K,R>>} An Observable that emits
  75. * GroupedObservables, each of which corresponds to a unique key value and each
  76. * of which emits those items from the source Observable that share that key
  77. * value.
  78. * @method groupBy
  79. * @owner Observable
  80. */
  81. function groupBy(keySelector, elementSelector, durationSelector, subjectSelector) {
  82. return function (source) {
  83. return source.lift(new GroupByOperator(keySelector, elementSelector, durationSelector, subjectSelector));
  84. };
  85. }
  86. exports.groupBy = groupBy;
  87. var GroupByOperator = (function () {
  88. function GroupByOperator(keySelector, elementSelector, durationSelector, subjectSelector) {
  89. this.keySelector = keySelector;
  90. this.elementSelector = elementSelector;
  91. this.durationSelector = durationSelector;
  92. this.subjectSelector = subjectSelector;
  93. }
  94. GroupByOperator.prototype.call = function (subscriber, source) {
  95. return source.subscribe(new GroupBySubscriber(subscriber, this.keySelector, this.elementSelector, this.durationSelector, this.subjectSelector));
  96. };
  97. return GroupByOperator;
  98. }());
  99. /**
  100. * We need this JSDoc comment for affecting ESDoc.
  101. * @ignore
  102. * @extends {Ignored}
  103. */
  104. var GroupBySubscriber = (function (_super) {
  105. __extends(GroupBySubscriber, _super);
  106. function GroupBySubscriber(destination, keySelector, elementSelector, durationSelector, subjectSelector) {
  107. _super.call(this, destination);
  108. this.keySelector = keySelector;
  109. this.elementSelector = elementSelector;
  110. this.durationSelector = durationSelector;
  111. this.subjectSelector = subjectSelector;
  112. this.groups = null;
  113. this.attemptedToUnsubscribe = false;
  114. this.count = 0;
  115. }
  116. GroupBySubscriber.prototype._next = function (value) {
  117. var key;
  118. try {
  119. key = this.keySelector(value);
  120. }
  121. catch (err) {
  122. this.error(err);
  123. return;
  124. }
  125. this._group(value, key);
  126. };
  127. GroupBySubscriber.prototype._group = function (value, key) {
  128. var groups = this.groups;
  129. if (!groups) {
  130. groups = this.groups = typeof key === 'string' ? new FastMap_1.FastMap() : new Map_1.Map();
  131. }
  132. var group = groups.get(key);
  133. var element;
  134. if (this.elementSelector) {
  135. try {
  136. element = this.elementSelector(value);
  137. }
  138. catch (err) {
  139. this.error(err);
  140. }
  141. }
  142. else {
  143. element = value;
  144. }
  145. if (!group) {
  146. group = this.subjectSelector ? this.subjectSelector() : new Subject_1.Subject();
  147. groups.set(key, group);
  148. var groupedObservable = new GroupedObservable(key, group, this);
  149. this.destination.next(groupedObservable);
  150. if (this.durationSelector) {
  151. var duration = void 0;
  152. try {
  153. duration = this.durationSelector(new GroupedObservable(key, group));
  154. }
  155. catch (err) {
  156. this.error(err);
  157. return;
  158. }
  159. this.add(duration.subscribe(new GroupDurationSubscriber(key, group, this)));
  160. }
  161. }
  162. if (!group.closed) {
  163. group.next(element);
  164. }
  165. };
  166. GroupBySubscriber.prototype._error = function (err) {
  167. var groups = this.groups;
  168. if (groups) {
  169. groups.forEach(function (group, key) {
  170. group.error(err);
  171. });
  172. groups.clear();
  173. }
  174. this.destination.error(err);
  175. };
  176. GroupBySubscriber.prototype._complete = function () {
  177. var groups = this.groups;
  178. if (groups) {
  179. groups.forEach(function (group, key) {
  180. group.complete();
  181. });
  182. groups.clear();
  183. }
  184. this.destination.complete();
  185. };
  186. GroupBySubscriber.prototype.removeGroup = function (key) {
  187. this.groups.delete(key);
  188. };
  189. GroupBySubscriber.prototype.unsubscribe = function () {
  190. if (!this.closed) {
  191. this.attemptedToUnsubscribe = true;
  192. if (this.count === 0) {
  193. _super.prototype.unsubscribe.call(this);
  194. }
  195. }
  196. };
  197. return GroupBySubscriber;
  198. }(Subscriber_1.Subscriber));
  199. /**
  200. * We need this JSDoc comment for affecting ESDoc.
  201. * @ignore
  202. * @extends {Ignored}
  203. */
  204. var GroupDurationSubscriber = (function (_super) {
  205. __extends(GroupDurationSubscriber, _super);
  206. function GroupDurationSubscriber(key, group, parent) {
  207. _super.call(this, group);
  208. this.key = key;
  209. this.group = group;
  210. this.parent = parent;
  211. }
  212. GroupDurationSubscriber.prototype._next = function (value) {
  213. this.complete();
  214. };
  215. /** @deprecated internal use only */ GroupDurationSubscriber.prototype._unsubscribe = function () {
  216. var _a = this, parent = _a.parent, key = _a.key;
  217. this.key = this.parent = null;
  218. if (parent) {
  219. parent.removeGroup(key);
  220. }
  221. };
  222. return GroupDurationSubscriber;
  223. }(Subscriber_1.Subscriber));
  224. /**
  225. * An Observable representing values belonging to the same group represented by
  226. * a common key. The values emitted by a GroupedObservable come from the source
  227. * Observable. The common key is available as the field `key` on a
  228. * GroupedObservable instance.
  229. *
  230. * @class GroupedObservable<K, T>
  231. */
  232. var GroupedObservable = (function (_super) {
  233. __extends(GroupedObservable, _super);
  234. function GroupedObservable(key, groupSubject, refCountSubscription) {
  235. _super.call(this);
  236. this.key = key;
  237. this.groupSubject = groupSubject;
  238. this.refCountSubscription = refCountSubscription;
  239. }
  240. /** @deprecated internal use only */ GroupedObservable.prototype._subscribe = function (subscriber) {
  241. var subscription = new Subscription_1.Subscription();
  242. var _a = this, refCountSubscription = _a.refCountSubscription, groupSubject = _a.groupSubject;
  243. if (refCountSubscription && !refCountSubscription.closed) {
  244. subscription.add(new InnerRefCountSubscription(refCountSubscription));
  245. }
  246. subscription.add(groupSubject.subscribe(subscriber));
  247. return subscription;
  248. };
  249. return GroupedObservable;
  250. }(Observable_1.Observable));
  251. exports.GroupedObservable = GroupedObservable;
  252. /**
  253. * We need this JSDoc comment for affecting ESDoc.
  254. * @ignore
  255. * @extends {Ignored}
  256. */
  257. var InnerRefCountSubscription = (function (_super) {
  258. __extends(InnerRefCountSubscription, _super);
  259. function InnerRefCountSubscription(parent) {
  260. _super.call(this);
  261. this.parent = parent;
  262. parent.count++;
  263. }
  264. InnerRefCountSubscription.prototype.unsubscribe = function () {
  265. var parent = this.parent;
  266. if (!parent.closed && !this.closed) {
  267. _super.prototype.unsubscribe.call(this);
  268. parent.count -= 1;
  269. if (parent.count === 0 && parent.attemptedToUnsubscribe) {
  270. parent.unsubscribe();
  271. }
  272. }
  273. };
  274. return InnerRefCountSubscription;
  275. }(Subscription_1.Subscription));
  276. //# sourceMappingURL=groupBy.js.map