Skip to content

danfoley1991/barebones-angular

Repository files navigation

Migrating from AngularJS to React

This is an AngularJS application, demonstrating how React components can be integrated, with a view to migrating the whole application to React.

Benefits of migrating to React

  • It is beneficial to use an up to date technology stack. AngularJS is no longer being supported, or developed. It has been superceded by Angular 2+. React is currently the de-facto standard web UI framework. It has a lot of support and adoption, and is being actively maintained/developed.
  • React’s architecture is extremely structured. Based on components, it would create a very clean and maintainable Javascript codebase. We are also able to use state management frameworks e.g. Redux. This greatly improves the architecture, provides much better state management for the application, particularly between components. It's also easier to test the code, because Redux uses pure Javascript functions to alter the state, with no side effects.
  • Redux developer tools offer excellent debugging features. This includes the ability to easily track state changes, and even roll back the application state and step through individual actions.
  • Many developers are either learning to use React, or are already skilled. This means that there would be fewer problems in attracting and retaining developers in the team.
  • New developers on the team would be able to understand the code more easily, and become productive sooner.
  • Using React opens up possibilities of utilising more efficient development tools. E.g. headless Visual Studio debugging, and modern testing frameworks.
  • Improved UI testing. The current Jest/Karma JS tests are not ideal. Using React components allows us to engage a React based JS testing framework (e.g. testing-library https://testing-library.com/docs/react-testing-library/intro). This allows more focussed tests, and quicker turnaround during development. These tests can also be fully automatic, and triggered when a developer makes changes to the code. This instant feedback cycle allows earlier detection of problems, and better quality code.
  • There is a huge amount of useful React components to leverage. UI libraries such as Bootstrap now have versions based on React. We would have the potential to use many third party components.

Terminology

AngularJS provides a component based architecture, which is structured using the “module”. Modules can contain “directives”, “services”, “filters” and “controllers”.

Adding a new React component to an AngularJS application

We begin by describing the most simple case of adding a new React component into an existing AngularJS application.

1. Add dependencies to the project

We first require the following entries added in the ‘dependencies’ section of the package.json file located at the project root (Note that current versions may be different):

"babel-eslint": "^10.1.0" "eslint": "^6.8.0" "prop-types": "^15.7.2", "react": "^16.13.1", "react-dom": "^16.13.1", "react2angular": "^4.0.6"

2. Define the React component

Consider the following simple React component. It’s irrelevant what the component does, it’s simply being used to illustrate the integration procedure:

class MySpinner extends Component {
  render() {
    return
    <div>
    <p>HELLO WORLD</p>
    </div>
  }
}

export default MySpinner;

3. Define an Angular module for the React component

This React component can be defined as an AngularJS module by writing the following:

import { react2angular } from 'react2angular';
angular.module('app.myspinner', \[\]).component('mySpincomponent',
               react2angular(require('./components/MySpinner').default, \[\]));

4. Create the top level Angular app module

The entry point for an angular application is the top level module. In creating this, we specify all the required dependent modules, including the one we just defined.

require('./modules/site-config');
require('./modules/home');
require('./modules/event');
require('./modules/date');
require('./modules/contact');
require('./modules/comms');
require('./modules/calendar');
require('./modules/backend');
require('./modules/app-config');
require('./modules/spinner');

var app = angular.module('app', [
 'app.myspinner',
 'app.config',
 'app.comms',
 'app.car',
 ...
]);

5. Using the new component

The previously defined Angular module/React component can be used in the usual way, as part of a web page. Note the use of AngularJS naming convention: e.g. my-component = myComponent):

<div class="collapse navbar-collapse" ng-class="!navCollapsed && 'in'">
 <ul class="nav navbar-nav">
 <li ui-sref-active="active" ng-click="navCollapsed = !navCollapsed"><a ui-sref="home">Home</a></li>
 <li ui-sref-active="active" ng-click="navCollapsed = !navCollapsed"><a ui-sref="car">Car</a></li>
 <li ui-sref-active="active" ng-click="navCollapsed = !navCollapsed"><a ui-sref="about">About</a></li>
 <li ui-sref-active="active" ng-click="navCollapsed = !navCollapsed">**<my-spincomponent></my-spincomponent>**</li>
 </ul>
