├── .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 |
--------------------------------------------------------------------------------