Angular 2 vs React: The Ultimate Dance Off

Eric Elliott
JavaScript Scene
Published in
13 min readOct 2, 2016

--

Sunset Breaks — Alí Marín (CC BY-NC-ND 2.0)

Most people who follow me know that I personally favor React, but of course I like my decisions to be educated, not based on uninformed bias. Lately, I’ve been exploring Angular 2 in-depth. Here’s how it compares to React in my opinion.

Note: I’ll sometimes abbreviate Angular 2 “ng2” — a common practice in the community.

Let’s Dance

I want to concentrate on the dev experience, so let’s look at how devs feel about using ng2 and React, respectively. About both Angular 2 and React, I asked my followers “Would you use it again?”:

Only 17% said they’d use Angular 2 again.

56% said they’d use React again.

That’s it. Game over. Right? Well, not quite. Most of the respondents had not used Angular 2 yet, so to make this fair, we really need to remove that group from the poll.

When we remove the group that hasn’t used them yet, the scores both look better:

React: 84%

Angular 2: 38%

React clearly wins the customer satisfaction poll. But this isn’t scientific. This is just my followers and the followers of people who retweeted the polls, and I talk primarily about React — but because these are actual users sharing their actual experience, I think it’s safe to say that while the numbers won’t be exactly the same across the general user population, they give us a good idea of what to expect.

Let’s take a closer look at the differences, and why people might feel the way they do.

Performance

So many people think that tech selections should be all about performance. React vs Angular 1 articles tended to center on performance (React won easily due to Angular 1 dirty checking and digest cycles). But Angular 2 has cleaned up its act. It’s fast. In my testing for both React and Angular 2, performance was not a big problem with either. They each have their caveats, but I won’t go into them deeply.

As with all other CS perf issues: if you have to repeat expensive operations many times, it will slow your app down. For React & ng2 alike, that means you should avoid DOM updates when you can, avoid deep state iteration if you can, avoid excessive creating / tearing down objects and components, etc… for the purposes of this article, we’ll assume that we’re comparing a couple supercars, and they’re both speed demons. Your mileage may vary.

That said, I value the performance of the team as highly as app performance. This comparison will concentrate on developer experience and developer team performance issues. Ultimately, your team needs to be able to move fast and make quick changes. We’re going to explore how each of these tools compare on those fronts.

How Angular 1 Transformed the Front End

Angular 1 combined ideas from Backbone, Knockout, and the emerging Web Components spec and brought custom components to the masses via ng1 custom directives. It also brought along a common pattern from OOP: The dependency injection container.

It should be obvious now, but IMO, the most transformational thing that Angular 1 popularized was custom components. Today, all the popular front-end frameworks support custom components.

How React Transformed the Front End

When React was released, MV* and 2-way data binding were taking the front-end by storm. Backbone and Knockout were heavy hitters. Angular 1 was the new kid on the block.

React did not come with any built-in concept of a model, and the team recommended that you use it alongside the Flux architecture. Flux is a UI architecture that prescribes unidirectional data flow and transactional state. I’ve covered the many benefits of both in detail in “10 Tips for Better Redux Architecture”. If you’re not familiar with React, Flux, and Redux, I highly recommend that you read it before continuing with this article.

React was transformational because it singlehandedly made MVC seem like obsolete tech & unleashed unidirectional flow on the masses.

In my opinion, this is the most significant transformation of front-end architecture since HTML & JavaScript.

Fortunately, it was so transformational, it’s no longer unique to React. You can achieve it in ng2, as well.

Stuff in Angular 2 You Won’t Find in React

Of course, both frameworks support building custom components. Angular 2 has a few extra things under the hood, but essentially, they both exist to build UIs on the front-end of the application stack. Angular 2 just has more prescriptions and “batteries included” philosophy.

Let’s look at some of Angular 2’s prescriptions.

TypeScript

