Replacing Angular 1
with
React and Redux
Pieces of A Frontend
Angular 1 | React | |
---|---|---|
View | HTML template + Controller | JSX Component |
State | Services/Factories | Redux |
Rest/API calls | Factory/Service? Controller? | actions/reducers middleware |
Dependencies | Bower/CDN | NPM Import/Require |
Dep Injection | Script Tag | Included in bundle |
Require & Bundling | Browserify with Grunt/Gulp | Webpack |
Library
(REST + Cookie/localStorage)
Redux (state management)
Containers
(smart components)
Components
View template
Controllers
Services (state management, cookie/localStorage)
Directives
React
Angular 1
React with Redux
Pieces
Factories
(REST + Cookie/localStorage)
React Stack Diagram
Example
Login Form
Example
Login Form
- Login HTML template
- Login Controller
- Auth Factory (with state handling)
Angular
app.factory('AuthService', function ($http, $cookieStore, $state, ENV) {
var authService = {};
var apiEndPoint = ENV.apiEndPoint,
sessionCookie = ENV.sessionCookie,
uiEndPoint = ENV.uiEndPoint;
authService.login = function(credentials) {
return $http.post(apiEndPoint + 'login', credentials);
};
authService.getUser = function() {
if (authService.getSessionId()) {
return $http.get(apiEndPoint + 'user?sessionId=' + authService.getSessionId(), {cache: true});
} else {
$state.go('login')
}
};
authService.forgot = function(form) {
return $http.post(apiEndPoint + 'forgot', {email: form.email, endPoint: uiEndPoint+'#/update'});
};
authService.update = function(token, email, password) {
return $http.post(apiEndPoint + 'updatePassword', {token: token, email: email, password: password});
};
authService.register = function(credentials) {
var data = {
email: credentials.email,
password: credentials.password,
endPoint: uiEndPoint+'#/confirmRegistration'
};
return $http.post(apiEndPoint + 'register', data);
};
authService.getSessionId = function () {
return $cookieStore.get(sessionCookie);
};
authService.isAuthenticated = function() {
return authService.getSessionId() !== undefined;
};
return authService;
});
Factory/Service?
State is not stored with each response, but instead passed to controller
Controller is responsible for setting state (factory is NOT singleton?)
View
app.controller('LoginCtrl', function($scope, $state, ENV, AuthService) {
$scope.isSubmitting = false;
if (AuthService.isAuthenticated()) {
$state.go('dashboard.home');
}
$scope.login = function(form) {
$scope.isSubmitting = true;
delete $scope.errorMessage;
if (ValidationService.validate($element)) {
AuthService.login(form)
.success(function(data, status, headers, config) {
delete $scope.error
$cookieStore.put(ENV.sessionCookie, data.sessionId);
$scope.isSubmitting = false;
$state.go('dashboard.home');
}).error(function(data, status, headers, config) {
$scope.error = true;
$scope.isSubmitting = false;
if (data && data.message) {
$scope.errorMessage = data.message
} else {
$scope.errorMessage = 'API ERROR'
}
});
} else {
$scope.isSubmitting = false;
}
};
});
View
<!-- define angular controller -->
<body ng-controller="mainController">
...
<!-- MAIN CONTENT AND INJECTED VIEWS -->
<div id="main">
{{ message }}
<div ng-view></div>
</div>
</body>
Example
Login Form
React
- Login component
- Account Actions (redux-matter)
- Account Reducer (redux-matter)
import React, {Component, PropTypes} from 'react';
import { Link } from 'react-router';
import { Actions } from 'redux-matter';
import TextField from 'material-ui/lib/text-field';
import RaisedButton from 'material-ui/lib/raised-button';
import CircularProgress from 'material-ui/lib/circular-progress';
import Checkbox from 'material-ui/lib/checkbox';
import './LoginForm.scss'; //Thanks to style-loader
export default class LoginForm extends Component {
static propTypes = {
onLogin: PropTypes.func
};
handleInputChange = (name, e) => {
e.preventDefault();
this.setState({
[name]: e.target.value
});
}
handleLogin = (e) => {
if(this.props.onLogin){
this.props.onLogin(this.state);
}
}
render(){
return (
<form className="LoginForm" onSubmit={ this.handleLogin }>
<TextField
floatingLabelText="Username/Email"
onChange={() => this.handleInputChange('username')}
/>
<TextField
floatingLabelText="Password"
type="password"
onChange={() => this.handlePrivateChange('password')}
/>
<div className="LoginForm-Submit">
<RaisedButton
label="Login"
type="submit"
disabled={ this.props.account && this.props.account.isFetching}
/>
</div>
</form>
);
}
}
Login Form Component
- HTML elements
- Imports SCSS
- State managed internally
- Actions passed as props (this.props.onLogin)
Login Container (Smart Component)
import React, {Component, PropTypes} from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { Link } from 'react-router';
import { Actions } from 'redux-matter';
import LoginForm from '../../components/LoginForm/LoginForm';
import Paper from 'material-ui/lib/paper';
import './Login.scss'; //Thanks to style-loader
class Login extends Component {
constructor(props) {
super(props);
}
handleLogin(loginData) {
this.props.login(loginData);
}
render() {
return (
<div className="Login">
<h2>Login</h2>
<Paper className="Login-Panel" zDepth={1}>
<LoginForm onLogin={ this.handleLogin } />
</Paper>
</div>
);
}
}
//Place state of redux store into props of component
function mapStateToProps(state) {
return {
account: state.account,
router: state.router
};
}
//Place action methods into props
function mapDispatchToProps(dispatch) {
return bindActionCreators(Actions, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(Login);
- State connected to Redux
- Calls redux action from redux-matter (this.props.login)
- Imports LoginForm Component
Title Text
- Bullet One
- Bullet Two
- Bullet Three
Useful Links
https://github.com/KyperTech/redux-grout
Replace Angular 1 with React
By Scott Prue
Replace Angular 1 with React
- 6,818