Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Example of using immutable.js and reselect #145

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions examples/async-immutable/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"presets": ["es2015"]
}
67 changes: 67 additions & 0 deletions examples/async-immutable/actions/asyncService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import * as types from '../constants/ActionTypes';

function selectReddit(reddit) {
return {
type: types.SELECT_REDDIT,
reddit
};
}

function invalidateReddit(reddit) {
return {
type: types.INVALIDATE_REDDIT,
reddit
};
}

function requestPosts(reddit) {
return {
type: types.REQUEST_POSTS,
reddit
};
}

function receivePosts(reddit, json) {
return {
type: types.RECEIVE_POSTS,
reddit: reddit,
posts: json.data.children.map(child => child.data),
receivedAt: Date.now()
};
}

export default function asyncService($http) {
function fetchPosts(reddit) {
return dispatch => {
dispatch(requestPosts(reddit));
return $http.get(`http://www.reddit.com/r/${reddit}.json`)
.then(response => response.data)
.then(json => dispatch(receivePosts(reddit, json)));
};
}

function shouldFetchPosts(state, reddit) {
const posts = state.postsByReddit.get(reddit);
if (!posts) {
return true;
}
if (posts.get('isFetching')) {
return false;
}
return posts.get('didInvalidate');
}

function fetchPostsIfNeeded(reddit) {
return (dispatch, getState) => {
if (shouldFetchPosts(getState(), reddit)) {
return dispatch(fetchPosts(reddit));
}
};
}

return {
selectReddit,
invalidateReddit,
fetchPostsIfNeeded
};
}
7 changes: 7 additions & 0 deletions examples/async-immutable/components/picker.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<span>
<h1>{{picker.value}}</h1>
<select ng-options="option for option in picker.options"
ng-model="picker.value"
ng-change="picker.onChange(picker.value)">
</select>
</span>
17 changes: 17 additions & 0 deletions examples/async-immutable/components/picker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export default function picker() {
return {
restrict: 'E',
controllerAs: 'picker',
controller: PickerController,
template: require('./picker.html'),
scope: {
options: '=',
value: '=',
onChange: '='
},
bindToController: true
};
}

class PickerController {
}
3 changes: 3 additions & 0 deletions examples/async-immutable/components/posts.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<ul>
<li ng-repeat="post in posts.posts">{{post.title}}</li>
</ul>
15 changes: 15 additions & 0 deletions examples/async-immutable/components/posts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export default function posts() {
return {
restrict: 'E',
controllerAs: 'posts',
controller: PostsController,
template: require('./posts.html'),
scope: {
posts: '=',
},
bindToController: true
};
}

class PostsController {
}
4 changes: 4 additions & 0 deletions examples/async-immutable/constants/ActionTypes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const REQUEST_POSTS = 'REQUEST_POSTS';
export const RECEIVE_POSTS = 'RECEIVE_POSTS';
export const SELECT_REDDIT = 'SELECT_REDDIT';
export const INVALIDATE_REDDIT = 'INVALIDATE_REDDIT';
22 changes: 22 additions & 0 deletions examples/async-immutable/containers/app.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<div>
<ngr-picker value="app.selectedReddit"
on-change="app.handleChange"
options="app.options">
</ngr-picker>
<p>
<span ng-show="app.lastUpdated">
Last updated at {{ app.lastUpdated | date:'mediumTime' }}.
</span>
<a href="#"
ng-show="!app.isFetching"
ng-click="app.handleRefreshClick()">
Refresh
</a>
</p>
<h2 ng-show="app.isFetching && app.posts.length === 0">Loading...</h2>
<h2 ng-show="!app.isFetching && app.posts.length === 0">Empty.</h2>
<div ng-show="app.posts.length > 0"
ng-style="{ opacity: app.isFetching ? 0.5 : 1 }">
<ngr-posts posts="app.posts"></ngr-posts>
</div>
</div>
55 changes: 55 additions & 0 deletions examples/async-immutable/containers/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { getPostsTojs, getIsFetching } from '../selectors'

export default function app() {
return {
restrict: 'E',
controllerAs: 'app',
controller: AppController,
template: require('./app.html'),
scope: {}
};
}

class AppController {

constructor($ngRedux, AsyncActions, $scope) {
const unsubscribe = $ngRedux.connect(this.mapStateToThis, AsyncActions)((selectedState, actions) => {
this.componentWillReceiveStateAndActions(selectedState, actions);
Object.assign(this, selectedState, actions);
});
this.options = ['angularjs', 'frontend'];
this.handleChange = this.handleChange.bind(this);
this.handleRefreshClick = this.handleRefreshClick.bind(this);

this.fetchPostsIfNeeded(this.selectedReddit);
$scope.$on('$destroy', unsubscribe);
}

componentWillReceiveStateAndActions(nextState, nextActions) {
if (nextState.selectedReddit !== this.selectedReddit) {
nextActions.fetchPostsIfNeeded(nextState.selectedReddit);
}
}

handleChange(nextReddit) {
this.selectReddit(nextReddit);
}

handleRefreshClick() {
this.invalidateReddit(this.selectedReddit);
this.fetchPostsIfNeeded(this.selectedReddit);
}

mapStateToThis(state) {
const { selectedReddit, postsByReddit } = state;

return {
selectedReddit,
// Use selectors here
posts: getPostsTojs(state),
isFetching: getIsFetching(state),
// Or get value from the state directly without selectors
lastUpdated: postsByReddit.get(selectedReddit) && postsByReddit.get(selectedReddit).get('lastUpdated')
};
}
}
12 changes: 12 additions & 0 deletions examples/async-immutable/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
<title>{%= o.htmlWebpackPlugin.options.title %}</title>
</head>
<body>
<div ng-app="async">
<ngr-async></ngr-async>
</div>
</body>
</html>
19 changes: 19 additions & 0 deletions examples/async-immutable/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//import 'babel-core/polyfill';
import angular from 'angular';
import ngRedux from 'ng-redux';
import thunk from 'redux-thunk';
import createLogger from 'redux-logger';
import rootReducer from './reducers';
import asyncService from './actions/asyncService';
import app from './containers/app';
import picker from './components/picker';
import posts from './components/posts';

