Skip to content

Commit

Permalink
Adds @substore decorator (angular-redux#412)
Browse files Browse the repository at this point in the history
This allows for a more declarative use of the ['fractal store' feature](https://github.com/angular-redux/store/blob/master/articles/fractal-store.md) introduced in v6.3.0. Sticking a `@WithSubStore` decorator on a component or service will modify the behaviour of any `@select, @select$, or @dispatch` invocations in instances of that class:

```typescript
@WithSubStore({
  basePathMethodName: 'getBasePath',
  localReducer: localReducerFunction,
})
@component({ /* ... */ })
class MyFractalComponent {
  @input() componentId;

  // Will only be called with the first non-nil value of this.componentId. Subsequent changes to the
  // input will be ignored.
  getBasePath() {
    return [ 'some', 'redux', 'path', this.componentId ];
  }

  // Gets an observable to 'some.redux.path.${this.componentId}.foo'
  // Under the hood:
  //   ngRedux.configureSubStore(
  //     ['some', 'redux' 'path', this.componentId],
  //     localReducerFunction)).select('foo')
  @select() foo$: Observable<String>;

  // Calls substore.dispatch() instead of NgRedux.instance.dispatch().
  @dispatch() myAction = () => ({ type: 'MINE' });
}
```
  • Loading branch information
SethDavenport authored Jun 15, 2017
1 parent c79261b commit d3470c3
Show file tree
Hide file tree
Showing 45 changed files with 3,391 additions and 455 deletions.
14 changes: 14 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
// Use IntelliSense to learn about possible Node.js debug attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Unit Tests",
"program": "${workspaceRoot}/tests.js"
}
]
}
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# 6.5.0

* Enabled fractal store features for the decorator interface. See
https://github.com/angular-redux/store/blob/master/articles/fractal-store.md for details.

# 6.4.5

* Fix a boundary condition where `MockNgRedux` could get instantiated
Expand Down
63 changes: 60 additions & 3 deletions articles/fractal-store.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,63 @@ existing subStore.

## What about @select, @select$, @dispatch?

We don't have decorator support for the fractal store yet. These decorators
act on the global store only right now. We're thinking about a clean way
of doing fractal decorators for a future release.
As of 6.5.0, the decorator interface has been expanded to support fractal
stores as well.

Tag your component or service with the `@WithSubStore` decorator, and a substore will be
configured behind the scenes; instance of that class's `@select`, `@select$`, and `@dispatch` decorators will now operate on that substore instead of the root store. Reworking the
example above with the decorator interface looks like this:

```typescript
interface IUser {
name: string,
occupation: string,
loc: number,
};

export const userComponentReducer = (state, action) =>
action.type === 'ADD_LOC' ?
{ ...state, loc: state.loc + action.payload } :
state;

export const defaultToZero = (obs$: Observable<number>) =>
obs$.map(n => n || 0);

@Component({
selector: 'user',
template: `
<p>name: {{ name$ |async }}</p>
<p>occupation: {{ occupation$ | async }}</p>
<p>lines of code: {{ loc$ | async }}</p>
   <button (click)=addCode(100)>Add 100 lines of code</button>
`,
})
@WithSubStore({
basePathMethodName: 'getBasePath',
localReducer: userComponentReducer,
})
export class UserComponent implements NgOnInit {
@Input() userId: String;

// The substore will be created at the first non-falsy path returned
// from this function.
getBasePath(): PathSelector | null {
return this.userId ? ['users', userId] : null;
}

// These selections are now scoped to the portion of the store rooted
// at ['users', userId];
@select('name') readonly name$: Observable<string>;
@select('occupation') readonly occupation$: Observable<string>;
@select$('loc', defaultToZero) readonly loc$: Observable<number>;

// These dispatches will be scoped to the substore as well, as if you
// had called ngRedux.configureSubStore(...).dispatch(numLines).
@dispatch()
addCode(numLines) {
// Dispatching from the sub-store ensures this component instance's
// subStore only sees the 'ADD_LOC' action.
return { type: 'ADD_LOC', payload: numLines };
}
}
```
2 changes: 1 addition & 1 deletion docs/assets/js/search.js

Large diffs are not rendered by default.

