├── .eslintrc.json ├── .gitignore ├── README.md ├── mock-data.json ├── package.json ├── public ├── favicon.ico └── index.html └── src ├── app.module.js ├── components ├── Comment │ └── index.js └── CommentList │ ├── index.js │ └── presenter.js ├── index.css ├── index.js └── services ├── AngularReactHelper └── index.js ├── AuthorService ├── index.js └── service.js └── CommentService ├── index.js └── service.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "plugins": [ 4 | "react", 5 | "jsx-a11y", 6 | "import" 7 | ], 8 | "rules": { 9 | "indent": ["error", 4], 10 | "arrow-parens": ["off"], 11 | "react/jsx-filename-extension": ["error", { "extensions": [".js", ".jsx"] }], 12 | "react/jsx-indent": ["off"], 13 | "react/jsx-curly-spacing": ["error", "always"], 14 | "react/forbid-prop-types": ["off"] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # testing 7 | coverage 8 | 9 | # production 10 | build 11 | 12 | # misc 13 | .DS_Store 14 | .env 15 | npm-debug.log 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AngularJS 1.x to React migration example 2 | 3 | This is an example app demonstrating a bridge from AngularJS to React. Read more here: [Our journey migrating 100k lines of code from AngularJS to React (Chapter 1)](https://tech.small-improvements.com/2017/01/25/how-to-migrate-an-angularjs-1-app-to-react/) 4 | 5 | 6 | This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app). 7 | 8 | ### Development 9 | Start local dev: 10 | ```bash 11 | $ npm start 12 | ``` 13 | To start the mock API [json-server](https://github.com/typicode/json-server) run: 14 | 15 | ```bash 16 | $ npm run api 17 | ``` 18 | 19 | 20 | -------------------------------------------------------------------------------- /mock-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": [ 3 | { "text": "First comment", "authorId": "a1" }, 4 | { "text": "Second comment", "authorId": "a2" } 5 | ], 6 | "authors": [ 7 | { "id": "a1", "name": "John Doe" }, 8 | { "id": "a2", "name": "Eddart Stark" } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng-react-example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "devDependencies": { 6 | "eslint": "^3.18.0", 7 | "eslint-config-airbnb": "^14.1.0", 8 | "eslint-plugin-import": "^2.2.0", 9 | "eslint-plugin-jsx-a11y": "^4.0.0", 10 | "eslint-plugin-react": "^6.10.2", 11 | "json-server": "^0.9.6", 12 | "react-scripts": "0.8.5" 13 | }, 14 | "dependencies": { 15 | "angular": "^1.6.1", 16 | "angular-ui-router": "^0.3.2", 17 | "lodash": "^4.17.4", 18 | "react": "^15.4.2", 19 | "react-dom": "^15.4.2" 20 | }, 21 | "scripts": { 22 | "start": "react-scripts start", 23 | "build": "react-scripts build", 24 | "test": "react-scripts test --env=jsdom", 25 | "eject": "react-scripts eject", 26 | "api": "json-server --watch mock-data.json --port 3004" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sfroestl/angular-react-migration/fa37570a4d414677e4a2343aa533d8ae9bd11e2c/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 17 | React App 18 | 19 | 20 | Home 21 | 22 | 23 | 24 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/app.module.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | 3 | const setupRoutes = ($stateProvider) => { 4 | $stateProvider 5 | .state('home', { 6 | url: '/', 7 | resolve: { 8 | comments: (CommentService) => CommentService.queryComments(), 9 | authors: (AuthorService) => AuthorService.queryAuthors(), 10 | }, 11 | controllerAs: '$ctrl', 12 | controller: function (comments) { 13 | this.comments = comments; 14 | }, 15 | template: ` 16 |

Hello World!

17 | 18 | `, 19 | }); 20 | }; 21 | 22 | const enableHtml5Mode = ($locationProvider) => { 23 | $locationProvider.html5Mode({ enabled: true }); 24 | }; 25 | 26 | module.exports = angular.module('ngReactExample', [ 27 | require('angular-ui-router'), 28 | require('./services/CommentService').name, 29 | require('./services/AuthorService').name, 30 | require('./components/CommentList').name, 31 | ]) 32 | .config(enableHtml5Mode) 33 | .config(setupRoutes); 34 | 35 | -------------------------------------------------------------------------------- /src/components/Comment/index.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | 3 | const Comment = ({ comment, author }) => ( 4 |
5 | { comment.text } | Author: { author.name } 6 |
7 | ); 8 | 9 | 10 | Comment.propTypes = { 11 | comment: PropTypes.shape({ 12 | text: PropTypes.string, 13 | }).isRequired, 14 | author: PropTypes.shape({ 15 | name: PropTypes.string, 16 | }).isRequired, 17 | }; 18 | 19 | export default Comment; 20 | -------------------------------------------------------------------------------- /src/components/CommentList/index.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | import { reactToAngularComponent } from '../../services/AngularReactHelper'; 3 | import CommentList from './presenter'; 4 | 5 | module.exports = angular.module('ngReactExample.commentList', [ 6 | ]).component('commentList', reactToAngularComponent(CommentList)); 7 | -------------------------------------------------------------------------------- /src/components/CommentList/presenter.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import Comment from '../Comment'; 3 | import { getAngularService } from '../../services/AngularReactHelper'; 4 | 5 | const CommentList = (props) => { 6 | const AuthorService = getAngularService(document, 'AuthorService'); 7 | const toComment = (c, i) => 8 | ; 9 | return ( 10 |
11 | { props.comments.map(toComment) } 12 |
13 | ); 14 | }; 15 | 16 | CommentList.propTypes = { 17 | comments: PropTypes.array.isRequired, 18 | }; 19 | 20 | export default CommentList; 21 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import './index.css'; 2 | require('./app.module'); 3 | 4 | -------------------------------------------------------------------------------- /src/services/AngularReactHelper/index.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | import ReactDOM from 'react-dom'; 3 | import React from 'react'; 4 | import { mapValues } from 'lodash'; 5 | 6 | function render(element, Component, props) { 7 | ReactDOM.render( 8 | , 9 | element, 10 | ); 11 | } 12 | 13 | function toBindings(propTypes) { 14 | return mapValues(propTypes, () => '<'); 15 | } 16 | 17 | function toProps(propTypes, controller) { 18 | return mapValues(propTypes, (val, key) => { 19 | return controller[key]; 20 | }); 21 | } 22 | 23 | export function getAngularService(document, name) { 24 | const injector = angular.element(document.body).injector() || {}; 25 | return injector.get(name); 26 | } 27 | 28 | export function reactToAngularComponent(Component) { 29 | const { propTypes = {} } = Component; 30 | return { 31 | bindings: toBindings(propTypes), 32 | controller: /*@ngInject*/ function controller($scope, $element) { 33 | this.$onChanges = () => render($element[0], Component, toProps(propTypes, this)); 34 | this.$onDestroy = () => ReactDOM.unmountComponentAtNode($element[0]); 35 | }, 36 | }; 37 | } 38 | 39 | -------------------------------------------------------------------------------- /src/services/AuthorService/index.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | import AuthorService from './service'; 3 | 4 | module.exports = angular.module('ngReactExample.AuthorService', [ 5 | ]).service('AuthorService', AuthorService); 6 | -------------------------------------------------------------------------------- /src/services/AuthorService/service.js: -------------------------------------------------------------------------------- 1 | const AUTHOR_URI = 'http://localhost:3004/authors' 2 | 3 | export default class AuthorService { 4 | 5 | static $inject = ['$http']; 6 | constructor($http) { 7 | this.$http = $http; 8 | this.authors = {}; 9 | } 10 | 11 | queryAuthors() { 12 | return this.$http 13 | .get(AUTHOR_URI) 14 | .then((resp) => this.setAuthors(resp.data)); 15 | } 16 | 17 | setAuthors(authors) { 18 | this.authors = authors.reduce((authorMap, author) => { 19 | authorMap[author.id] = author 20 | return authorMap; 21 | }, this.authors); 22 | return authors; 23 | } 24 | 25 | getAuthor(id) { 26 | return this.authors[id]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/services/CommentService/index.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | import CommentService from './service'; 3 | 4 | module.exports = angular.module('ngReactExample.CommentService', [ 5 | ]).service('CommentService', CommentService); 6 | -------------------------------------------------------------------------------- /src/services/CommentService/service.js: -------------------------------------------------------------------------------- 1 | const COMMENTS_URI = 'http://localhost:3004/comments'; 2 | 3 | export default class CommentService { 4 | 5 | static $inject = ['$http']; 6 | constructor($http) { 7 | this.$http = $http; 8 | } 9 | 10 | queryComments() { 11 | return this.$http.get(COMMENTS_URI).then((resp) => { 12 | return this.setComments(resp.data); 13 | }); 14 | } 15 | 16 | getComments() { 17 | return this.comments; 18 | } 19 | 20 | setComments(comments) { 21 | this.comments = comments; 22 | return comments; 23 | } 24 | } 25 | --------------------------------------------------------------------------------