Skip to content

Commit

Permalink
(feat): zipObject (#80)
Browse files Browse the repository at this point in the history
  • Loading branch information
ggradnig committed Jun 1, 2020
1 parent ca601f7 commit 8840d66
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 2 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ npm install rxjs-etc --save

Higher-order variants of `combineLatestArray` - that takes `Observable<Observable<T>[]>` and returns `Observable<T[]>` - and `combineLatestObject`.

* [combineLatestObject](./source/observable/combineLatestObject.ts), [forkJoinObject](./source/observable/forkJoinObject.ts)
* [combineLatestObject](./source/observable/combineLatestObject.ts), [forkJoinObject](./source/observable/forkJoinObject.ts), [zipObject](./source/observable/zipObject.ts)

Like the array versions, but these take objects. Observable properties are combined using either `combineLatest` or `forkJoin`.
Like the array versions, but these take objects. Observable properties are combined using either `combineLatest`, `forkJoin` or `zip`.

* [forkJoinConcurrent](./source/observable/forkJoinConcurrent.ts)

Expand Down
1 change: 1 addition & 0 deletions source/observable/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ export * from "./toggle";
export * from "./traverse";
export * from "./zipArray";
export * from "./zipPadded";
export * from "./zipObject";
73 changes: 73 additions & 0 deletions source/observable/zipObject-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* @license Use of this source code is governed by an MIT-style license that
* can be found in the LICENSE file at https://github.com/cartant/rxjs-etc
*/
/*tslint:disable:no-unused-expression*/

import { marbles } from "rxjs-marbles";
import { zipObject } from "./zipObject";

describe("zipObject", () => {
it(
"should zip the Observable values of an object and mirror that object with the notification values",
marbles((m) => {
const a = m.hot(" --a");
const b = m.hot(" ---b");
const expected = m.cold(" ---i", { i: { a: "a", b: "b" } });
const destination = zipObject({ a, b });
m.expect(destination).toBeObservable(expected);
})
);

it(
"should support multiple notifications from the input observables",
marbles((m) => {
const a = m.hot(" --a-c");
const b = m.hot(" ---b-d");
const expected = m.cold(" ---i-j", {
i: { a: "a", b: "b" },
j: { a: "c", b: "d" },
});
const destination = zipObject({ a, b });
m.expect(destination).toBeObservable(expected);
})
);

it(
"should support empty objects",
marbles((m) => {
const expected = m.cold("(i|)", { i: {} });
const destination = zipObject({});
m.expect(destination).toBeObservable(expected);
})
);

it(
"should support objects with single property",
marbles((m) => {
const a = m.hot(" --a");
const expected = m.cold(" --i", { i: { a: "a" } });
const destination = zipObject({ a });
m.expect(destination).toBeObservable(expected);
})
);

it(
"should support objects with non-observable properties",
marbles((m) => {
const expected = m.cold("(i|)", { i: { a: "a" } });
const destination = zipObject({ a: "a" });
m.expect(destination).toBeObservable(expected);
})
);

it(
"should support objects with some non-observable properties",
marbles((m) => {
const a = m.hot(" --a");
const expected = m.cold(" --(i|)", { i: { a: "a", b: "b" } });
const destination = zipObject({ a, b: "b" });
m.expect(destination).toBeObservable(expected);
})
);
});
61 changes: 61 additions & 0 deletions source/observable/zipObject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* @license Use of this source code is governed by an MIT-style license that
* can be found in the LICENSE file at https://github.com/cartant/rxjs-etc
*/

import { Observable, of } from "rxjs";
import { map } from "rxjs/operators";
import { isObservable } from "../util";
import { zipArray } from "./zipArray";

/**
* Like the `zip` operator, but instead of an array, it takes an object. The properties of this object can be Observable
* or non-Observable. `zipObject` subscribes to the Observable properties and combines them into an object that mirrors
* the input object, but with the notification values of those Observables. Non-Observable values are wrapped with `of` before zipping.
*
* ## Example
* Combine name, weight, and species of animals
* ```typescript
* import { zipObject } from 'rxjs-etc';
*
* let name$ = of('kitty', 'doggo', 'chirpy');
* let weight$ = of(4, 7, 0.5);
* let species$ = of('Dog', 'Cat', 'Bird');
*
* zipObject({
* name: name$,
* weight: weight$,
* species: species$
* })
* .subscribe(value => console.log(value));
*
* // Output:
* // { name: 'kitty', weight: 4, species: 'Cat'}
* // { name: 'doggo', weight: 7, species: 'Dog' }
* // { name: 'chirpy', weight: 0.5, species: 'Bird' }
* ```
* @param instance
* @return {Observable<T>}
* @static true
* @name zipObject
*/
export function zipObject<T>(
instance: { [K in keyof T]: T[K] | Observable<T[K]> }
): Observable<T> {
type K = keyof T;
const entries = Object.entries(instance) as [
string,
T[K] | Observable<T[K]>
][];
const observables = entries.map(([, value]) =>
isObservable(value) ? value : of(value)
);
return zipArray(observables).pipe(
map((values) =>
values.reduce(
(acc, value, index) => ({ ...acc, [entries[index][0]]: value }),
{} as T
)
)
);
}

0 comments on commit 8840d66

Please sign in to comment.