-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add
mapAndCacheObjectElements()
BREAKING CHANGE: `mapAndCacheElements()` is now `mapAndCacheArrayElements()`
- Loading branch information
Showing
7 changed files
with
220 additions
and
62 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
export { cache } from "./cache"; | ||
export { filterBehavior } from "./filter-behavior"; | ||
export { mapAndCacheElements } from "./map-and-cache-elements"; | ||
export { mapAndCacheArrayElements } from "./map-and-cache-array-elements"; | ||
export { mapAndCacheObjectElements } from "./map-and-cache-object-elements"; | ||
export { skipAfter } from "./skip-after"; | ||
export { withHistory } from "./with-history"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
29 changes: 29 additions & 0 deletions
29
projects/s-rxjs-utils/src/lib/operators/map-and-cache-array-elements.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { ArrayIteratee } from "micro-dash"; | ||
import { OperatorFunction } from "rxjs"; | ||
import { mapAndCacheElements } from "./map-and-cache-elements"; | ||
|
||
/** | ||
* Applies `buildDownstreamItem` to each item in the upstream array and emits the result. Each downstream item is cached using the key generated by `buildCacheKey` so that the next emission contains references to the matching objects from the previous emission, without running `buildDownstreamItem` again. The cache is only held between successive emissions. | ||
* | ||
* This is useful e.g. when using the result in an `*ngFor` expression of an angular template, to prevent angular from rebuilding the inner component and to allow `OnPush` optimizations in the inner component. | ||
* | ||
* If multiple items in an upstream array have the same cache key, it will only call `buildDownstreamItem` once. | ||
* | ||
* ```ts | ||
* const mapWithCaching = mapAndCacheElements( | ||
* (item) => item, | ||
* (item) => item + 1 | ||
* ) | ||
* ``` | ||
* ``` | ||
* source: -[1, 2]---[1, 2, 3]---[2]--| | ||
* mapWithCaching: -[2, 3]---[2, 3, 4]---[3]--| | ||
* ``` | ||
*/ | ||
export const mapAndCacheArrayElements = mapAndCacheElements as < | ||
UpstreamType, | ||
DownstreamType | ||
>( | ||
buildCacheKey: ArrayIteratee<UpstreamType, any>, | ||
buildDownstreamItem: ArrayIteratee<UpstreamType, DownstreamType>, | ||
) => OperatorFunction<UpstreamType[], DownstreamType[]>; |
54 changes: 18 additions & 36 deletions
54
projects/s-rxjs-utils/src/lib/operators/map-and-cache-elements.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
120 changes: 120 additions & 0 deletions
120
projects/s-rxjs-utils/src/lib/operators/map-and-cache-object-elements.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
import { identity, ObjectWith } from "micro-dash"; | ||
import { Subject } from "rxjs"; | ||
import { expectSingleCallAndReset } from "s-ng-test-utils"; | ||
import { | ||
expectPipeResult, | ||
testCompletionPropagation, | ||
testErrorPropagation, | ||
testUnsubscribePropagation, | ||
} from "../../test-helpers"; | ||
import { mapAndCacheObjectElements } from "./map-and-cache-object-elements"; | ||
|
||
describe("mapAndCacheObjectElements()", () => { | ||
it("maps over the object using the given function", async () => { | ||
await expectPipeResult<ObjectWith<number>, number[]>( | ||
[{ a: 1, b: 2, c: 3 }, { a: 1, b: 2, e: 5 }, { a: 1, e: 5, f: 6 }], | ||
mapAndCacheObjectElements(identity, (item) => item * 3), | ||
[[3, 6, 9], [3, 6, 15], [3, 15, 18]], | ||
); | ||
}); | ||
|
||
it("emits the same object reference for items that have the same cache key", () => { | ||
const source = new Subject<ObjectWith<{ index: number }>>(); | ||
const next = jasmine.createSpy(); | ||
|
||
source | ||
.pipe( | ||
mapAndCacheObjectElements( | ||
(item, key) => key, | ||
(item) => ({ index: item.index + 1 }), | ||
), | ||
) | ||
.subscribe(next); | ||
|
||
source.next({ a: { index: 1 } }); | ||
const emission1 = next.calls.mostRecent().args[0]; | ||
|
||
source.next({ a: { index: 1 }, b: { index: 2 } }); | ||
const emission2 = next.calls.mostRecent().args[0]; | ||
|
||
expect(next).toHaveBeenCalledTimes(2); | ||
expect(emission1).toEqual([{ index: 2 }]); | ||
expect(emission2).toEqual([{ index: 2 }, { index: 3 }]); | ||
expect(emission1[0]).toBe(emission2[0]); | ||
}); | ||
|
||
it("does not call `buildDownstreamItem` if there is a match in the cache", () => { | ||
const source = new Subject<ObjectWith<number>>(); | ||
const buildDownstreamItem = jasmine.createSpy(); | ||
|
||
source | ||
.pipe(mapAndCacheObjectElements(identity, buildDownstreamItem)) | ||
.subscribe(); | ||
|
||
source.next({ a: 10 }); | ||
expectSingleCallAndReset(buildDownstreamItem, 10, "a"); | ||
|
||
source.next({ a: 10, b: 15 }); | ||
expectSingleCallAndReset(buildDownstreamItem, 15, "b"); | ||
}); | ||
|
||
it("only calls `buildDownstreamItem` once for a given cache key", () => { | ||
const source = new Subject<ObjectWith<number>>(); | ||
const buildDownstreamItem = jasmine.createSpy(); | ||
|
||
source | ||
.pipe(mapAndCacheObjectElements(identity, buildDownstreamItem)) | ||
.subscribe(); | ||
|
||
source.next({ a: 5, b: 5, c: 5, d: 20 }); | ||
expect(buildDownstreamItem).toHaveBeenCalledTimes(2); | ||
expect(buildDownstreamItem).toHaveBeenCalledWith(5, "a"); | ||
expect(buildDownstreamItem).toHaveBeenCalledWith(20, "d"); | ||
|
||
buildDownstreamItem.calls.reset(); | ||
source.next({ a: 5, b: 5, c: 5, d: 20, e: 25, f: 25 }); | ||
expect(buildDownstreamItem).toHaveBeenCalledTimes(1); | ||
expect(buildDownstreamItem).toHaveBeenCalledWith(25, "e"); | ||
}); | ||
|
||
it("always returns the same object reference for a given cache key", () => { | ||
const source = new Subject<ObjectWith<{ index: number }>>(); | ||
const next = jasmine.createSpy(); | ||
|
||
source | ||
.pipe( | ||
mapAndCacheObjectElements( | ||
(item) => item.index, | ||
(item) => ({ index: item.index + 1 }), | ||
), | ||
) | ||
.subscribe(next); | ||
|
||
source.next({ a: { index: 1 }, b: { index: 1 } }); | ||
const emission1 = next.calls.mostRecent().args[0]; | ||
|
||
source.next({ c: { index: 1 }, d: { index: 1 }, e: { index: 1 } }); | ||
const emission2 = next.calls.mostRecent().args[0]; | ||
|
||
expect(next).toHaveBeenCalledTimes(2); | ||
for (const value of [...emission1, ...emission2]) { | ||
expect(value).toBe(emission1[0]); | ||
} | ||
}); | ||
|
||
it("passes along unsubscribes", () => { | ||
testUnsubscribePropagation(() => | ||
mapAndCacheObjectElements(identity, identity), | ||
); | ||
}); | ||
|
||
it("passes along errors", () => { | ||
testErrorPropagation(() => mapAndCacheObjectElements(identity, identity)); | ||
}); | ||
|
||
it("passes along completion", () => { | ||
testCompletionPropagation(() => | ||
mapAndCacheObjectElements(identity, identity), | ||
); | ||
}); | ||
}); |
29 changes: 29 additions & 0 deletions
29
projects/s-rxjs-utils/src/lib/operators/map-and-cache-object-elements.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { ObjectIteratee } from "micro-dash"; | ||
import { OperatorFunction } from "rxjs"; | ||
import { mapAndCacheElements } from "./map-and-cache-elements"; | ||
|
||
/** | ||
* Applies `buildDownstreamItem` to each item in the upstream object and emits an array containing the results. Each downstream item is cached using the key generated by `buildCacheKey` so that the next emission contains references to the matching objects from the previous emission, without running `buildDownstreamItem` again. The cache is only held between successive emissions. | ||
* | ||
* This is useful e.g. when using the result in an `*ngFor` expression of an angular template, to prevent angular from rebuilding the inner component and to allow `OnPush` optimizations in the inner component. | ||
* | ||
* If multiple items in an upstream object have the same cache key, it will only call `buildDownstreamItem` once. | ||
* | ||
* ```ts | ||
* const mapWithCaching = mapAndCacheElements( | ||
* (item, key) => key, | ||
* (item, key) => item + 1 | ||
* ) | ||
* ``` | ||
* ``` | ||
* source: -{ a: 1, b: 2 }---{ a: 1, b: 2, c: 3 }---{ b: 2 }--| | ||
* mapWithCaching: -[2, 3]-----------[2, 3, 4]--------------[3]--| | ||
* ``` | ||
*/ | ||
export const mapAndCacheObjectElements = mapAndCacheElements as < | ||
UpstreamType, | ||
DownstreamType = UpstreamType[keyof UpstreamType] | ||
>( | ||
buildCacheKey: ObjectIteratee<UpstreamType, any>, | ||
buildDownstreamItem: ObjectIteratee<UpstreamType, DownstreamType>, | ||
) => OperatorFunction<UpstreamType, DownstreamType[]>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters