This is *not* a pro/con feature list of the two frameworks.

Comparing features is an interesting topic, but as a primary deciding factor when choosing between the two, I don’t think it’s really that valuable. Both are mature frameworks with fine performance, fine features, fine libraries, fine communities, fine i18n, fine routing, fine documentation, fine SEO (it’s 2017, crawlers know about your SPA).

Instead, I aim to document some of the differences in day-to-day experience use which do impact resultant productivity and quality which fall under the concern of “developer ergonomics”.

In this post, I highlight a few frustrations I’ve experienced on the topic of ergonomics:

Components and Refactoring

Testing

Framework-Specific Constructs

To exemplify these topics, I have implemented the same small application in both frameworks, using their respective default seed apps from each project’s official cli ( @angular/cli & create-react-app ). For a fair comparison, I have done my best to reflect what I estimate as each framework's best practices or "official" framework semantics rather than my personal taste. The application can be found here: https://github.com/FLGMwt/ergonomics-ng2-vs-react.

The application retrieves a list of secret agents from a service and displays them. A user can select an agent by clicking the button associated with the agent. Doing so will highlight that agent row and change the application header to display which agent was selected.

Of note, the external service example is synchronous for simplicity. For the purposes of this example, it’s enough to say that stateful RxJS services in Angular 2+ and Redux in React each would add a bump of complexity to their respective app.

Refactoring

The flagship pattern that makes both React and Angular 2+ attractive is “Component-Based Architecture”. In these front-end frameworks, the component is the building block of development. Similar to classes in object-oriented programming, components aim to represent isolated functionality to be composed together to build an application.

Given the following application, an observer will notice two distinct concerns:

An application header (title, hero, navigation) A list of some entity (users, books, search results, etc.)

https://gist.github.com/5d82c0c6c16845ff25aa028c83a8df00

Component-based architecture guides us to separate these two semantic concerns into two code concerns. React and Angular 2+ (as well as Web Components, Polymer, and others) represent this separation in similar ways: creating new custom “DOM” elements, represented in the familar XML structure. A resulting application may look something like:

https://gist.github.com/4d8ebb9fdda8a95d8513ce3574f6729f

With these custom elements implemented elsewhere:

https://gist.github.com/0c10de624820044cd3e80de385c8a459

https://gist.github.com/7776e4e13a79630b697611da4ceaac28

This separation allows us to make changes to one section of the component, such as the header, without full consideration of the entity list in respects such as modification and testing.

These benefits in mind, creating and extracting components should be as frictionless as possible to encourage isolation and reap the benefits of the Single Responsibility Principle.

The Sample App

Here, I’ll introduce the Agent sample application. Both applications are implemented as a single component (for now) and I’ll skip over the bootstrapping code.

On the Angular front, the area of interest is here: with a focus on the agent-selector.component.* files, in particular, agent-selector.component.ts. Angular supports using string literals for component templates and css, as well as inline styles, but in accordance with offical Angular Best Practices, these have been implemented in separate files.

In the React arena, the component is implemented in agent-selector.js with a corresponding test file here. In the React ecosystem, it is more common to use inline styles, especially for simple rules.

Both implementions provide a AgentService with a single method returning a static list of strings representing agents. On the Angular 2+ side, this is injected whereas React's use is imported directly.

A Refactor In Action

An attentive reader may notice AgentSelector 's similarity to the example above wherin it is acting as a "God" component, handling the responsibilities of both a header and an entity list. While this example is trivial, a maturation of this application would likely lead to both concerns growing in scope; the header could take on a hero image, navigation, a carosel, and more, while the entity list could add pagination, search, and a detailed display for each entity.

Looking forward, let’s imagine our first step to growing this app is separating out the AgentList from the AgentSelector "God" component.

Keeping in mind that the AgentSelector 's title displays the selected agent and therefore manages the local state of the application, the AgentList needs the following data: the list of agents, the selected agent, and some mechanism for communicating to the parent when an agent is selected.

Let’s skip the tests for now and start with the Angular 2+ side.

Angular 2+

In line with our pattern of separate files for a component’s html , css , and ts files, we know we'll at least need a new one of each of those for our AgentListComponent .

This is pretty straightforward for the css, since we’re just dropping the agent styles from our main component css and putting them in the new component css.

Our new template file is a similar story where we’re pulling out the ul and putting it in the new template file. However, the delta shows different semantics around the agent select. This is due to Angular 2+ change detection requiring child-parent communication to use the @Output annotation and EventEmitter s. Since we can no longer use a component-local callback, the click event now triggers an emit on this output handler. Additionally, our original template file needs to reference our new component and pass it input bindings for agents and selectedAgent as well as a component-local handler for the child's event emitter output property.