angular.module('async', [ngRedux])
.config(($ngReduxProvider) => {
$ngReduxProvider.createStoreWith(rootReducer, [thunk, createLogger()]);
})
.service('AsyncActions', asyncService)
.directive('ngrAsync', app)
.directive('ngrPicker', picker)
.directive('ngrPosts', posts);
37 changes: 37 additions & 0 deletions examples/async-immutable/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "ng-redux-async-immutable-example",
"version": "0.0.0",
"description": "ng-redux async immutable example",
"scripts": {
"start": "webpack-dev-server --content-base dist/ --inline"
},
"repository": {
"type": "git",
"url": "https://github.com/angular-redux/ng-redux.git"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/angular-redux/ng-redux/issues"
},
"homepage": "https://github.com/angular-redux/ng-redux#readme",
"dependencies": {
"angular": "^1.4.4",
"babel-core": "^6.8.0",
"babel-loader": "^6.2.4",
"babel-preset-es2015": "^6.6.0",
"immutable": "^3.8.1",
"ng-redux": "^3.0.0",
"redux": "^3.0.0",
"redux-logger": "^2.0.2",
"redux-thunk": "^1.0.0",
"reselect": "^3.0.1"
},
"devDependencies": {
"babel-core": "^6.9.1",
"babel-loader": "^6.2.4",
"html-loader": "^0.3.0",
"html-webpack-plugin": "^2.30.1",
"webpack": "^1.13.1",
"webpack-dev-server": "^1.14.1"
}
}
60 changes: 60 additions & 0 deletions examples/async-immutable/reducers/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { combineReducers } from 'redux';
import {
SELECT_REDDIT, INVALIDATE_REDDIT,
REQUEST_POSTS, RECEIVE_POSTS
} from '../constants/ActionTypes';
import { fromJS } from 'immutable'


function selectedReddit(state = 'angularjs', action) {
switch (action.type) {
case SELECT_REDDIT:
return action.reddit;
default:
return state;
}
}

function posts(state = fromJS({
isFetching: false,
didInvalidate: false,
items: []
}), action) {
switch (action.type) {
case INVALIDATE_REDDIT:
return state.set('didInvalidate', true);
case REQUEST_POSTS:
return state.mergeDeep(fromJS({
isFetching: true,
didInvalidate: false,
}));
case RECEIVE_POSTS:
var updatedState = state.mergeDeep(fromJS({
isFetching: false,
didInvalidate: false,
items: action.posts,
lastUpdated: action.receivedAt
}));
return updatedState;
default:
return state;
}
}

function postsByReddit(state = fromJS({}), action) {
switch (action.type) {
case INVALIDATE_REDDIT:
case RECEIVE_POSTS:
case REQUEST_POSTS:
return state.set(action.reddit, posts(state.get(action.reddit), action));
default:
return state;
}
}

const rootReducer = combineReducers({
postsByReddit,
selectedReddit
});

export default rootReducer;
26 changes: 26 additions & 0 deletions examples/async-immutable/selectors/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { createSelector } from 'reselect'

function getPostsState(state) {
return state.postsByReddit.get(state.selectedReddit);
}

const getPostList = createSelector(
[getPostsState],
(postsState) => {
return postsState && postsState.get('items');
}
);

export const getPostsTojs = createSelector(
[getPostList],
(postList) => {
return (postList && postList.toJS()) || [];
}
);

export const getIsFetching = createSelector(
[getPostsState],
(postsState) => {
return postsState && postsState.get('isFetching')
}
);
23 changes: 23 additions & 0 deletions examples/async-immutable/store/configureStore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { createStore, applyMiddleware } from 'redux';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this file from another example? And I don't think it's actually used anywhere, right?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the code is originally from https://github.com/angular-redux/ng-redux/tree/master/examples/async. I think you are right. The file isn't used in this example.

import thunkMiddleware from 'redux-thunk';
import loggerMiddleware from 'redux-logger';
import rootReducer from '../reducers';

const createStoreWithMiddleware = applyMiddleware(
thunkMiddleware,
loggerMiddleware
)(createStore);

export default function configureStore(initialState) {
const store = createStoreWithMiddleware(rootReducer, initialState);

if (module.hot) {
// Enable Webpack hot module replacement for reducers
module.hot.accept('../reducers', () => {
const nextRootReducer = require('../reducers');
store.replaceReducer(nextRootReducer);
});
}

return store;
}
Loading