Skip to content

Commit 196b00e

Browse files
committed
Merge pull request #29 from EvanOxfeld/port-async-example
Port redux project async example to ng-redux
2 parents cb7f03c + 6b95227 commit 196b00e

File tree

14 files changed

+374
-0
lines changed

14 files changed

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

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+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export const REQUEST_POSTS = 'REQUEST_POSTS';
2+
export const RECEIVE_POSTS = 'RECEIVE_POSTS';
3+
export const SELECT_REDDIT = 'SELECT_REDDIT';
4+
export const INVALIDATE_REDDIT = 'INVALIDATE_REDDIT';

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

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: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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 asyncService from './actions/asyncService';
8+
import app from './containers/app';
9+
import picker from './components/picker';
10+
import posts from './components/posts';
11+
12+
angular.module('async', [ngRedux])
13+
.config(($ngReduxProvider) => {
14+
$ngReduxProvider.createStoreWith(rootReducer, [thunk, createLogger()]);
15+
})
16+
.service('AsyncActions', asyncService)
17+
.directive('ngrAsync', app)
18+
.directive('ngrPicker', picker)
19+
.directive('ngrPosts', posts);

0 commit comments

Comments
 (0)