I have a love/hate relationship with TypeScript. One of my pet peeves is the popular idea that TypeScript is going to save your app from all sorts of bugs. I have bad news for you: It won’t.

Read “The Shocking Secret About Static Types”. Bug reduction is not what static types are good at.

Type correctness does not guarantee program correctness.

Result? It’s true that static types can eliminate one class of bugs, but at the cost of increased code complexity, and the over-all bug-reduction impact on the project is small.

Static types are great primarily for developer tooling. It’s very nice to get in-line documentation about things like function call signatures as you code. But you don’t always need inline annotations everywhere to accomplish that.

I love the idea of specifying type annotations when I need to for clarity, developer documentation, and enhanced developer tooling, but for the most part, I have a strong preference for type inference. Inline type declarations have a tendency to clutter code and make it harder to read.

The good news is that TypeScript is pretty good at type inference, and if you want to, it will let you rely on inference most of the time. Its inference capabilities seem better than Tern, and the developer tools for Atom and Microsoft Code are much better than comparable Tern plugins. In fact, I’d go so far as to say that TypeScript provides the best intellisense developer experience available in the JavaScript world today. You might fall in love with it.

TypeScript provides the best intellisense developer experience available in the JavaScript world today.

If only I could use TypeScript strictly for its type inference capabilities and skip raising errors for undeclared modules and ambiguous parameters, I’d use it on regular JavaScript files all the time. Easy choice.

If only type annotations always lived in separate files (like the definitely typed `d.ts` files) by convention with zero config, I’d use more annotations, too, and I’d like TypeScript a lot more. You could more easily integrate TypeScript with any workflow, because it would literally be “just JavaScript”. You could compile with any compiler that supports standard JavaScript.

In reality, TypeScript adds a whole lot of overhead to large projects, including configuration overhead (keeping track of all your library typings to prevent module import errors), syntax overhead (typings are typically declared inline), lots of `any` escape hatches, etc…

To clarify `any` escape hatches, TypeScript supports generics using type constructor style syntax, but it’s not always clear or easy to get things working using functional programming style constructs, observables, switching higher-order functions, etc…

Instead of figuring out the right way to type things, developers commonly resort to using `any`. Essentially the same thing as giving up on type checking and just saying, “any type will work here”, even when that isn’t strictly true.

All of the overhead and escape hatches add more surface area for bugs to hide in. It’s common to see TypeScript projects where there are TypeScript errors that just get ignored by the teams. Sometimes hundreds or thousands of errors.

As HTML proved, if you make something that works when there are errors, errors will proliferate.

Any time there are errors that get ignored, errors cease to be useful and just turn into noise. TypeScript proponents are probably already all up in the comments telling me how these teams are doing it wrong, and they really need to fix the errors, and they’re right, but.

It’s not always obvious how to fix errors quickly, and in the meantime, project managers are hounding developers to get their features out. Since TypeScript can compile working builds even when there are errors, it’s possible, perhaps likely, that large projects will get into that situation whether the developers like and agree with it or not. If you ask individual contributors on that project if they would ever have let the project get into that state if it had been up to them, probably 100% would say “no”.

But that’s not how large projects with large teams work.

“We know this sucks, but we’ll fix it later.” — Every real dev team that ever existed.

It’s possible that TSLint and better documentation can fix this problem. That remains to be seen.

So, would I use TypeScript on my next project? While I love the developer tooling, I’m finding the overhead to be more trouble than its worth.

The more I use TypeScript, the more I think it slows me down more than it speeds me up. And if it’s not speeding me up, and it doesn’t reduce bug density much, why the ___ am I using it?

All that said, you might still fall in love with TypeScript.

It may slow you down, (remember, I have no proof of that — just a nagging feeling) but you might feel like it makes you more productive. Sometimes feelings and liking things counts a lot.

Developer happiness is important. You may find that when you’re not dealing with TypeScript’s limitations:

Using TypeScript just makes you happy.

There’s something satisfying about building a typed function interface and knowing that the next dev who comes along will know how to call the function without reading docs or looking at the source code.

