Skip to content

Commit 9f2c547

Browse files
committed
Port redux async example to ng-redux
1 parent cb7f03c commit 9f2c547

File tree

13 files changed

+368
-0
lines changed

13 files changed

+368
-0
lines changed

examples/async/actions/async.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import fetch from 'isomorphic-fetch';
2+
3+
export const REQUEST_POSTS = 'REQUEST_POSTS';
4+
export const RECEIVE_POSTS = 'RECEIVE_POSTS';
5+
export const SELECT_REDDIT = 'SELECT_REDDIT';
6+
export const INVALIDATE_REDDIT = 'INVALIDATE_REDDIT';
7+
8+
export function selectReddit(reddit) {
9+
return {
10+
type: SELECT_REDDIT,
11+
reddit
12+
};
13+
}
14+
15+
export function invalidateReddit(reddit) {
16+
return {
17+
type: INVALIDATE_REDDIT,
18+
reddit
19+
};
20+
}
21+
22+
function requestPosts(reddit) {
23+
return {
24+
type: REQUEST_POSTS,
25+
reddit
26+
};
27+
}
28+
29+
function receivePosts(reddit, json) {
30+
return {
31+
type: RECEIVE_POSTS,
32+
reddit: reddit,
33+
posts: json.data.children.map(child => child.data),
34+
receivedAt: Date.now()
35+
};
36+
}
37+
38+
function fetchPosts(reddit) {
39+
return dispatch => {
40+
dispatch(requestPosts(reddit));
41+
return fetch(`http://www.reddit.com/r/${reddit}.json`)
42+
.then(response => response.json())
43+
.then(json => dispatch(receivePosts(reddit, json)));
44+
};
45+
}
46+
47+
function shouldFetchPosts(state, reddit) {
48+
const posts = state.postsByReddit[reddit];
49+
if (!posts) {
50+
return true;
51+
}
52+
if (posts.isFetching) {
53+
return false;
54+
}
55+
return posts.didInvalidate;
56+
}
57+
58+
export function fetchPostsIfNeeded(reddit) {
59+
return (dispatch, getState) => {
60+
if (shouldFetchPosts(getState(), reddit)) {
61+
return dispatch(fetchPosts(reddit));
62+
}
63+
};
64+
}

examples/async/components/picker.html

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<span>
2+
<h1>{{picker.value}}</h1>
3+
<select ng-options="option for option in picker.options"
4+
ng-model="picker.value"
5+
ng-change="picker.onChange(picker.value)">
6+
</select>
7+
</span>

examples/async/components/picker.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
export default function picker() {
2+
return {
3+
restrict: 'E',
4+
controllerAs: 'picker',
5+
controller: PickerController,
6+
template: require('./picker.html'),
7+
scope: {
8+
options: '=',
9+
value: '=',
10+
onChange: '='
11+
},
12+
bindToController: true
13+
};
14+
}
15+
16+
class PickerController {
17+
}

examples/async/components/posts.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<ul>
2+
<li ng-repeat="post in posts.posts">{{post.title}}</li>
3+
</ul>

examples/async/components/posts.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export default function posts() {
2+
return {
3+
restrict: 'E',
4+
controllerAs: 'posts',
5+
controller: PostsController,
6+
template: require('./posts.html'),
7+
scope: {
8+
posts: '=',
9+
},
10+
bindToController: true
11+
};
12+
}
13+
14+
class PostsController {
15+
}

examples/async/containers/app.html

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<div>
2+
<ngr-picker value="app.selectedReddit"
3+
on-change="app.handleChange"
4+
options="app.options">
5+
</ngr-picker>
6+
<p>
7+
<span ng-show="app.lastUpdated">
8+
Last updated at {{ app.lastUpdated | date:'mediumTime' }}.
9+
</span>
10+
<a href="#"
11+
ng-show="!app.isFetching"
12+
ng-click="app.handleRefreshClick()">
13+
Refresh
14+
</a>
15+
</p>
16+
<h2 ng-show="app.isFetching && app.posts.length === 0">Loading...</h2>
17+
<h2 ng-show="!app.isFetching && app.posts.length === 0">Empty.</h2>
18+
<div ng-show="app.posts.length > 0"
19+
ng-style="{ opacity: app.isFetching ? 0.5 : 1 }">
20+
<ngr-posts posts="app.posts"></ngr-posts>
21+
</div>
22+
</div>

