From 50e06935a52ec3f9c43e6e5596b57e76ac4426fe Mon Sep 17 00:00:00 2001 From: doktordirk Date: Tue, 22 Feb 2022 14:35:36 +0100 Subject: [PATCH] feat(connectTo): pass target instance to selector Allows selectors to be based on the target instance's state --- src/decorator.ts | 12 ++++++------ test/unit/decorator.spec.ts | 19 ++++++++++++++++++- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/decorator.ts b/src/decorator.ts index 6edc206..e108a1f 100644 --- a/src/decorator.ts +++ b/src/decorator.ts @@ -6,19 +6,19 @@ import { Store } from "./store"; export interface ConnectToSettings { onChanged?: string; - selector: ((store: Store) => Observable) | MultipleSelector; + selector: ((store: Store, targetInstance: any) => Observable) | MultipleSelector; setup?: string; target?: string; teardown?: string; } export interface MultipleSelector { - [key: string]: ((store: Store) => Observable); + [key: string]: ((store: Store, targetInstance: any) => Observable); } const defaultSelector = (store: Store) => store.state; -export function connectTo(settings?: ((store: Store) => Observable) | ConnectToSettings) { +export function connectTo(settings?: ((store: Store, targetInstance: any) => Observable) | ConnectToSettings) { let $store: Store; // const store = Container.instance.get(Store) as Store; @@ -27,14 +27,14 @@ export function connectTo(settings?: ((store: Store) => Observabl ...settings }; - function getSource(selector: (((store: Store) => Observable))): Observable { + function getSource(selector: (((store: Store, targetInstance: any) => Observable)), targetInstance: any): Observable { // if for some reason getSource is invoked before setup (bind lifecycle, typically) // then we have no choice but to get the store instance from global container instance // otherwise, assume that $store variable in the closure would be already assigned the right // value from created callback // Could also be in situation where it doesn't come from custom element, or some exotic setups/scenarios const store = $store || ($store = Container.instance.get(Store)); - const source = selector(store); + const source = selector(store, targetInstance); if (source instanceof Observable) { return source; @@ -94,7 +94,7 @@ export function connectTo(settings?: ((store: Store) => Observabl throw new Error("Provided onChanged handler does not exist on target VM"); } - this._stateSubscriptions = createSelectors().map(s => getSource(s.selector).subscribe((state: any) => { + this._stateSubscriptions = createSelectors().map(s => getSource(s.selector, this).subscribe((state: any) => { const lastTargetIdx = s.targets.length - 1; const oldState = s.targets.reduce((accu = {}, curr) => accu[curr], this); diff --git a/test/unit/decorator.spec.ts b/test/unit/decorator.spec.ts index d4ef8a7..8b8c542 100644 --- a/test/unit/decorator.spec.ts +++ b/test/unit/decorator.spec.ts @@ -1,6 +1,6 @@ import { Container } from "aurelia-framework"; import { Subscription } from "rxjs"; -import { pluck, distinctUntilChanged } from "rxjs/operators"; +import { pluck, distinctUntilChanged, map } from "rxjs/operators"; import { Store } from "../../src/store"; import { connectTo } from "../../src/decorator"; @@ -62,6 +62,23 @@ describe("using decorators", () => { expect(sut.state).toEqual(initialState.bar); }); + it("should pass the target instance to a state selector", () => { + const { initialState } = arrange(); + + @connectTo((store, targetInstance: DemoStoreConsumer) => store.state.pipe(map(state => state[targetInstance.baz]))) + class DemoStoreConsumer { + baz: keyof DemoState = "foo"; + state: DemoState; + } + + const sut = new DemoStoreConsumer(); + expect(sut.state).toEqual(undefined); + + (sut as any).bind(); + + expect(sut.state).toEqual(initialState.foo); + }); + describe("with a complex settings object", () => { it("should be possible to provide a selector", () => { const { initialState } = arrange();