</div>

Replacing an existing AngularJS directive

A more typical situation is that an angular module already exists, and contains a directive. Consider the following html which refers to an Angular element directive:

<div ng-controller="defaultCtrl">
 <unordered-list list-source="products" list-property="price | currency" />
</div>

The corresponding definition of this directive could be:

angular.module("app.unorderedlist", []).directive("unorderedList", function () {
 return {
   link: function (scope, element, attrs) {
   var data = scope\[attrs\["unorderedList"\] || attrs\["listSource"\]\];
   var propertyExpression = attrs\["listProperty"\] || "price | currency";
   if (angular.isArray(data)) {
     var listElem = angular.element("<ul>");
     if (element\[0\].nodeName == "\#comment") {
       element.parent().append(listElem);
     } else {
       element.append(listElem);
   }

   for (var i = 0; i < data.length; i++) {
     var itemElement = angular.element("<li>").text(scope.$eval(propertyExpression, data\[i\]));
     listElem.append(itemElement);
   }
  }},

   restrict: "EACM"
 }}).controller("defaultCtrl", function ($scope) {

   $scope.products = [
     { name: "Apples", category: "Fruit", price: 1.20, expiry: 10 },
     { name: "Bananas", category: "Fruit", price: 2.42, expiry: 7 },
     { name: "Pears", category: "Fruit", price: 2.02, expiry: 6 }
   ];
 })

So how would we convert this into a React component? Here are the steps:

1. Define the React component

First we need to create an equivalent React component for the Angular directive. This can be implemented as a class based component. Stateful React components use a ‘state’ object, which is analagous to the ‘scope’ in an Angular directive.

Notice also how much cleaner the generated HTML (JSX) code is in the render() function, compared to the code in the Angular directive.

import React, { Component } from 'react';

class MyList extends Component {
constructor(props) {
 super(props);
 this.state = {
     products : [
         { name: "Apples", category: "Fruit", price: 1.20, expiry: 10 },
         { name: "Bananas", category: "Fruit", price: 2.42, expiry: 7 },
         { name: "Pears", category: "Fruit", price: 2.02, expiry: 6 }
     ]
     };
 }

 render() {
     const productRows = this.state.products.map((product, index) =>
     <li key={index}>{product.name}</li>
 );

 return (
     <div>
     <ul>
     {productRows}
     </ul>
     </div>
 );
 }
}

export default MyList;

2. Define an Angular module for the React component

This React component can be defined as an AngularJS module by writing the following:

import { react2angular } from 'react2angular';

angular.module('app.unorderedlist', []).component('unorderedList', react2angular(require('./components/MyList').default, []));

3. Create the top level Angular app module

The entry point for an angular application is the top level module. In creating this, we specify all the required dependent modules, including the one we just defined.

require('./modules/site-config');
require('./modules/home');
require('./modules/event');
require('./modules/date');
require('./modules/contact');
require('./modules/comms');
require('./modules/calendar');
require('./modules/backend');
require('./modules/app-config');
require('./modules/spinner');
require('./modules/unorderedlist');

var app = angular.module('app', [
 'app.unorderedlist',
 'app.myspinner',
 'app.config',
 'app.comms',
 'app.car',
 ...
]);

4. Using the new component

The previously defined Angular module/React component can be used in the usual way, as part of a web page. Note the use of AngularJS naming convention: e.g. my-component = myComponent).

Note: We removed the ‘ng-controller’ attribute from the <div> element:

&lt;div&gt;
&lt;unordered-list list-source="products" list-property="price | currency" /&gt;
&lt;/div&gt;

Useful React component libraries

React Bootstrap

Web UI libraries have existed for some time. One of the most popular is Bootstrap. This provides a lot of useful UI components, such as spinner and accordian. A version of Bootstrap has been developed to work with React. https://react-bootstrap.github.io/getting-started/introduction/