examples/async/containers/app.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import * as AsyncActions from '../actions/async';
2+
3+
export default function app() {
4+
return {
5+
restrict: 'E',
6+
controllerAs: 'app',
7+
controller: AppController,
8+
template: require('./app.html'),
9+
scope: {}
10+
};
11+
}
12+
13+
class AppController {
14+
15+
constructor($ngRedux, $scope) {
16+
const unsubscribe = $ngRedux.connect(this.mapStateToThis, AsyncActions)((selectedState, actions) => {
17+
this.componentWillReceiveStateAndActions(selectedState, actions);
18+
Object.assign(this, selectedState, actions);
19+
});
20+
this.options = ['angularjs', 'frontend'];
21+
this.handleChange = this.handleChange.bind(this);
22+
this.handleRefreshClick = this.handleRefreshClick.bind(this);
23+
24+
this.fetchPostsIfNeeded(this.selectedReddit);
25+
}
26+
27+
componentWillReceiveStateAndActions(nextState, nextActions) {
28+
if (nextState.selectedReddit !== this.selectedReddit) {
29+
nextActions.fetchPostsIfNeeded(nextState.selectedReddit);
30+
}
31+
}
32+
33+
handleChange(nextReddit) {
34+
this.selectReddit(nextReddit);
35+
}
36+
37+
handleRefreshClick() {
38+
this.invalidateReddit(this.selectedReddit);
39+
this.fetchPostsIfNeeded(this.selectedReddit);
40+
}
41+
42+
// Which part of the Redux global state does our component want to receive?
43+
mapStateToThis(state) {
44+
const { selectedReddit, postsByReddit } = state;
45+
const {
46+
isFetching,
47+
lastUpdated,
48+
items: posts
49+
} = postsByReddit[selectedReddit] || {
50+
isFetching: true,
51+
items: []
52+
};
53+
54+
return {
55+
selectedReddit,
56+
posts,
57+
isFetching,
58+
lastUpdated
59+
};
60+
}
61+
}

examples/async/index.html

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
5+
<title>{%= o.htmlWebpackPlugin.options.title %}</title>
6+
</head>
7+
<body>
8+
<div ng-app="async">
9+
<ngr-async></ngr-async>
10+
</div>
11+
</body>
12+
</html>

examples/async/index.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import 'babel-core/polyfill';
2+
import angular from 'angular';
3+
import ngRedux from 'ng-redux';
4+
import thunk from 'redux-thunk';
5+
import createLogger from 'redux-logger';
6+
import rootReducer from './reducers';
7+
import app from './containers/app';
8+
import picker from './components/picker';
9+
import posts from './components/posts';
10+
11+
angular.module('async', [ngRedux])
12+
.config(($ngReduxProvider) => {
13+
$ngReduxProvider.createStoreWith(rootReducer, [thunk, createLogger()]);
14+
})
15+
.directive('ngrAsync', app)
16+
.directive('ngrPicker', picker)
17+
.directive('ngrPosts', posts);

examples/async/package.json

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"name": "ng-redux-async-example",
3+
"version": "0.0.0",
4+
"description": "ng-redux async example",
5+
"scripts": {
6+
"start": "webpack-dev-server --content-base dist/ --inline"
7+
},
8+
"repository": {
9+
"type": "git",
10+
"url": "https://github.com/wbuchwalter/ngRedux.git"
11+
},
12+
"license": "MIT",
13+
"bugs": {
14+
"url": "https://github.com/wbuchwalter/ngRedux/issues"
15+
},
16+
"homepage": "https://github.com/wbuchwalter/ngRedux#readme",
17+
"dependencies": {
18+
"angular": "^1.4.4",
19+
"isomorphic-fetch": "^2.1.1",
20+
"ng-redux": "^3.0.0",
21+
"redux": "^3.0.0",
22+
"redux-logger": "^2.0.2",
23+
"redux-thunk": "^1.0.0"
24+
},
25+
"devDependencies": {
26+
"babel-core": "^5.6.18",
27+
"babel-loader": "^5.1.4",
28+
"html-loader": "^0.3.0",
29+
"html-webpack-plugin": "^1.6.1",
30+
"webpack": "^1.9.11",
31+
"webpack-dev-server": "^1.10.1"
32+
}
33+
}

0 commit comments

Comments
 (0)