On the component side, we need a new `agent-list.component.ts` which sets up the selector, points to the new template and styles, as well as declares the aforementioned input properties and output EventEmitter . This component is said to be "pure" since it holds no state, has no dependencies, and relies only on its input bindings and event bindings.

The last step is to register this component with the AppModule. This allows the Angular 2+ template interpolation to know what agent-list means when it sees in in the parent component template.

React

On the React side, since our application logic currently completely lives in a single component file, our new list component likely will live in its own file as well.

In the diff for the parent component, we’ll see a familiar replacement of the ul with an AgentList component. For the agent select handler, we pass a direct reference to the parent's click handler. In React-land, callbacks are just data and all parameters to components are passed the same way: as propName={value} , similar to html attributes with curly braces for javascript interpolation. Therefore, the semantics for input data, callbacks, and even DOM input are the same.

Just as it was in the Angular 2+ implementation, our AgentList is a pure component. In React, pure components can be stated simply as a function which takes in props (data) and returns markup. We utilize this in the AgentList component. The only markup that changed was a semantic rename of the agent select callback.

Of note, there is no React equivalent of a module manifest. Since React uses object references for its markup, there is no ambiguity about where custom components come from or what defines them.

Looking Back

From a very naive perspective, the Angular 2+ extraction required modifications in 4 files, and resulted in 3 new files, with the React extraction requiring only 1 file modification and 1 new file. Supposing we appease those uncomfortable with styled components/inline styles and put our React styles in separate style files, this would still only be a ~2, +2 diff. I know this might seem to be a cheap critique, but there is non-trivial cognitive burden when working with changes across multiple files at a time.

Testing

Testing! Bet you forgot about that one, eh? When evaluating technologies, testing should absolutely be at the forefront of the discussion (er, or at least the secondfront).

Testing in Angular 2+ (Before Refactor)

At the core of testing in Angular 2+ is the TestBed . TestBed is a utility which helps set up an in-memory application to render your app in, complete with template rendering and dependency injection.

The configureTestingModule for the TestBed seen here (before the refactor) looks similar to a Module definition. First, we provide the AgentSelectorComponent , our component under test. Then, since AgentSelectorComponent depends on the AgentService (and we like our unit tests to be unit tetsts :D), we provide the TestBed module a stub implementation to inject (implemented earlier in the file).

Once the testing module is configured, we can create an instance of a testing fixture based on the AgentSelectorComponent component. This fixture is a "handle" of sorts for the testing environment based on that component. Off of that, we can grab things like the debugElement which represents the DOM element created by the component.

Finally in our beforeEach , we grab a reference to the instance of the AgentService off of the debugElement 's injector. You'll notice that in order to grab the service to later stub out during tests, we must grab the instance after the component has been instantiated and mounted. According to the Angular 2+ docs, this is because components receive cloned versions of the object provided by the DI system. Individual tests can spyOn methods of services to verify calls and fake return values.

In each of our tests, we call fixture.detectChanges() to cause the inital render of the component, as well as after each modification to the component's props to propagate changes to the component and assert on behavior.

The simplest texting example can be found here:

https://gist.github.com/1ec3c240da87d57f634db22426e78627

Tests against the DOM component are familiar DOM manipulation operations one might see in vanilla JS. The items in the DOM are asserted upon using jasmine 's expect and associated matchers.

Finally, the test setup is wrapped with the more-or-less “magic” async utility from Angular 2+'s testing library. The call to compileComponents() is actually asynchronous, but the helper async() Angular 2+'s zone.js library creates a sort of execution context to abtract this asynchronicity away from the test author. This is also necessary to use when testing asynchronous service calls (docs for example) but doesn't cover all asynchronous things. There's also fakeAsync which does something different.

Testing in React (Before Refactor)

The clear winner of React testing libraries is AirBnB’s Enzyme (along with its good friends, sinon for stubbing and chai for assertions). It leverages React’s test renderer and provides an API for doing DOM-like queries and assertions.

Since our component under test relies on agent-service , we use a beforeEach to setup a stub for that service and have it return an empty array as a default setup (tests can override this). afterEach restores the original functionality so that tests don't affect each other.

Each test then uses enzyme 's shallow render function which we call with our component under test. The return value of shallow is a queryable representation of the component, not unlike Angular 2+'s nativeElement or a jQuery DOM node.

The simplest testing case can be seen here:

