Pro Yearly is on sale from $80 to $50! »

Migrating from Angular to React: Manc React

Migrating from Angular to React: Manc React


Jack Franklin

May 02, 2017


  1. From Angular to React by @Jack_Franklin

  2. From X to Y My experience migrating complex software (Regardless

    of the tech choices).
  3. None
  4. None
  5. None
  6. None
  7. None
  8. The Store

  9. — Started 2 years ago, built on Angular 1 —

    Primarily used within iframes — 2 main developers no longer with the company — Heavy on features — Tickets being sold constantly — Used by our flagship clients
  10. The store works well.

  11. But changing it is very tricky.

  12. — Unclear which features are actually needed — Many different

    use cases — Regular general admission tickets — Reserved seating tickets — Queued tickets for big sales — VIP tickets ("meet and greet") — Merchandise addons (Ticket + album = 10% discount) — We have a lot of stores in the wild
  13. We want to be in a position to iterate quickly

    on this product, but with the current codebase we can't.
  14. What do we do?

  15. 1. Start completely from scratch 2. Stick with Angular 1,

    and rewrite complex parts. 3. Migrate from Angular 1 to 2/3/4 4. Migrate bit by bit to something else
  16. Start completely from scratch ! — We can't afford to

    not work on bugs, improvements on the existing product — The Angular app began as a big rewrite - business wouldn't like it to happen again
  17. Stick with Angular 1 ! — Angular 1 is reaching

    its end - focus is now on Angular 2 and beyond. — Not much developer expertise in Angular across Songkick
  18. Migrate from Angular 1 to 2 and beyond ! —

    Not much developer expertise in Angular across Songkick — Not many Angular evangelists at Songkick
  19. Migrate bit by bit to something else ! — Allows

    us to work on bugs in existing code whilst migrating others — Allows us to build new features whilst migrating
  20. Migrate bit by bit to something else ! — We

    can experiment by moving piece by piece and try different approaches — Current stores continue to function correctly
  21. Migrate bit by bit to something else ! — We

    can move quickly and release small parts at once - avoiding risky big bang releases — Small downside: migration specific code - plumbing
  22. What to migrate to?

  23. None
  24. Why React? ! I and my team are all familiar

    with React ! Another, newer, product at Songkick was built with React ! React leaves you with less framework specific code
  25. Note: I am not saying that Angular is a bad

    choice, or that you shouldn't use it. Just for us, it wasn't the best fit.
  26. So, we've decided to migrate, and what to migrate to.

  27. Now for the hard part — 40,000 lines of JavaScript

    — Most of those lines are tied directly to Angular — Many dependencies outdated due to lack of maintanance over past 6 months — A store that cannot be broken at any point (we sell tickets pretty much continuously)
  28. The Theory Components (or directives in Angular)

  29. None
  30. None
  31. None
  32. The Plan

  33. None
  34. None
  35. None
  36. The theory is easy!

  37. But more complicated in practice...

  38. Angular and dependency injection

  39. Angular 1 was built in a time where we had

    no module system in JavaScript, so it provided one.
  40. // define your Foo object const Foo = {...} angular.service('Foo',

    Foo) // in another Angular thing function SomeController(Foo) { ... } Angular will inject the Foo dependency into SomeController at runtime.
  41. In ES2015 we have a module system (which Babel transpiles

    for us) const Foo = {...} export default Foo // in another file import Foo from './foo'
  42. ES2015 modules are brilliant And have loads of advantages over

    dependency injection that this migration gives you for free.
  43. The step by step process of migrating an Angular directive

    to a React component
  44. 1. Find all dependencies that the directive (or a dependency

    of the directive) injects.
  45. 2. Migrate all of those one by one to React

  46. But wait! You need to migrate localStorage-service.js away from Angular.

    angular.service('LocalStorage', function() { ... }) But this service is used in ~15 places in the app. And we want to release the migration in tiny chunks.
  47. So, we can first pull the logic into a standalone

    thing: const localStorage = { ... }
  48. And then export it so any non Angular code can

    just import it: const localStorage = { ... } export default localStorage
  49. And then leave the small Angular wrapper: const localStorage =

    { ... } export default localStorage angular.service('LocalStorage', function() { return localStorage })
  50. None
  51. ! Existing Angular code can still use the local storage

    code without being aware it's changed ! New React components can import the module without any of the Angular boilerplate " We had to write a small amount of migration specific code
  52. Migration specific code Code that you know will be deleted

    when the migration is finished. A neccessary part of the migration but you should keep it to a minimum. You should also group it so it's easily found and removed later.
  53. So, we've migrated all dependencies away from Angular

  54. Now we can migrate listingsButton- directive.js to React

  55. import React, { Component } from 'react' class ListingsButton extends

    Component { // code left out to save space :) render() { return ( <a href="" className="">Buy now!</a> ) } }
  56. But how do we embed this within Angular? Angular directives

    are used as if they were custom HTML elements: <li> <h2>Rihanna at The O2</h2> <listings-button url="...">Buy now</listings-button> </li>
  57. None
  58. ngReact lets you embed React components within Angular applications It

    is the piece of code that enables our entire migration strategy to work. Remember: you can render many React apps on a page in any place you want.
  59. Aside: React is really malleable

  60. With ngReact <li> <h2>Rihanna at The O2</h2> <react-component name="ListingsButton" props="..."

    /> </li>
  61. We can continue this strategy, migrating the other components to

    React: <li> <react-component name="ListingsTitle" props="..." /> <react-component name="ListingsButton" props="..." /> </li>
  62. None
  63. And now we can migrate this into one big React

    component! import React, { Component } from 'react' import ListingsTitle from './listings-title' import ListingsButton from './listings-button' class ListingsItem extends Component { render() { return ( <li> <ListingsTitle ... /> <ListingsButton ... /> </li> ) } }
  64. And update our template <react-component name="ListingsItem" props="..." />

  65. None
  66. application/

  67. <br />

  68. Thanks for coming back!

  69. Maintaining functionality — At no point can the store be

    broken. — Newly migrated code must have exactly the same functionality — Put simply: users must not notice any difference
  70. Unit Tests — Existing Angular tests can be migrated as

    the services move away from Angular — Used as an opportunity to add test coverage
  71. React components are very testable import React from 'react' import

    ListingsDate from './listings-date' import { mount } from 'enzyme' describe('ListingsDateComponent', () => { describe('an event with just a start date', function() { const event = { event_date: { date: '2016-01-01' } } const wrapper = mount(React.createElement(ListingsDate, { event })) it('formats the month correctly', function() { expect(wrapper.find('.date-block__seg--month').text()).toEqual('Jan') }) it('formats the day correctly', function() { expect(wrapper.find('.date-block__seg--day').text()).toEqual('1') }) it('formats the year correctly', function() { expect(wrapper.find('.date-block__seg--year').text()).toEqual('2016') }) }) })

  73. Unit tests are good because they provide quick feedback and

    can test at a very granular level.
  74. Unit tests (in our case) are bad because when you

    migrate code, you migrate tests Therefore, they are not technically proof that everything works as before.
  75. Acceptance tests

  76. None
  77. Automated tests that are run using Protractor & Selenium —

    ! They test the entire system, with no faked API calls or fake data — ! This means they are entirely independent of the migration — " They can flake sometimes and are slow to run
  78. Because they are slow to run, we keep acceptance tests

    to our core user journeys
  79. They run on every single build, and they have to

    pass to enable us to deploy
  80. The pipeline

  81. None
  82. Making sure deploys have succeeded

  83. Track user activity to ensure deploys are successful

  84. None
  85. !

  86. Unfortunately, fires will happen during this process.

  87. To date we've had 5 fires caused by migration: —

    Two were down to bad data in our test / dev environment not matching "real life"
  88. To date we've had 5 fires caused by migration: —

    Two were particular journeys not being covered by any unit or acceptance tests
  89. To date we've had 5 fires caused by migration: —

    Another was down to timezones ! — 3 people in Japan bought tickets for an event on the wrong day because of me...
  90. You have to communicate how and why a fire happened,

    but how you'll prevent it next time Because you can never ever release bug free so!ware.
  91. Momentum & Prioritisation

  92. One of the core goals of this migration was to

    make the code easier to work on, add features to and fix bugs with more confidence.
  93. Pick work based on bugs and churn rate, not code

    quality Churn rates: the amount of times a file is changed
  94. Code quality Churn rate Bug rate Priority Bad Low Low

    Low Good Low Low Lowest Bad Medium High High Good Low High High Bad High High Highest
  95. A migrated codebase is not a perfect code base

  96. 1. Migrate from X to Y 2. Refactor or redesign

    Y based on new learnings
  97. The next time you touch some code, you'll know more

    about it than before. So why make it perfect now?
  98. Keeping people happy and productive We knew this migration was

    going to be at least 6 months, more likely closer to a year.
  99. And in any large software migration...

  100. There will be good times...

  101. And bad ones...

  102. But, remember, it can always be worse...

  103. But, remember, it can always be worse...

  104. None
  105. Anyway; where was I?!

  106. Momentum & Prioritisation

  107. Mix larger, multi-week work with short, 1-2 day work, to

    keep momentum.
  108. <1 day's worth of work

  109. 1 week's worth of work

  110. Break down large work into small PRs

  111. Release early, release often

  112. Release early, release often ! Encourages small pull requests and

    units of work
  113. Release early, release often ! Keeps momentum up in the

  114. Release early, release often ! Easier to react 1 to

    a bug if release causes it 1 pun very much intended
  115. The scout's rule Always leave things better than when you

    found them.
  116. Mix visual work + "under the hood work"

  117. Have some form of tracking or metrics, however rough it

  118. None
  119. None
  120. None
  121. Tooling Because you can't do a JavaScript talk without talking

    about tools. #fatigue
  122. Migrations are a good excuse to reconsider tooling And also

    provide a break from working on the actual app codebase.
  123. From Karma to Jest Our tests were running on Karma,

    but we wanted to try Jest from Facebook. We had (at the time) ~600 tests across ~130 files. So we started migrating them!
  124. None
  125. From Browserify to Webpack The store was built using a

    combination of Makefiles, misc command line tools and Browserify. This was hard for us to follow and was very brittle. We moved to Webpack which lead to reduce build times, smaller bundles and got us more inline with the rest of the community.
  126. By providing a mix of migration tasks (big, small, visual,

    "under the hood", tooling), we're able to keep work fun and interesting
  127. Communicate to the business Specifically: Why?

  128. Take 1 "Well, Angular 1 is reaching end of life

    and React offers a much better component model that fits our ideas of how to build so#ware" "React's lifecycle methods and small API is easier for developers to learn" "React's state model is less magical; its unidirectional data flow really simplifies code and makes it easier to reason about"
  129. Take 2 "Right now when you ask us for a

    new feature, or bug fix, it's hard and takes a long time to fix and have confidence that we've not inadvertently broken anything else" "This migration will enable us to have a leaner, stable codebase which we will have more confidence in and be able to build features and fix bugs more quickly"
  130. "So nothing will change in the next year on the

  131. Long term user advantages

  132. xf buddies

  133. How's it going so far? We estimate to be ~51%

    done. Most of the core visual journey is done. All forms that users interact with are now in React. We've had very few bug reports on any of our new React code, compared to the old Angular code.
  134. We've done some stuff well, and I expect to learn

    we've done some stuff badly.
  135. Will you ever be done? Our natural "finish" point is

    when we've removed Angular from our codebase. But when that happens we'll keep refactoring & revisiting old code as we fix bugs and introduce new features.
  136. Takeaways

  137. 1. Don't migrate for the sake of it "Framework X

    is now out of date" is not a valid reason!
  138. 2. Plan, plan and plan again

  139. 3. Cross business communication is more important than you realise

    at first
  140. 4. Prioritise based on pain points in your current application

  141. 5. Mix up tasks based on difficulty + visual/tooling/etc to

    keep it interesting
  142. 6. Have some rough metrics that you can use to

    track progress internally
  143. 7. Don't be surprised if you end up rewriting some

    migrated code
  144. 8. Don't appoint David Moyes.

  145. Thank you! — — Questions / want a job?!