18 changes: 16 additions & 2 deletions docs/classes/devtoolsextension.html
Original file line number Diff line number Diff line change
Expand Up @@ -933,6 +933,9 @@
<li class=" tsd-kind-class">
<a href="ngreduxtestingmodule.html" class="tsd-kind-icon">Ng<wbr>Redux<wbr>Testing<wbr>Module</a>
</li>
<li class=" tsd-kind-interface">
<a href="../interfaces/ifractalstoreoptions.html" class="tsd-kind-icon">IFractal<wbr>Store<wbr>Options</a>
</li>
<li class=" tsd-kind-interface tsd-has-type-parameter">
<a href="../interfaces/observablestore.html" class="tsd-kind-icon">Observable<wbr>Store</a>
</li>
Expand All @@ -954,9 +957,15 @@
<li class=" tsd-kind-type-alias">
<a href="../globals.html#transformer" class="tsd-kind-icon">Transformer</a>
</li>
<li class=" tsd-kind-function">
<a href="../globals.html#withsubstore" class="tsd-kind-icon">With<wbr>Sub<wbr>Store</a>
</li>
<li class=" tsd-kind-function">
<a href="../globals.html#dispatch" class="tsd-kind-icon">dispatch</a>
</li>
<li class=" tsd-kind-function">
<a href="../globals.html#enablefractalreducers" class="tsd-kind-icon">enable<wbr>Fractal<wbr>Reducers</a>
</li>
<li class=" tsd-kind-function tsd-has-type-parameter">
<a href="../globals.html#select" class="tsd-kind-icon">select</a>
</li>
Expand All @@ -979,6 +988,11 @@ <h3>Hierarchy</h3>
<ul class="tsd-hierarchy">
<li>
<span class="target">DevToolsExtension</span>
<ul class="tsd-hierarchy">
<li>
<a href="mockdevtoolsextension.html" class="tsd-signature-type">MockDevToolsExtension</a>
</li>
</ul>
</li>
</ul>
</section>
Expand Down Expand Up @@ -1008,7 +1022,7 @@ <h3>enhancer</h3>
<li class="tsd-description">
<aside class="tsd-sources">
<ul>
<li>Defined in <a href="https://github.com/SethDavenport/ng2-redux/blob/0584dc5/src/components/dev-tools.ts#L26">src/components/dev-tools.ts:26</a></li>
<li>Defined in <a href="https://github.com/SethDavenport/ng2-redux/blob/0dbbb4f/src/components/dev-tools.ts#L26">src/components/dev-tools.ts:26</a></li>
</ul>
</aside>
<div class="tsd-comment tsd-typography">
Expand Down Expand Up @@ -1038,7 +1052,7 @@ <h3>is<wbr>Enabled</h3>
<li class="tsd-description">
<aside class="tsd-sources">
<ul>
<li>Defined in <a href="https://github.com/SethDavenport/ng2-redux/blob/0584dc5/src/components/dev-tools.ts#L51">src/components/dev-tools.ts:51</a></li>
<li>Defined in <a href="https://github.com/SethDavenport/ng2-redux/blob/0dbbb4f/src/components/dev-tools.ts#L51">src/components/dev-tools.ts:51</a></li>
</ul>
</aside>
<div class="tsd-comment tsd-typography">
Expand Down
138 changes: 62 additions & 76 deletions docs/classes/mockdevtoolsextension.html
Original file line number Diff line number Diff line change
Expand Up @@ -913,10 +913,10 @@
<li class="current tsd-kind-class">
<a href="mockdevtoolsextension.html" class="tsd-kind-icon">Mock<wbr>Dev<wbr>Tools<wbr>Extension</a>
<ul>
<li class=" tsd-kind-property tsd-parent-kind-class tsd-is-inherited">
<li class=" tsd-kind-method tsd-parent-kind-class tsd-is-inherited">
<a href="mockdevtoolsextension.html#enhancer" class="tsd-kind-icon">enhancer</a>
</li>
<li class=" tsd-kind-property tsd-parent-kind-class tsd-is-inherited">
<li class=" tsd-kind-method tsd-parent-kind-class tsd-is-inherited">
<a href="mockdevtoolsextension.html#isenabled" class="tsd-kind-icon">is<wbr>Enabled</a>
</li>
</ul>
Expand All @@ -933,6 +933,9 @@
<li class=" tsd-kind-class">
<a href="ngreduxtestingmodule.html" class="tsd-kind-icon">Ng<wbr>Redux<wbr>Testing<wbr>Module</a>
</li>
<li class=" tsd-kind-interface">
<a href="../interfaces/ifractalstoreoptions.html" class="tsd-kind-icon">IFractal<wbr>Store<wbr>Options</a>
</li>
<li class=" tsd-kind-interface tsd-has-type-parameter">
<a href="../interfaces/observablestore.html" class="tsd-kind-icon">Observable<wbr>Store</a>
</li>
Expand All @@ -954,9 +957,15 @@
<li class=" tsd-kind-type-alias">
<a href="../globals.html#transformer" class="tsd-kind-icon">Transformer</a>
</li>
<li class=" tsd-kind-function">
<a href="../globals.html#withsubstore" class="tsd-kind-icon">With<wbr>Sub<wbr>Store</a>
</li>
<li class=" tsd-kind-function">
<a href="../globals.html#dispatch" class="tsd-kind-icon">dispatch</a>
</li>
<li class=" tsd-kind-function">
<a href="../globals.html#enablefractalreducers" class="tsd-kind-icon">enable<wbr>Fractal<wbr>Reducers</a>
</li>
<li class=" tsd-kind-function tsd-has-type-parameter">
<a href="../globals.html#select" class="tsd-kind-icon">select</a>
</li>
Expand All @@ -971,7 +980,7 @@
<h3>Hierarchy</h3>
<ul class="tsd-hierarchy">
<li>
<span class="tsd-signature-type">DevToolsExtension</span>
<a href="devtoolsextension.html" class="tsd-signature-type">DevToolsExtension</a>
<ul class="tsd-hierarchy">
<li>
<span class="target">MockDevToolsExtension</span>
Expand All @@ -985,93 +994,70 @@ <h2>Index</h2>
<section class="tsd-panel tsd-index-panel">
<div class="tsd-index-content">
<section class="tsd-index-section tsd-is-inherited">
<h3>Properties</h3>
<h3>Methods</h3>
<ul class="tsd-index-list">
<li class="tsd-kind-property tsd-parent-kind-class tsd-is-inherited"><a href="mockdevtoolsextension.html#enhancer" class="tsd-kind-icon">enhancer</a></li>
<li class="tsd-kind-property tsd-parent-kind-class tsd-is-inherited"><a href="mockdevtoolsextension.html#isenabled" class="tsd-kind-icon">is<wbr>Enabled</a></li>
<li class="tsd-kind-method tsd-parent-kind-class tsd-is-inherited"><a href="mockdevtoolsextension.html#enhancer" class="tsd-kind-icon">enhancer</a></li>
<li class="tsd-kind-method tsd-parent-kind-class tsd-is-inherited"><a href="mockdevtoolsextension.html#isenabled" class="tsd-kind-icon">is<wbr>Enabled</a></li>
</ul>
</section>
</div>
</section>
</section>
<section class="tsd-panel-group tsd-member-group tsd-is-inherited">
<h2>Properties</h2>
<section class="tsd-panel tsd-member tsd-kind-property tsd-parent-kind-class tsd-is-inherited">
<h2>Methods</h2>
<section class="tsd-panel tsd-member tsd-kind-method tsd-parent-kind-class tsd-is-inherited">
<a name="enhancer" class="tsd-anchor"></a>
<h3>enhancer</h3>
<div class="tsd-signature tsd-kind-icon">enhancer<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">function</span></div>
<aside class="tsd-sources">
<p>Inherited from DevToolsExtension.enhancer</p>
<ul>
<li>Defined in lib/src/components/dev-tools.d.ts:20</li>
</ul>
</aside>
<div class="tsd-comment tsd-typography">
<div class="lead">
<p>A wrapper for the Chrome Extension Redux DevTools.
Makes sure state changes triggered by the extension
trigger Angular2&#39;s change detector.</p>
</div>
<dl class="tsd-comment-tags">
<dt>argument</dt>
<dd><p>options: dev tool options; same
format as described here:
[zalmoxisus/redux-devtools-extension/blob/master/docs/API/Arguments.md]</p>
</dd>
</dl>
</div>
<div class="tsd-type-declaration">
<h4>Type declaration</h4>
<ul class="tsd-parameters">
<li class="tsd-parameter-siganture">
<ul class="tsd-signatures tsd-kind-type-literal tsd-parent-kind-property tsd-is-not-exported">
<li class="tsd-signature tsd-kind-icon"><span class="tsd-signature-symbol">(</span>options<span class="tsd-signature-symbol">?: </span><span class="tsd-signature-type">Object</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">undefined</span><span class="tsd-signature-symbol">)</span><span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">any</span></li>
</ul>
<ul class="tsd-descriptions">
<li class="tsd-description">
<h4 class="tsd-parameters-title">Parameters</h4>
<ul class="tsd-parameters">
<li>
<h5><span class="tsd-flag ts-flagOptional">Optional</span> options: <span class="tsd-signature-type">Object</span><span class="tsd-signature-symbol"> | </span><span class="tsd-signature-type">undefined</span></h5>
</li>
</ul>
<h4 class="tsd-returns-title">Returns <span class="tsd-signature-type">any</span></h4>
</li>
<ul class="tsd-signatures tsd-kind-method tsd-parent-kind-class tsd-is-inherited">
<li class="tsd-signature tsd-kind-icon">enhancer<span class="tsd-signature-symbol">(</span>options<span class="tsd-signature-symbol">?: </span><span class="tsd-signature-type">Object</span><span class="tsd-signature-symbol">)</span><span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">any</span></li>
</ul>
<ul class="tsd-descriptions">
<li class="tsd-description">
<aside class="tsd-sources">
<p>Inherited from <a href="devtoolsextension.html">DevToolsExtension</a>.<a href="devtoolsextension.html#enhancer">enhancer</a></p>
<ul>
<li>Defined in <a href="https://github.com/SethDavenport/ng2-redux/blob/0dbbb4f/src/components/dev-tools.ts#L26">src/components/dev-tools.ts:26</a></li>
</ul>
</li>
</ul>
</div>
</aside>
<div class="tsd-comment tsd-typography">
<div class="lead">
<p>A wrapper for the Chrome Extension Redux DevTools.
Makes sure state changes triggered by the extension
trigger Angular2&#39;s change detector.</p>
</div>
</div>
<h4 class="tsd-parameters-title">Parameters</h4>
<ul class="tsd-parameters">
<li>
<h5><span class="tsd-flag ts-flagOptional">Optional</span> options: <span class="tsd-signature-type">Object</span></h5>
</li>
</ul>
<h4 class="tsd-returns-title">Returns <span class="tsd-signature-type">any</span></h4>
</li>
</ul>
</section>
<section class="tsd-panel tsd-member tsd-kind-property tsd-parent-kind-class tsd-is-inherited">
<section class="tsd-panel tsd-member tsd-kind-method tsd-parent-kind-class tsd-is-inherited">
<a name="isenabled" class="tsd-anchor"></a>
<h3>is<wbr>Enabled</h3>
<div class="tsd-signature tsd-kind-icon">is<wbr>Enabled<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">function</span></div>
<aside class="tsd-sources">
<p>Inherited from DevToolsExtension.isEnabled</p>
<ul>
<li>Defined in lib/src/components/dev-tools.d.ts:24</li>
</ul>
</aside>
<div class="tsd-comment tsd-typography">
<div class="lead">
<p>Returns true if the extension is installed and enabled.</p>
</div>
</div>
<div class="tsd-type-declaration">
<h4>Type declaration</h4>
<ul class="tsd-parameters">
<li class="tsd-parameter-siganture">
<ul class="tsd-signatures tsd-kind-type-literal tsd-parent-kind-property tsd-is-not-exported">
<li class="tsd-signature tsd-kind-icon"><span class="tsd-signature-symbol">(</span><span class="tsd-signature-symbol">)</span><span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">any</span></li>
<ul class="tsd-signatures tsd-kind-method tsd-parent-kind-class tsd-is-inherited">
<li class="tsd-signature tsd-kind-icon">is<wbr>Enabled<span class="tsd-signature-symbol">(</span><span class="tsd-signature-symbol">)</span><span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">any</span></li>
</ul>
<ul class="tsd-descriptions">
<li class="tsd-description">
<aside class="tsd-sources">
<p>Inherited from <a href="devtoolsextension.html">DevToolsExtension</a>.<a href="devtoolsextension.html#isenabled">isEnabled</a></p>
<ul>
<li>Defined in <a href="https://github.com/SethDavenport/ng2-redux/blob/0dbbb4f/src/components/dev-tools.ts#L51">src/components/dev-tools.ts:51</a></li>
</ul>
<ul class="tsd-descriptions">
<li class="tsd-description">
<h4 class="tsd-returns-title">Returns <span class="tsd-signature-type">any</span></h4>
</li>
</ul>
</li>
</ul>
</div>
</aside>
<div class="tsd-comment tsd-typography">
<div class="lead">
<p>Returns true if the extension is installed and enabled.</p>
</div>
</div>
<h4 class="tsd-returns-title">Returns <span class="tsd-signature-type">any</span></h4>
</li>
</ul>
</section>
</section>
<footer class="with-border-bottom">
Expand Down
Loading

0 comments on commit d3470c3

Please sign in to comment.