https://gist.github.com/f017982ae1cbcbed6eb15ba631f6866c

In contrast to the Angular tests, it’s common for most React Enzyme tests to render the component under test in the test body. This allows a test to instantiate the component with props such as shallow(<Header title='My Test Title' />) . An example for the secret agent app can be seen here. This follows the React component philosophy which can be pharaphrased as "a component is a pure function of its props (input) and its state". In essense, components are treated as functions and Enzyme tests allow test authors to write component tests as if calling that function with different arguments (props).

Before the AgentList refactor, the React and Angular 2+ tests of the AgentSelector are strinkingly similar: render the component, do some DOM querying and assert on the results. Some telling distinctions arise when multiple components are involved, however. This time, I'll start with React.

Testing in React (After Refactor)

Since the new AgentList component doesn't have any service dependencies, it doesn't require the use of sinon.stub . It does, however, assume it will receive at least a list of agents passed in as props. Thankfully, this can be achieved during the test render by providing props to the component using the identical mechanism used for writing React applications, as stated above. Example

https://gist.github.com/a4d865ea041ff72459207e57d350cfc9

The story gets interesting when looking at the updated parent component, AgentSelector . Now that AgentSelector is itself rendering an AgentList , what should we expect?

Thinking about this in non-FE framework terms, this might be akin to our AvatarService being extracted from a ProfileService . If I'm writing a unit test for the new ProfileService with a dependency on the new AvatarService , I'd likely stub out AvatarService with a stub/mocking framework. My updated tests would assert that I'm calling the stubbed AvatarService with the correct parameters and that faked responses from AvatarService have the correct effect on ProfileService . Here's that Single Responsibility Principle again.

The component wrapper that Enzyme’s shallow returns has a debug() method that returns a readable html-style string.

https://gist.github.com/716975da65d2ef6235e4efbfa6a9cfd0

Here we’ll observe that while the beginning of the markup looks like proper html output, the second part didn’t actually invoke the AgentList to render the resultant html. I'll infer from my object-oriented example above that this is a Good Thing™. Unless the API exposed by AgentList , changes, why should AgentSelector care about its implementation/effect? That's what AgentList 's tests are for. This is referred to as "shallow rendering", hence the shallow function name.

The reader will notice that AgentList 's arguments in this example are known. In fact, the tests in the example app asserts on these.

There are Enzyme APIs for fully rendering components along with their children for testing complex multi-component interactions, but these should only be used in addition to isolated (shallow) unit tests and sparingly so.

Testing in Angular 2+ (After Refactor)

In Angular 2+, unfortunately, there not an exact match to the “shallow render” concept. If you attempt to run any test against AgentSelectorComponent after the AgentListComponent extraction, you'll get the following error:

https://gist.github.com/2344354a04f0cf6624256345ec8ebc36

This feedback is a little alarming, but the answer is actually in the first item of the list: our testing module doesn’t know anything about agent-list . That's fine! Right? We're just testing the root component. Unfortunately, we're stuck with two not-so-great options for testing AgentSelectorComponent :

Add AgentListComponent to the list of component declarations in our test module Add NO_ERRORS_SCHEMA to our test module's configuration (#3 in the error output above)

Adding AgentListComponent to the list of component declarations

In the above OOP example, this is akin to providing a real implementation of AvatarService to the tests for ProfileService . This is by definition not a unit test and makes our tests of AgentSelectorComponent dependent on the implementation of AgentListComponent . Additionally, we'd be accepting defeat down the entire component tree. If AgentListComponent took a service dependency on a new AgentDetailService , AgentSelectorComponent tests would need to then wire that up as well. If AgentListComponent took the next logical componentization step and extracted out a AgentListItemComponent , tests for both AgentSelectorComponent and AgentListComponent would be responsible for providing real implementations of that component. In a reasonably-sized application, this would quickly become unruly as well as devastatingly fragile.

Add NO_ERRORS_SCHEMA to the test module's configuration

Thankfully, the Angular 2+ testing guide addresses this limitation by offering the NO_ERRORS_SCHEMA option.

Again, unfortunately, this comes with a tradeoff. NO_ERRORS_SCHEMA is an all or nothing approach to avoiding template errors in the pursuit of component testing isolation. That is, you can add NO_ERRORS_SCHEMA to your testing configuration to perform shallow tests, but you no longer receive compiler errors on your templates during testing.

This means any and all errors or typos as far as html, Angular 2+’s templating language, or interpolated JavaScript go will be forgiven.

