Skip to content

Commit efeef10

Browse files
committed
Merge pull request #11 from wbuchwalter/bc-1.0.0
Breaking changes for 1.0.0
2 parents 31f939f + 89c3f82 commit efeef10

File tree

17 files changed

+351
-200
lines changed

17 files changed

+351
-200
lines changed

README.md

Lines changed: 107 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -3,56 +3,36 @@
33

44
For Angular 2 see [ng2-redux](https://github.com/wbuchwalter/ng2-redux).
55

6-
**Warning: The API will be subject to breaking changes until `1.0.0` is released. You can follow progress on the `bc-1.0.0` branch**
6+
**Warning: The API will be subject to breaking changes until `1.0.0` is released.**
77

88
[![build status](https://img.shields.io/travis/wbuchwalter/ng-redux/master.svg?style=flat-square)](https://travis-ci.org/wbuchwalter/ng-redux)
99
[![npm version](https://img.shields.io/npm/v/ng-redux.svg?style=flat-square)](https://www.npmjs.com/package/ng-redux)
1010

11-
## Installation
12-
```js
13-
npm install --save ng-redux
14-
```
1511

16-
## Overview
12+
*ngRedux lets you easily connect your angular components with Redux.*
1713

18-
ngRedux lets you easily connect your angular components with Redux.
19-
the API is straightforward:
2014

21-
```JS
22-
$ngRedux.connect(selector, callback);
23-
```
15+
## Table of Contents
2416

25-
Where `selector` is a function that takes Redux's entire store state as argument and returns an object that contains the slices of store state that your component is interested in.
26-
e.g:
27-
```JS
28-
state => ({todos: state.todos})
29-
```
30-
Note: if you are not familiar with this syntax, go and check out the [MDN Guide on fat arrow functions (ES2015)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions)
31-
32-
If you haven't, check out [reselect](https://github.com/faassen/reselect), an awesome tool to create and combine selectors.
17+
- [Installation](#installation)
18+
- [Quick Start](#quick-start)
19+
- [API](#api)
20+
- [Using DevTools](#using-devtools)
3321

22+
## Installation
3423

35-
This returned object will be passed as argument to the callback provided whenever the state changes.
36-
ngRedux checks for shallow equality of the state's selected slice whenever the Store is updated, and will call the callback only if there is a change.
37-
**Important: It is assumed that you never mutate your states, if you do mutate them, ng-redux will not execute the callback properly.**
38-
See [Redux's doc](http://gaearon.github.io/redux/docs/basics/Reducers.html) to understand why you should not mutate your states.
39-
24+
**The current npm version is outdated, and will be updated once 1.0.0 is finished**
25+
```js
26+
npm install --save ng-redux
27+
```
4028

41-
## Getting Started
29+
## Quick Start
4230

4331
#### Initialization
4432

45-
```JS
46-
$ngReduxProvider.createStoreWith(reducer, [middlewares], storeEnhancer);
47-
```
48-
#### Parameters:
49-
* reducer (Function): A single reducer composed of all other reducers (create with redux.combineReducer)
50-
* [middleware] (Array of Function or String): An array containing all the middleware that should be applied. Functions and strings are both valid members. String will be resolved via Angular, allowing you to use dependency injection in your middlewares.
51-
* storeEnhancer: Optional function that will be used to create the store, in most cases you don't need that, see [Store Enhancer official doc](http://rackt.github.io/redux/docs/Glossary.html#store-enhancer)
52-
5333
```JS
5434
import reducers from './reducers';
55-
import {combineReducers} from 'redux';
35+
import { combineReducers } from 'redux';
5636
import loggingMiddleware from './loggingMiddleware';
5737
import 'ng-redux';
5838

@@ -64,75 +44,120 @@ angular.module('app', ['ngRedux'])
6444
```
6545

6646
#### Usage
67-
```JS
68-
export default function todoLoader() {
69-
return {
70-
controllerAs: 'vm',
71-
controller: TodoLoaderController,
72-
template: "<div ng-repeat='todo in vm.todos'>{{todo.text}}</div>",
73-
74-
[...]
75-
};
76-
}
47+
*Note: this sample is using the ControllerAs syntax, usage is slightly different without ControllerAs, see API section for more details*
7748

78-
class TodoLoaderController {
79-
80-
constructor($ngRedux) {
81-
this.todos = [];
82-
$ngRedux.connect(state => ({todos: state.todos}), ({todos}) => this.todos = todos);
49+
```JS
50+
import * as CounterActions from '../actions/counter';
51+
52+
class CounterController {
53+
constructor($ngRedux, $scope) {
54+
/* ngRedux will merge the requested state's slice and actions onto the $scope,
55+
you don't need to redefine them in your controller */
56+
57+
$ngRedux.connect($scope, this.mapStateToScope, CounterActions, 'vm');
8358
}
8459

85-
[...]
60+
// Which part of the Redux global state does our component want to receive on $scope?
61+
mapStateToScope(state) {
62+
return {
63+
counter: state.counter
64+
};
65+
}
8666
}
8767
```
8868

89-
**Note: The callback provided to `connect` will be called once directly after creation to allow initialization of your component states**
69+
```HTML
70+
<div>
71+
<p>Clicked: {{vm.counter}} times </p>
72+
<button ng-click='vm.increment()'>+</button>
73+
<button ng-click='vm.decrement()'>-</button>
74+
<button ng-click='vm.incrementIfOdd()'>Increment if odd</button>
75+
<button ng-click='vm.incrementAsync()'>Increment Async</button>
76+
</div>
77+
```
9078

79+
## API
9180

81+
### `createStoreWith([reducer], [middlewares], [storeEnhancers])`
9282

93-
You can also grab multiple slices of the state by passing an array of selectors:
83+
Creates the Redux store, and allow `connect()` to access it.
9484

95-
```JS
96-
constructor($ngRedux) {
97-
this.todos = [];
98-
this.users = [];
99-
$ngRedux.connect(state => ({
100-
todos: state.todos,
101-
users: state.users
102-
}),
103-
({todos, users}) => {
104-
this.todos = todos
105-
this.users = users;
106-
});
107-
}
108-
```
85+
#### Arguments:
86+
* [`reducer`] \(*Function*): A single reducer composed of all other reducers (create with redux.combineReducer)
87+
* [`middlewares`] \(*Function[]*): Optional, An array containing all the middleware that should be applied. Functions and strings are both valid members. String will be resolved via Angular, allowing you to use dependency injection in your middlewares.
88+
* [`storeEnhancers`] \(*Function[]*): Optional, this will be used to create the store, in most cases you don't need to pass anything, see [Store Enhancer official documentation.](http://rackt.github.io/redux/docs/Glossary.html#store-enhancer)
10989

11090

111-
#### Unsubscribing
91+
### `connect([scope], [mapStateToScope], [mapDispatchToScope], [propertyKey])`
11292

113-
You can close a connection like this:
93+
Connects an Angular component to Redux.
11494

115-
```JS
95+
#### Arguments
96+
* [`scope`] \(*Object*): The `$scope` of your controller.
97+
* [`mapStateToScope`] \(*Function*): connect will subscribe to Redux store updates. Any time it updates, mapStateToTarget will be called. Its result must be a plain object, and it will be merged into `target`.
98+
* [`mapDispatchToScope`] \(*Object* or *Function*): If an object is passed, each function inside it will be assumed to be a Redux action creator. An object with the same function names, but bound to a Redux store, will be merged into your component `$scope`. If a function is passed, it will be given `dispatch`. It’s up to you to return an object that somehow uses `dispatch` to bind action creators in your own way. (Tip: you may use the [`bindActionCreators()`](http://gaearon.github.io/redux/docs/api/bindActionCreators.html) helper from Redux.).
99+
* [`propertyKey`] \(*string*): If provided, `mapStateToScope` and `mapDispatchToScope` will merge onto `$scope[propertyKey]`. This is needed especially when using the `ControllerAs` syntax: in this case you should provide the same value than the value provided to controllerAs (e.g: `'vm'`). When not using `ControllerAs` syntax, you are free to omit this argument, everything will be merged directly onto `$scope`.
116100

117-
constructor($ngRedux) {
118-
this.todos = [];
119-
this.unsubscribe = $ngRedux.connect(state => ({todos: state.todos}), ({todos}) => this.todos = todos);
120-
}
101+
#### Remarks
102+
As `$scope` is passed to `connect`, ngRedux will listen to the `$destroy` event and unsubscribe the change listener itself, you don't need to keep track of your subscribtions.
121103

122-
destroy() {
123-
this.unsubscribe();
124-
}
104+
### Store API
105+
All of redux's store methods (i.e. `dispatch`, `subscribe` and `getState`) are exposed by $ngRedux and can be accessed directly. For example:
125106

107+
```JS
108+
$ngRedux.subscribe(() => {
109+
let state = $ngRedux.getState();
110+
//...
111+
})
126112
```
127113

114+
This means that you are free to use Redux basic API in advanced cases where `connect`'s API would not fill your needs.
128115

129-
#### Accessing Redux's store methods
130-
All of redux's store methods (i.e. `dispatch`, `subscribe` and `getState`) are exposed by $ngRedux and can be accessed directly. For example:
116+
117+
## Using DevTools
118+
In order to use Redux DevTools with your angular app, you need to install [react](https://www.npmjs.com/package/react), [react-redux](https://www.npmjs.com/package/react-redux) and [redux-devtools](https://www.npmjs.com/package/redux-devtools) as development dependencies.
131119

132120
```JS
133-
redux.bindActionCreators(actionCreator, $ngRedux.dispatch);
121+
[...]
122+
import { devTools, persistState } from 'redux-devtools';
123+
import { DevTools, DebugPanel, LogMonitor } from 'redux-devtools/lib/react';
124+
import React, { Component } from 'react';
125+
126+
angular.module('app', ['ngRedux'])
127+
.config(($ngReduxProvider) => {
128+
$ngReduxProvider.createStoreWith(rootReducer, [thunk], [devTools()]);
129+
})
130+
.run(($ngRedux, $rootScope) => {
131+
React.render(
132+
<App store={ $ngRedux }/>,
133+
document.getElementById('devTools')
134+
);
135+
136+
//To reflect state changes when disabling/enabling actions via the monitor
137+
//there is probably a smarter way to achieve that
138+
$ngRedux.subscribe(_ => {
139+
setTimeout($rootScope.$apply, 100);
140+
});
141+
});
142+
143+
class App extends Component {
144+
render() {
145+
return (
146+
<div>
147+
<DebugPanel top right bottom>
148+
<DevTools store={ this.props.store } monitor = { LogMonitor } />
149+
</DebugPanel>
150+
</div>
151+
);
152+
}
153+
}
134154
```
135-
**Note:** If you choose to use `subscribe` directly, be sure to [unsubscribe](#unsubscribing) when your current scope is $destroyed.
136155

137-
### Example:
138-
An example can be found here (in TypeScript): [tsRedux](https://github.com/wbuchwalter/tsRedux/blob/master/src/components/regionLister.ts).
156+
```HTML
157+
<body>
158+
<div ng-app='app'>
159+
[...]
160+
</div>
161+
<div id="devTools"></div>
162+
</body>
163+
```

examples/counter/components/counter.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@
44
<button ng-click='vm.increment()'>+</button>
55
<button ng-click='vm.decrement()'>-</button>
66
<button ng-click='vm.incrementIfOdd()'>Increment if odd</button>
7+
<button ng-click='vm.incrementAsync()'>Increment Async</button>
78
</div>

examples/counter/components/counter.js

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,14 @@ export default function counter() {
1313

1414
class CounterController {
1515

16-
constructor($ngRedux) {
17-
this.counter = 0;
18-
$ngRedux.connect(state => ({
19-
counter: state.counter
20-
}),
21-
({counter}) => this.counter = counter);
16+
constructor($ngRedux, $scope) {
17+
$ngRedux.connect($scope, this.mapStateToScope, CounterActions, 'vm');
18+
}
2219

23-
let {increment, decrement, incrementIfOdd} = bindActionCreators(CounterActions, $ngRedux.dispatch);
24-
this.increment = increment;
25-
this.decrement = decrement;
26-
this.incrementIfOdd = incrementIfOdd;
20+
// Which part of the Redux global state does our component want to receive on $scope?
21+
mapStateToScope(state) {
22+
return {
23+
counter: state.counter
24+
};
2725
}
2826
}

examples/counter/devTools.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { persistState } from 'redux-devtools';
2+
import { DevTools, DebugPanel, LogMonitor } from 'redux-devtools/lib/react';
3+
import React, { Component } from 'react';
4+
5+
angular.module('counter')
6+
.run(($ngRedux, $rootScope) => {
7+
React.render(
8+
<App store={ $ngRedux }/>,
9+
document.getElementById('devTools')
10+
);
11+
//Hack to reflect state changes when disabling/enabling actions via the monitor
12+
$ngRedux.subscribe(_ => {
13+
setTimeout($rootScope.$apply, 100);
14+
});
15+
});
16+
17+
18+
class App extends Component {
19+
render() {
20+
return (
21+
<div>
22+
<DebugPanel top right bottom>
23+
<DevTools store={ this.props.store } monitor = { LogMonitor } />
24+
</DebugPanel>
25+
</div>
26+
);
27+
}
28+
}
29+
30+

examples/counter/index.html

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44
<meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
55
<title>{%= o.htmlWebpackPlugin.options.title %}</title>
66
</head>
7-
<body ng-app='counter'>
8-
<ngr-counter></ngr-counter>
7+
<body>
8+
<div ng-app='counter'>
9+
<ngr-counter></ngr-counter>
10+
</div>
11+
<div id="devTools"></div>
912
</body>
1013
</html>

examples/counter/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ import 'ng-redux';
33
import rootReducer from './reducers';
44
import thunk from 'redux-thunk';
55
import counter from './components/counter';
6+
import { devTools } from 'redux-devtools';
67

78
angular.module('counter', ['ngRedux'])
89
.config(($ngReduxProvider) => {
9-
$ngReduxProvider.createStoreWith(rootReducer, [thunk]);
10+
$ngReduxProvider.createStoreWith(rootReducer, [thunk], [devTools()]);
1011
})
1112
.directive('ngrCounter', counter);

examples/counter/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
"babel-loader": "^5.3.2",
2222
"html-loader": "^0.3.0",
2323
"html-webpack-plugin": "^1.6.1",
24+
"react": "^0.13.3",
25+
"redux-devtools": "^1.1.1",
2426
"webpack": "^1.11.0",
2527
"webpack-dev-server": "^1.10.1"
2628
},

examples/counter/webpack.config.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ var HtmlWebpackPlugin = require('html-webpack-plugin');
55
module.exports = {
66
entry: [
77
'webpack/hot/dev-server',
8-
'./index.js'
8+
'./index.js',
9+
//Remove the following line to remove devTools
10+
'./devTools.js'
911
],
1012
output: {
1113
path: path.join(__dirname, 'dist'),

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
},
3131
"dependencies": {
3232
"invariant": "^2.1.0",
33+
"lodash": "^3.10.1",
3334
"redux": "^1.0.1"
3435
}
3536
}

0 commit comments

Comments
 (0)