Angular 1 to React Migration
Recently I decided to migrate one of my side projects to React. I had several reasons for that, but the most important is that, obviously, Angular 1 is old, and nobody cares about it anymore. No community, no bugfixes, no improvements. It is basically dead.
So I decided to give React a go. And you know what? I was blown away by how easy it was to rewrite my whole application from Angular 1 to React. It took just a couple of weekends, impressive given that my application is rather complex with several dozen related and interdependent components.
In this article, I’m going to share my impressions and will describe the transition I had to undergo to think in React.
Overview and Comparison
Here’s the TL;DR:
- React is a small view library, but you still need to learn a lot to create a basic application.
- Prepare to write more code, but bonus, it should be more straightforward.
- More JavaScript-ish. Less mental overhead.
- One-way data flow is verbose in practice, but more direct and easy to reason about.
- JSX is not that bad. It’s more lintable, so it’s easier to spot errors.
- You have to use classes for stateful components. But please don’t use inheritance.
- In React, you always want to have less state to manage, so you should prefer function components to class components.
Ecosystem
Usually, people say that React is simple because it’s just a rendering library, which is true (one can easily walk through the entire documentation in several hours), but that doesn’t mean it’s easy to create a new project with it. And that’s because React itself is not sufficient for a complete application. You will need to learn things outside the React library but within the React ecosystem.
As soon as you start making React applications, your first questions will probably be:
- How do I fetch the data?
- How do I describe routes?
- How do I manage state?
- How do I interact with API?
There are of course multiple answers to all those questions. But you need to understand that React won’t answer them. Those are the decisions you’re going to have to make on your own.
Verboseness
After moving from Angular 1 to React, the next thing you’ll notice is that you have to write more code. In my experience, it’s around two times more typing. It’s frustrating, but after some time you get used to it. In general, there’s a tradeoff between verboseness and clarity — React is more verbose, but as an upside, the code is more readable.
In practice, however, I would say, it’s perfectly possible to write readable and maintainable Angular 1 code just as, vice versa, it’s possible to write terrible and convoluted React code. So I guess it boils down to your personal style.
React concepts that increase verboseness are JSX and one-way data flow, but we’ll talk more on those later.
React is more JavaScript-ish
Every time you learn a new framework, there’s an overhead of what you need to learn. Those are things that lie in your brain and make you productive within a particular framework but are useless when you switch to another thing. React is more down-to-earth than anything else I’ve used before. Whatever you do, just write JavaScript, even when you write templates. But more on this in the next part.
JSX
When people encounter JSX, they often complain about violation of separation concern. You write JavaScript code and HTML-like markup in the same file, and that scares people off. In reality, though, it’s not a violation if you put them in one file. It’s a violation when you mix up logic with a view, but that’s totally avoidable.
Constructions like if
and for
within your markup have nothing to do with separation of concerns.
We do the same thing in Angular with ng-if
and ng-each
. But in React, the code is not artificial — that’s just markup + JavaScript. Since it’s a superset of JavaScript, it’s perfectly lintable, so you will know you’re missing a method at the linting time. In the case of Angular, it will silently fail even at runtime (because templates in Angular are just strings).
This one was a game changer for me.
On the negative side of things, it’s good to understand that JSX is not exactly HTML + JavaScript. There’s still stuff that is React-specific; for example, you have to write all the properties in camel case, and you can’t use class
. Instead, you have to write className
.
Oh, and you also can’t use strings for styles. Instead you write something like this: style={{ marginTop: '5px' }}
. All this means you can’t just take a piece of your HTML markup and use it as React template — you still need to adjust.
One-way data flow
Okay, moving forward to one of React’s key selling points: the data flow. In React, it always goes from the parent to children. What if a child component needs to update the state? In that case, it should communicate to its parent via a callback. The parent will update the state, and so the data will pass down to the child again, causing the rerender (the virtual one).
In practice, this does lead to a more understandable stream-lined data flow. As a downside, you again have to tolerate the mental overhead. Imagine you have a tree of components, and something changed at the very bottom of that tree. To propagate the change to the top components, you have to pass a callback all the way down through all the levels of the tree. It is cumbersome, but state managers like Mobx or Redux help to deal with that. However, they can add more overhead.
Classes
So I couldn’t avoid this aspect. I still think that classes are weird out-of-the-place creatures that shouldn’t have made it to JavaScript. I imagine they were introduced to make Java folks feel at home. Underneath, nothing changed — that’s still a good old prototype-based inheritance. class
is just syntactic sugar that attempts to hide the nature of the JavaScript, and it fails to do that.
Class-based programming in JavaScript is complex, hard, and convoluted. As opposed to functional programming, that may be a bit wordy first, but in the end, it scales so much better. Even in React docs they warn against using inheritance with classes.
But well, whatever I say, you can’t change the fact that React API use classes. If you’re using a stateless component, you should use a pure function. Whereas if you have state, you need to use classes instead. And as a React developer, your job is to try to localize the state, which means using pure stateless components (functions) as much as possible and use clauses as little as possible.
To dive deeper into that topic, read this writeup by Dan Abramov.
Real-world Examples
Now on to the fun part. Let’s see several examples of how you might migrate an Angular 1 component to React.
Stateless component
Let’s assume we have a component that displays a single user’s info. In Angular 1, it could look something like this:
const TMPLT = ` <div class="item clearfix"> <div class="ava"> <img ng-src="{{user.avatar}}" /> </div> <div> <div class="name"> <a href="/users/{{user.id}}/">{{ user.name }}</a> </div> <div class="phone"> {{ user.phone }} </div> </div> </div> `; export default function() { return { restrict: 'E', scope: { user: '=' }, template: TMPL } }
Note that nasty user: '='
thing. That’s a two-way data-binding, meaning you can change it in the component and it will change in the parent as well, and vice versa. That’s something you can’t have in React.
Rewriting it in React turns out to be really easy; all you have to do is to create a pure function. That function accepts properties and returns a JSX markup that needs be rendered.
export default function({ user: { name, phone, avatar } }) { return ( <div className="item clearfix"> <div className="ava pull-left"> <img src={avatar} /> </div> <div className="name pull-right"> <div className="name"> <a href={`/users/${user.id}`}>{ name }</a> </div> <div className="phone">{ phone } </div> </div> </div> ) }
Note that:
- We removed the Angular-specific attributes (
ng-src
) and added some React-specific ones (className
). - Everything inside
{}
is just a JavaScript code, not some artificial mini-language. - In this specific example, it actually became less code.
Introducing state
Now let’s see how would you implement an address book component (to display a single user, we’ll reuse a component from the previous section).
Angular 1:
const TMPL = ` <div class="address-book"> <user user="u" ng-for="u in users"></user> </div> ` export default function() { return { restrict: 'E', scope: { }, template: TMPL, link } function link($scope) { fetchUsers().then((users) => { $scope.users = users; }) } }
We’re not interested in how fetchUsers
might be implemented, but in the case of Angular, you’d probably use a $http
service to send AJAX request, since it’s part of the API.
React:
class AddressBook { constructor(props) { super(props) // always invoke super this.state = { users: [] // default state } } componentDidMount() { // gets invoked after component fetchUsers().then((users) => { this.setState({ users }) }) } render() { const { users } = this.state return ( <div class="address-book"> { users.map(u => <user user={u}></user>) } </div> ) } }
Note that:
- We had to utilize classes here. In React, every component that has state needs to be a class. It has a
componentDidMount
method, which is a lifecycle hook, and it’s being invoked after component rendered into the DOM. - Instead of
ng-for
, we’re now using a plain JavaScript to render a list, which is very nice. - Instead of directly setting state, we have to use
setState
method. Otherwise React will never know that the state has changed and it needs to rerender.
Propagating the state
Let’s have some fun now. Suppose that for every contact we have an editable field, say, a phone number. In Angular, we will use ng-model
for that.
const TMPLT = ` <div class="item clearfix"> <div class="ava"> <img ng-src="{{user.avatar}}" /> </div> <div> <div class="name"> <a href="/users/{{user.id}}/">{{ user.name }}</a> </div> <div class="phone"> <input type="text" ng-model="user.phone" /> </div> </div> </div>
Nothing else is changed. Since we have a 2-way binding, when we change the phone, it automatically propagates to the parent array of users, and that’s it. With React though, we need to implement the propagation ourselves.
export default function({ user: { id, name, phone, avatar } }, onChange) { return ( <div className="item clearfix"> <div className="ava pull-left"> <img src={avatar} /> </div> <div className="pull-right"> <div className="name"> <a href={`/users/${id}`}>{ name }</a> </div> <div className="phone"> <input type="text" value={phone} onChange={changed} /> </div> </div> </div> ) function changed({ target: { value } }) { onChange({ id, name, avatar, phone: value }) } }
Note that:
- We need to manually sync the state between
input
and the inner state of the application. - When the change happens, we need to pass the updated user model to the parent, by invoking the callback.
- It’s the job of the parent component to take the updated user object and update its state.
Conclusion
What you have just read is a subjective opinion and more like my personal notes about the experience I had. Yours might be very different, so feel free to share it with me.
And let’s draw a line here:
- React is wordier, but that’s okay.
- One-way data flow requires some overhead but scales much better.
- JSX is a blessing.
- You have to deal with classes, but don’t use inheritance, prefer composition.
- React is not that scary when you actually try to use it.
Published on Web Code Geeks with permission by Yanis Triandaphilov, partner at our WCG program. See the original article here: Angular 1 to React Migration Opinions expressed by Web Code Geeks contributors are their own. |