What’s more, the “ignored” directives/selectors offers no information about their rendering or use in the component under test save the element’s existence. For example, note the final test of AgentSelectorComponent in the refactored Angular 2+ code. It can test the non-nullness of a agent-list component, but it cannot inspect anything about the use of agent-list . Continuing the OOP testing analogy, this is akin to using a mocking library which could assert that an injected dependency was called without being able to inspect the arguments used to call the dependency.

Looking Back

While I feel that the NO_ERRORS_SCHEMA option in Angular 2+ is far superior to embracing the fragility and setup fanfare around eschewing true unit testing, I think the tradeoffs are considerable. Asserting not only the existence of a dependent component but also the use of the components is an important part of hardened unit tests.

Additionally, I have experienced frustration around some of the magic surrounding Angular 2+ testing as far as the async and fakeAsync helpers go which is particularly conflated by Jasmine's own async done => { /*testBody */ done()} syntax.

As far as mocking out services, it is again frustrating to work around some of the limitations of Angular 2+’s testing ecosystem. Even looking past the need to mock after instantiation, more troublesome is the lack of a framework-provided or community-standard solution for mocking service classes for injection. The testing docs guide users to create manual stubs for services or to spy on real services (blue pull quote). The former is cumbersome and requires some overhead when RxJS observables are involved. The latter is infeasible with services with even a single injected dependency and again, breaks the isolation value of unit tests.

Both Angular 2+ and React have their own flavor of testing patterns and their own rendering and “DOM”-querying APIs, but it is this author’s opinion that, in additon to that learning curve, Angular 2+ poses a greater challenge to testing.authors opinion

Framework-Specific Constructs and Syntax

Great news! With Angular 2+ and React embracing modern JavaScript with Babel and TypeScript, both frameworks offer productive new language features such as destructuring, arrow functions, class syntax, and more! That said, both frameworks also aim to enable developers to quickly build featureful, styled applications for the web. In line with that agenda, they have their own flavor of mixing JavaScript with web technologies like HTML and CSS.

React

Most strikingly, React shook the JavaScript the world by intentionally pulling markup into JavaScript as a syntax called JSX. Instead of JavaScript manipulating distinct HTML markup in other files, React components are intrinsically responsible for providing their content. Most simplistically, a React component can be built as stateless functional component taking in a name as a prop and returning an h1 with a message using:

https://gist.github.com/ae2f3acc431d6e8936a315d1c8b4c986

Or as a component class using the render function:

https://gist.github.com/be6acb766078f8506e78ce8e8e2b8de5

And then using it in an application:

https://gist.github.com/c309f1abc722de0b730a6013120619d3

To result in a webpage containing:

https://gist.github.com/20e45c6a2d0f8fc0cd7b49998c9876de

Once we’re comfortable (or at least familiar) with the concept of HTML in our JavaScript, there’s really only one extra syntax to add on: curly braces for interpolation. Anything inside of curly braces is interpreted as JavaScript rather than “HTML”/JSX. While we’ve seen this as a means for supplying props and rendering the value of a prop, it can also be used in three “semantic React” ways. I’ll note that these are not any type of special syntax, but rather the implementation of three semantic JavaScript patterns used in React. To set up the examples, I’ll first point out that the following two React render methods are functionally identical:

https://gist.github.com/d9ba7e027a3c59b860cbed6aeb5720a1

Knowing now that JSX nodes can be held in normal JavaScript variables and any JavaScript variable or value within curly braces inside of JSX, the following shouldn’t look too abnormal:

https://gist.github.com/20201b1ee23dd0c18ac85d18fc411d5a

The first example maps over a list, returning an li for each value. The second uses JavaScript's boolean short circuting to either return a falsy value from the left side (resulting in nothing being rendered) or the truthy component on the right side.

One might also consider style properties in raw JSX to be a special syntax of React ( backgroundColor vs background-color , but these are actually the standard CSSOM property names: see more).

Angular 2+

Angular 2+ takes a different approach in that it keeps its css free of JavaScript and its JavaScript/TypeScript free of anything resembling markup. It chooses instead to keep most business logic in TypeScript .ts component files and additionally implement an HTML-based interpolation language which includes a superset of JavaScript (not exactly TypeScript not exactly JavaScript) in typically separate .html files.

An analogue to the HelloWorld React component might look something like this:

https://gist.github.com/5810775b87866c0bc47f74af97c21e9f

With a corresponding template file which uses double curly braces for interpolation in the “expression context” of the companion component class:

https://gist.github.com/043ed1251725388a9a2c2fc4a7e6dfb1

To be used in another component’s template like so:

https://gist.github.com/bab1a135b6f589d38db876c93d66e512

Binding

When the right hand value of a property binding is to be interpreted as JavaScript rather than a literal value, the left hand side must be wrapped in square brackets, as seen above. Since this example is in fact literal, this could also be written as name="Duchess Archer" .

Event bindings are handled differently from property/data bindings. They use parens instead of square brackets such as <button (click)="handleClick($event)" > The left hand here binds to the native button click event and provides code to execute when that event is fired. In this case, a function handleClick on the wrapping component is called with the implicit $event variable. Be careful to note that the right hand expression is not a function reference but rather literal code.

If a child component needs to communicate to a parent component, it must use a custom event with the @Output property. This is illustrated in the Angular 2+ AgentList refactor above.

Finally, when a child component accepts an input from its parent which the child component itelf is also in need of directly setting, supplying that property from the partent with the [()] shorthand creates both an input binding as well as an event binding which sets the parent-local value of. Docs example here.

The FormsModule provides an even more advanced use of this operator in the form of [(ngModel)] which can be gleaned from the docs.

Structural Directives

Since the interpolated JavaScript in Angular 2+ template bindings is not responsible for structural logic such as conditional rendering and iteration, this responsibility is served by another mechanism provided by Angular 2+: structural directives.

With these directives, or special syntax within templates, we can reproduce the same markup as the React examples above (components with inline templates used for brevity):

https://gist.github.com/4d972781e1072b044d52558ac0d9fee5

In the iteration example, the element with the *ngFor directive is repeated for each item in the iterable, providing the item for use in any nested template markup. The conditional render example uses *ngIf to only include the associated node if the expression is truty. The third example builds on this by adding ; else non_header , which renders a template with the reference #non_header if showHeader is falsy. This template is subsequently defined by wrapping the intended rendered content in an ng-template element.

Pipes

Within a template, the interpolation flavor of JavaScript cannot access any external modules or functions outside of its expression context (the instance of the associated component). In order to perform presentational transforms, however, you can use any Pipes which are registered with your current module. These can either be parameterized or not:

https://gist.github.com/c1e87c1c5fb249760e66eb0a75b72e0c

They may also be chained indefinitely | pipe1 | pipe2 ... by adding a pipe and then a... Pipe.

Looking Back

Certainly, each framework diverges in its own way from the traditional JavaScript manipulation of static-CSS-styled, static HTML.

Perhaps my memory is poor, but I find myself strongly favoring React’s preference to leverage native JavaScript for interpolation within markup using a consistent symbol for both prop interpolation and structural logic.

While I am amused by the “banana-in-a-box” mnemonic for Angular 2+’s [()] , the framework's preponderance of syntax, however necessary, can be distracting during development and when reading code. Additionally, some things are dangerously close to obfuscation for the sake of brevity. In particular, the [()] shorthand if used in a hierarchy of components can easily trick engineers into creating multiple sources of truth for the bound value. Similarly, the async Pipe, which is stateful, impure, and creates Observable subscriptions to pump values and trigger change detection according to its own lifecycle, uses a construct which the docs state as "a way to write display-value transformations that you can declare in your HTML".

Construct-wise, I won’t go into detail here (uh, what’s my word count at?), but I do think change detection is a fascinating topic. Did you know the Angular 2+ team invented execution contexts for JavaScript and that’s what wraps the entire application in order to run full application change on the entire component tree for just about every event anywhere (mouse move, keyup, Promise resolve)? Or that a single setState at the root of a React application can cause every component to render, but may not result in a single DOM manipulation? Despite this, both frameworks still have wicked performance as well as powerful means of optimizing this workflow. Read more here: Angular [2+] Change Detection Explained) and here: React Reconcilliation.

Looking Way Back

You may have noticed a lean in my opinion. I’ll admit again that is was an opinion piece.

I’ve had some of my most joyful programming experiences doing componentization refactors with no functional change in mind other than maintainability. Those refactors have been assisted by a suite of tests that are easy to write, read, and debug. Those experiences happened to have been in an application written in React.

I hope to think that I’m a minority in my severe opinion on Angular 2+ and some of the difficulties I’ve had. I’d love to have every one of my complaints corrected and redacted. I have, however, spent no small amount of time trying to counter some of them.

And while I’m excited for the results of this year’s “State of JS” survey around React and Angular, last year’s satisfaction results seem to mirror my own: 92% percent of React users would use it again, while only 65% Angular 2 users would use it again (source). It’s a new framework though so hopefully we’ll see an uptick this year! : D

-Ryan Stelly @ryanastelly