A first-hand journey from the developers leading the charge at Hotel Engine.
Speakers:
Michelle Lim, Frontend Engineer
Clayton Weller, Backend Engineer
John Siladie, Vice President of Engineering
From Angular to React:
A JavaScript Journey
Michelle Lim
Clayton Weller
John Siladie
Hotel Engine is a Denver-based,
high-growth travel tech company.
What we do:
Why we are even giving
this presentation?
What did we decide to do?
How are we doing it?
When?
NOW!
Only surviving version of 2008 logo
Current Logo... much better
Small team
Large app
Need to keep pushing out new features
Currently working in a CI/CD framework
Replace one component at a time
Replace an Angular component with React equivalent
<!DOCTYPE html>
<html ng-app="demo.app">
<head>
<meta charset="utf-8">
<title>Trip Bucket List</title>
<link rel="stylesheet" href="./public/app.css">
</head>
<body>
<bucket-list></bucket-list>
<script type="text/javascript" src="./public/vendor.js"></script>
<script type="text/javascript" src="./public/app.js"></script>
</body>
</html>
index.html
(function () {
'use strict';
angular.module('demo.app')
.component('bucketList', { // aka <bucket-list>
template: require('./bucket-list.html'),
controller: BucketListController
});
function BucketListController() {
const $ctrl = this;
$ctrl.trips = require('../../data/trips').default;
$ctrl.trip = {};
$ctrl.addTrip = addTrip;
$ctrl.updateTrip = updateTrip;
function addTrip() {
// does it
}
function updateTrip() {
// does it
}
}
})();
bucket-list.component.js
<div>
<h1>Trip Bucket List</h1>
<div>
<!-- Add Trip form -->
<form>
<input placeholder="City" ng-model="$ctrl.trip.city"/>
<input placeholder="Country" ng-model="$ctrl.trip.country"/>
<button ng-click="$ctrl.addTrip()">Add Trip</button>
</form>
<!-- Trips -->
<div ng-repeat="trip in $ctrl.trips track by trip.id">
<img ng-src="{{trip.image}}" alt="{{trip.city}}"/>
<span>{{trip.city}}, {{trip.country}}</span>
<!-- Trip checkbox -->
<input type="checkbox" ng-change="$ctrl.updateTrip()" ng-model="trip.completed" />
<label for="checked"></label>
</div>
</div>
</div>
bucket-list.html
→ index.html
→ bucket-list.component.js
→ bucket-list.html
BucketList.jsx
BucketList.jsx - break out components?
import React from 'react';
import { AddTripForm, TripsTable } from '../../index';
const BucketList = ({ trips }) => (
<div>
<h1>Trip Bucket List</h1>
<AddTripForm />
<TripsTable trips={trips} />
</div>
);
export default BucketList;
github.com/coatue-oss/react2angular
import { react2angular } from 'react2angular';
import { BucketList } from 'react-components';
export default angular
.module('demo.app')
.component('reactBucketList', react2angular(BucketList, ['trips']));
react-bucket-list.component.js
import { react2angular } from 'react2angular';
import { BucketList } from 'react-components';
export default angular
.module('demo.app')
.component('reactBucketList', react2angular(BucketList, ['trips']));
(function () {
'use strict';
angular.module('demo.app')
.component('reactBucketListWrapper', {
template: `<react-bucket-list trips="$ctrl.trips"></react-bucket-list>`,
controller: BucketListWrapperController
});
function BucketListWrapperController() {
const $ctrl = this;
$ctrl.trips = require('../../data/trips').default;
}
})();
<!DOCTYPE html>
<html ng-app="app">
<head>
<meta charset="utf-8">
<title>Trip Bucket List</title>
<link rel="stylesheet" href="./public/app.css">
</head>
<body>
<!-- <bucket-list></bucket-list> // original Angular component -->
<react-bucket-list-wrapper></react-bucket-list-wrapper>
<script type="text/javascript" src="./public/vendor.js"></script>
<script type="text/javascript" src="./public/app.js"></script>
</body>
</html>
index.html
#DENStartupWeek
State
Bucket-list-wrapper
Connect a React component to the global Redux
store so it can interact with the Angular app
trips: [
{
id: 1,
city: 'Seoul',
country: 'South Korea',
image: 'https://www.webuildvalue.com/static/upload/seo/seoul-megacity-growth-sustainable.jpg',
completed: false
},
{
id: 2,
city: 'Vancouver',
country: 'British Columbia',
image: 'https://www.hellobc.com/content/uploads/2018/03/2-6787-vancouver-skyline.jpg',
completed: true
},
{
id: 3,
city: 'Lisbon',
country: 'Portugal',
image: 'https://www.discoverwalks.com/blog/wp-content/uploads/2018/03/lisbonintwodays-816x538.jpg',
completed: false
},
]
export const addTrip = (state = initialState, { trip }) => ({
...state,
trips: [
...state.trips,
trip
]
});
export const updateTrip = (state = initialState, { trip }) => {
const updatedTrips = state.trips.map(t => {
if (t.id === trip.id) {
return trip;
}
return t;
});
return {
...state,
trips: updatedTrips
};
};
github.com/angular-redux/ng-redux
import { react2angular } from 'react2angular';
import { BucketList } from 'react-components';
export default angular
.module('app')
.component('reactBucketList', react2angular(BucketList, ['store']));
(function () {
'use strict';
angular.module('app')
.component('reactBucketListWrapper', {
template: `<react-bucket-list store="$ctrl.store"></react-bucket-list>`,
controller: BucketListWrapperController
});
function BucketListWrapperController($ngRedux) {
const $ctrl = this;
$ctrl.store = $ngRedux;
}
react-bucket-list.component.js
import React from 'react';
import { AddTripForm, TripList } from '../../index';
import { Provider } from "react-redux";
const BucketList = ({ store }) => (
<Provider store={store}>
<div>
<h1>Trip Bucket List</h1>
<AddTripForm />
<TripList />
</div>
</Provider>
);
export default BucketList;
BucketList.jsx
import React from 'react';
import { connect, useDispatch, useSelector } from "react-redux";
import { Creators as TripActions } from '../../Redux/Actions/Trips';
const TripList = () => {
const trips = useSelector(state => state.Trips.trips);
const dispatch = useDispatch();
const handleCheckbox = trip => {
dispatch(TripActions.updateTrip({
...trip,
completed: !trip.completed
}));
}
return (
<div>
{trips.map(trip =>
<div key={trip.id}>
...
</div>
)}
</div>
);
}
export default connect(null, null)(TripList);
TripsTable.jsx
(shared)
event or query
new data
const updateTrip = () => {
scope.$apply(() => { $ctrl.completed = !$ctrl.completed }); };
const updateTrip = () => {
$scope.$apply(() => { $ctrl.completed = !$ctrl.completed }); };
View our open positions here:
Chat with us.