Just go into it knowing that it’s going to add a lot of overhead to your project, and knowing that it’s not going to help you reduce bugs much.

What is it going to do? Dev tool magic.

I know I just pissed off a whole lot of TypeScript fans. I’m not saying “TypeScript sucks, don’t use it.” I am saying, “TypeScript is cool and it has great dev tools, but it also has big trade-offs that developers need to be aware of, and it may not provide all of the expected benefits.”

Dependency Injection

First, I love dependency injection. I use it all the time. But Angular 2 beats you over the head with dependency injection. Reading the docs and unit testing recipes, you’ll find all sorts of stuff about providers and mocking dependencies, and unit testing with `beforeEachProviders()`, and it’s going to take some time to wrap your head around it all. There’s a lot of magic going on under the hood to pull off ng2 injectables, and misunderstandings about how it all works and how you’re supposed to use providers can be a real sticking point for beginners.

There’s an easier way. Instead of importing your dependencies and relying on a framework to automagically stuff them into your class constructor signature, just write a function that takes your dependency. It’s really easy & it works like this:

Dependency Injection (DI) proponents will say that I’m oversimplifying (I agree) and that I’m not addressing instantiation (I agree). I’m saying it doesn’t matter.

In practice, what you end up with in Angular 2 is that instead of mocking just a handful of things that actually need mocking (network access, disk access, timers), you end up mocking a whole lot of stuff that should be black-boxed implementation details.

I’ve said this many times before but I’m going to keep saying it until it’s common knowledge:

Mocking is a code smell.

Of course mocking is sometimes required, but if your app is littered with `*.mock.ts` files, you’re doing something wrong. Your app is too tightly coupled (ironically, the idea of DI is to make your app less coupled), or you’re trying to mock a whole lot of things that don’t need mocking.

You don’t need to inject and mock the whole world.

Jasmine

Read “Why I Use Tape Instead of Mocha and So Should You”. Most of it applies to Jasmine.

Jasmine provides an API that poorly attempts to read like English sentences, and provides a bunch of assertion bells and whistles. The result is there are millions of ways to write tests and assertions, meaning that you may end up needing to carefully read each one to understand what it’s doing.

You’re better off restricting yourself to a handful of core assertions. Mostly equality checks. On a large team? Good luck with that.

Jasmine also encourages a lot of dependency on `beforeEach()` and `afterEach()`, which can accidentally encourage shared state: a very common problem in unit tests which can cause random test failures (Jasmine tests correctly get executed in random order to discourage the use of shared state and dependencies on test order).

In addition to those critiques, Jasmine has one more flaw that drives me 3@+ $#|+ crazy. When you compare two objects for equality, it serializes and prints both objects on one big line. In other words, the default reporter output is basically #$%!@#$ worthless.

