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,584