To test this we initially used Webpack, which is a popular module bundler for Javascript files. In theory it can be used to include associated CSS files, but as we will describe, this caused some problems.

To start with, we can import the required React-Bootstrap items into our React component:

import React, { Component } from 'react'
import Spinner from 'react-bootstrap/Spinner';
import Button from 'react-bootstrap/Button';
import 'bootstrap/dist/css/bootstrap.css';

We are now able to make use of Spinner and Button in our React component definition

class MySpinner extends Component {

 render() {
     return (
         &lt;div&gt;
         &lt;Button variant="primary" enabled="true"&gt;
         &lt;Spinner
         as="span"
         animation="border"
         size="sm"
         role="status"
         aria-hidden="true"
         /&gt;
         Loading...
         &lt;/Button&gt;
         &lt;/div&gt;
       );
   }
}

Webpack requires CSS loaders, which are specified in the webpack.config.js file:

*module: {
loaders: \[
 {
 test: /\\.css$/,
 loader: 'style-loader!css-loader'
 }
 ...
}*

This should be all we need to get the application working. But the following problem occurred at run time:

ERROR in ./~/css-loader/dist/cjs.js!./~/bootstrap/dist/css/bootstrap.css

Module build failed: TypeError: Cannot read property 'split' of undefined

 at Object.loader (/home/andrew/angular/barebones-angular/node\_modules/css-loader/dist/index.js:84:33)

 @ ./~/bootstrap/dist/css/bootstrap.css 2:26-86

The only workaround for this problem was to include the CSS file using a conventional <link> element in the main application index.html

&lt;link rel="stylesheet" href="./node\_modules/bootstrap/dist/css/bootstrap.css"&gt;

React testing techniques

The recommended testing tools for React applications are:-

Testing-library has superceded Enzyme as a React component tester. It also has mocking capabilities.

Setting up Jest to test a React component

When creating a pure React app, you would use the following command

npx create-react-app

However in the case of a hybrid AngularJS/React app, this command hasn’t been used to create the application, so we need to set up configuration files and dependencies manually. The following link is a useful tutorial on setting up React with Webpack, and also how to set up Jest/Enzyme: https://www.freecodecamp.org/news/how-to-combine-webpack-4-and-babel-7-to-create-a-fantastic-react-app-845797e036ff/

https://www.freecodecamp.org/news/how-to-set-up-jest-enzyme-like-a-boss-8455a2bc6d56/

First thing is to specifiy the command to start the tests. This is done by adding the following to package.json:

"scripts": {
 "start": "webpack-dev-server --content-base --inline --hot --port 1234",
 **"test": "jest"**
},

There are a few dependencies required

"devDependencies": {
"@babel/plugin-proposal-class-properties": "^7.8.3",
"@babel/preset-env": "^7.9.5",
"@babel/preset-react": "^7.9.4",
"babel-core": "^6.22.1",
"babel-jest": "^25.4.0",
"babel-loader": "^6.2.10",
"babel-preset-es2015": "^6.22.0",
"babel-preset-react": "^6.22.0",
"babel-preset-stage-0": "^6.22.0",
"jest": "^25.4.0",
"jest-transform-stub": "^2.0.0",
"react-test-renderer": "^16.13.1",
"webpack": "^1.14.0",
"webpack-dev-server": "^1.16.2"
},

And because React uses JSX syntax, this needs to be translated into conventional Javascript. There’s a plugin called Babel which does this

"jest": {
 "transform": {
 "^.+\\\\.js?$": "babel-jest"
 }
}

There’s also a configuration file for babel, which is .babelrc in the top level directory of your application. Add the following to this file

{
 "presets": [
 "@babel/preset-env",
 "@babel/preset-react"
 ],
 "plugins": \["@babel/plugin-proposal-class-properties"\]
}

Useful web links

https://www.robinwieruch.de/minimal-react-webpack-babel-setup

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published