[Editor’s note: We had to trim about 8 paragraphs that was nothing but !@#$ bleeps from this part of the article. We’d share the full transcript, but it might make children cry and puppies hide under couches.]

Chai prints nice diffs. Tape prints this:

not ok 1 should be the same---operator: equalexpected: |-{ x: 1, y: { z: 3 } }actual: |-{ x: 1, y: { z: 2 } }...

For small objects I can easily see the differences. There are plug-in reporters that make it even better:

Developers need clear, obvious reporting for differences when tests fail, and Jasmine falls flat on its face in that department. If you know how to hook up a good in-browser reporter to fix this problem, please comment.

If you’re waiting for it to be fixed in core, don’t hold your breath. An open issue for this problem has been hanging around since 2014.

Other Things

There are also a bunch of Angular things. Forms & validators, etc… but of course all of those things are available to React users if they want to use them. The big difference is React users won’t always find the best solutions in the obvious places (like React documentation). Instead, React users rely more on community best practices, blog articles, and other learning resources to identify the best choices.

All the extra “other things” bloat the Angular 2 bundle. Angular 2 + RxJS is pretty huge. Almost 800k minified. That’s about 4 times larger than React + Redux, and it doesn’t leave much breathing room for your app code if you want your app to load and render quickly.

This is the only big, noticeable performance difference that I think really matters, and it matters quite a bit. Improving load times will radically improve your app’s KPIs across the board. Shaving milliseconds off of load times can move needles on important business metrics.

4 times larger (nearly a megabyte!) really is a big deal. If you don’t need it, don’t load it.

EDIT: As several commenters have pointed out, ng2 has the ability to do Ahead Of Time compiling (AOT) and tree-shaking. With a correctly configured production build, you can reduce the bundle size dramatically using only the features actually used in your app, which give the final bundle a much smaller footprint. I strongly recommend that you configure your production bundle correctly. It will have a potentially dramatic impact on bounce rates, conversion rates, customer satisfaction, and churn.

Stuff in React You Won’t Find in Angular 2

Angular 2, much like Angular 1, relies on HTML templates.

The good thing about ng2 templates is that they’re basically an enhanced form of standard HTML.

The bad thing about ng2 templates is that you have to learn a bit of Angular DSL. Stuff like `*ngIf` and `*ngFor`.

The ugly thing about ng2 templates is that when you make a mistake, they fail at runtime. Sometimes silently. Debugging Angular 2 templates can be a pain.

Edit: I’m told this has been fixed in the most recent versions of Angular 2 when you use AOT. Template errors will be reported at compile time. I have not had the opportunity to test the fix, yet.

React doesn’t have templates. Instead, it has JSX, which compiles to JavaScript at compile time.

The great thing about JSX is that when you make a mistake, it’s likely to be caught at design time by a linter, or compile time, instead of waiting for runtime. Babel, ESLint, and lots of editor tools have built-in support for JSX.

Another great thing about JSX is that you can target more than just HTML with it. You can target custom markups, you can target the canvas, you can even target native mobile UI.

The bad thing about JSX is that it doesn’t exactly line up 1:1 with HTML tags. It’s a Frankenstein hybrid of JavaScript APIs and markup. That means you have to learn its quirks, such as using `className` instead of `class` — but since you can pretty freely mix plain old JavaScript with JSX for loops, conditionals, etc… I think it beats ng2 for learnability.

What About State?

Both Angular 2 and React can use whatever data management layer you like. If you don’t mind opting out of 2-way data binding (which, IMO is a bad thing anyway), you can bring your own state management to ng2. I recommend ngrx/store, which is basically Redux + observables.

For React, try Redux (for most large apps) or MobX (for less complex apps that don’t need transactional state).

For a lot more detail on modern state management for JavaScript apps, check out “10 Tips for Better Redux Architecture”.

What Should You Choose?

After using both heavily for a while (React much longer, of course), it seems obvious to me that Angular 2 requires a lot more tech buy in and a lot more boilerplate in the apps.

If you love the tech choices and you can live with the additional boilerplate overhead, pick Angular 2.

If you’re unhappy with some of the Angular 2 tech choices, and you want something slimmer and less prescriptive, pick React.

Of course, there are other options (some of them very good), but there’s value in a large ecosystem and developer community, and React & Angular dominate usage. There is not even a close third. The only library more popular than these two is jQuery (yes, still).

Keep dancing.

Start your free lesson on EricElliottJS.com

Eric Elliott is a tech product and platform advisor, author of “Composing Software”, cofounder of EricElliottJS.com and DevAnywhere.io, and dev team mentor. He has contributed to software experiences for Adobe Systems, Zumba Fitness, The Wall Street Journal, ESPN, BBC, and top recording artists including Usher, Frank Ocean, Metallica, and many more.

He enjoys a remote lifestyle with the most beautiful woman in the world.

--

--

JavaScript Scene
JavaScript Scene

Published in JavaScript Scene

JavaScript, software leadership, software development, and related technologies.

Eric Elliott
Eric Elliott