Friday, September 22, 2017

AngularJS to React/Redux and back again

I've found AngularJS to have many analogues (corresponding pieces) to React/Redux.  Let me show you.

A Redux state almost exactly corresponds to an AngularJS $scope variable.

In a Redux reducer file, you have an initial state that looks like this:
const initialState = {
  inited: false,
  loading: true,
  user: {
    first: '',
    last: '',
    email: ''
  },
  error: ''
}

This initial state almost directly corresponds to an AngularJS $scope initial state.  If it doesn't, you can easily move the AngularJS variables that are outside the $scope variable into the $scope variable to match up exactly.

Not every AngularJS controller has an "inited" variable but many do.  In Redux, an "inited" variable is essential to signal that the Redux state has not been populated by data from the backend yet.  An easy way to do this is to check the "inited" prop in the componentDidMount() method in the React component that requires it (or any React component that requires it) and, if it is false, load up the data and set the "inited" value (well, call an action to call a reducer to do it) to true.  Even if you don't have an "inited" variable in your AngularJS $scope variables, it is easy to add one.  The AngularJS controller function might be converted like this:
componentDidMount() {
  if (!this.props.inited) {
    // do this only once
    this.props.model('inited', true)
    // in AngularJS, this is the controller function
    getUser(this.props)
  }
}
Speaking of Redux reducers and so on (and since I brought it up in the code directly above), it is very convenient and useful to make a "model" action along the lines of an AngularJS ng-model.  The "model" action will take a string indicating the value to change, e.g. props.model('user.first', 'John'), and ultimately create something that looks like this:
<input type="text" value={ props.user.first } 
  onChange={e => props.model('user.first', e.target.value)}/>
Often, I've found it easy to copy the HTML from my AngularJS templates (HTML) into the React render() method and fix them up.  There are a three tricks that you have to remember:
  1. Do a global search and replace on "class=" to replace with "className=".  Before you make this change, your React component may render but will not apply the CSS classes.
  2. Search for each "style=" (e.g. style="background: blue") and convert each instance (e.g. style={ { background: 'blue' } }).  (Don't forget to make it a JavaScript object by having the double curly braces.) . Really, if your AngularJS still contains "style" attributes, it's just better to convert them into CSS classes and "class" attributes rather than go through the hassle of converting them to React.  Before you make this change, your React component will often render a blank screen; this is a big hint that you have unconverted "style" attributes.
  3. Change each AngularJS template to have a single root element, not multiple root elements.  Multiple root elements are allowed in AngularJS but there is no reason to use them and it is easy enough to just wrap it all in a div tag so it is easy to convert to React.  React components (specifically, JSX and the render() method) require a single root element.
react-router-redux is a pretty convenient analogue to AngularJS router modules (either the built-in or ui-router).

Factories and services can be stripped of their AngularJS specifics and placed in your React application's modules folder.  Usually, I place them in a subfolder of modules.  Often, factories and services use the AngularJS $http service; you can convert these calls to use fetch (from the isomorphic-fetch npm module) or Axios (or superagent or request or whatever).

AngularJS filters are essentially just functions; I just strip out the code and drop them into the modules folder itself.  A call to a filter is easy to convert.  In AngularJS, a filter looks like this:
{{ 'mytitle' | translate }}
In React, it becomes:
{ translate('mytitle') }
In AngularJS, we have the "ng-if" and "ng-repeat" attributes which are used a lot in almost every AngularJS application to do control flow.

The AngularJS "ng-if" attribute is used like this:
<div ng-if="showing">
  My title
</div>
In React, it becomes a conditional, like this:
{ props.showing? (
<div>
  My title
</div>
) : '' }
An AngularJS "ng-repeat" attribute is a little more complicated.  From this:
<table>
<thead>
<tr>
<th ng-repeat="column in columns">
  {{ column }}
</th>
</tr>
</thead>
</table>
To this in React:
<table>
<thead>
<tr>
{ props.columns.length? props.columns
  .map((column, index) => (
<th key={ index }>
  { column }
</th>)
  ).reduce((p, c) => [p, '', c]) : ''}
</tr>
</thead>
</table>
Admittedly, this post is a little slapdash and not really enough to convert an entire AngularJS to React/Redux. But I think that you will agree that there are a lot of concepts that easily port from AngularJS to Redux with a little tweaks, here and there.

No comments:

Post a Comment