struggle.png

Google's Angular and Facebook's React are now most popular tools for building browser (and not only) applications. Both are great solutions. While Angular2 is still in beta it has been already tested by few Google developer teams - AdWords, GreenTea and Fiber. List of applications built with React is really long and you can find there names like Instagram, Netflix, PayPal and many more.

Brutal war is coming.

First blood

There is already a bloodstained Angular 2 versus React comparison out there (by Cory House). It touches some important, higher level characteristic of both fighters. First drops of blood were spit on the ground. But the real struggle is yet to begin…

Know your enemy

Choosing between Angular and React is like choosing between buying an off-the-shelf computer and building your own with off-the-shelf parts.

Cory House has also told us:

Angular2 React
764k minified 151k minified (with Redux)
complete solution out of the box simple view library
a lot of Angular-specific syntax just know JavaScript
great consistency (and TypeScript) initial confusion and decision overload
wiring HTML and JS together in your head JSX - one file for component
mature, comprehensive framework a bunch of fast-moving, open-source libs
manual debugging, lack of completion support JSX - excellent development experience
web components friendly web components maybe
fails quietly at run time JSX - compile-time check -> fail fast

I can just add that React has great browser devtools extensions (Redux too). Sadly I could not find any for Angular2.

Arena

To compare these front end technologies I made a little TODO application. To make things even simpler I prepared single Redux core for both applications (inspired by Angular 2 — Introduction to Redux). Both are written in TypeScript so comparison is even clearer. You can find the source code used for this comparison on GitHub:

The Fight

The core of both technologies is a component. A unit of view. Both expect your app to be a tree of components. Also both encourage us to pass data to a component tree by its root vertex. Root-to-leaves data flow is what should make application "more functional", so let's go!

Round I: Functional Components

In such arborescence based application each vertex is a component. Each component:

  • accepts some data from parent node (I'll call it input)

  • returns a sub tree of components (view)

In both Angular2 and React input is handled by passing data from an element to its children by sub nodes properties (not html attributes). In both solutions view is more or less an XML tree.

TodoList component

A reusable, filterable, simple list of todos needs two pieces of information - array of todos (ITodo[]) and way of filtering (FilterType enum value). So our components input can look like this:

interface ITodoListProps {
  todos: ITodo[];
  filter: FilterType;
}

And components used anywhere in the app:

React:

<TodoList todoList={todos} filter={filterType}/>

Angular2:

<ul [todoList]="todos" [filter]="filterType"></ul>

And this is React component definition:

// src/components/todo-list.tsx

import { Todo } from './todo';

export function TodoList(props: ITodoListProps): JSX.Element {
  return (
    <ul className="todo-list">
      {todosFilter(props.todoList, props.filter)
        .map(todo => (
          <Todo todo={todo} key={todo.id}/>
        ))}
    </ul>
  );
}

And Angular2 version:

// src/components/todo-list.ts

import { Todo } from './todo';

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  directives: [Todo],
  host: {'class':'todo-list'},
  pipes: [TodosFilter],
  selector: '[todoList]',
  templateUrl: '/src/components/todo-list.html'
})
export class TodoList implements ITodoListProps {
  @Input() todoList: ITodo[];
  @Input() filter: FilterType;
}
// src/components/todo-list.html

<template ngFor #todo [ngForOf]="todoList | todosFilter:filter">
  <li [todo]="todo"></li>
</template>

Without any doubt React version is stateless, pure and simple. It takes data and returns DOM. Wow!

It works but it does not compile properly. I do not know if it's TypeScript .tsx support issue or React typings problem (please post a comment if you know the origin of this issue). Anyway, it failed so I had to switch to a class based React component. It holds state - 'props' is now member of component - but 'props' can still be treated as immutable input in functional approach:

// src/components/todo-list.tsx

import { Todo } from './todo';

export class TodoList extends Component<ITodoListProps, {}> {
  render(): JSX.Element {
    return (
      <ul className="todo-list">
        {todosFilter(this.props.todoList, this.props.filter)
          .map(todo => <Todo todo={todo} key={todo.id} />)}
      </ul>
    );
  }
}

Angular version has much more configuration. It seems to be too much for such simple example. It is there because Angular does not mix JS with HTML (directives, host, pipes, selector, templateUrl) and because of more sophisticated change detection mechanics (changeDetection).

The selector is a way to bind component JS definition with element in template - in React it's not needed as JS component is used as template element in JSX.

The pipes and directives are here to inform component what other components, directives and pipes (inline html filters) would be used in template - again JS components are used directly in JSX templates. React does not provide anything like templateless directives (for me it is a problem and I'll mention it later) nor inline pipes (because we can use JS pure functions).

The templateUrl is self-describing enough I think, isn't it? And it is considered a downside. But I really appreciate possibility to separate bigger templates from component file. Lack of compile time template check is of course Angular's weakness.

The host appears because of main difference in template rendering.

React's component function (or render method) returns the whole component tree with one vertex:

return (
  <ul className="todo-list">
    {todoList.map( todo => <Todo todo={todo} />)}
  </ul>
);

In Angular component is bound to an element (by selector mentioned above) which becomes root node of components tree. This root element is called host. So in Angular template we only put contents of the host element:

// src/components/todo-list.html

<li [todo]="todo" *ngFor="#todo of todoList"></li>

And if we want to add anything (like a css class, attribute value) to host element we can declare it in the host definition - e.g. {'class':'todo-list'} will add "todo-list" class to <ul [todoList]=… ></ul>. We can also bind dynamic values and listeners to host element using TypeScript decorators on component's properties.

Todo component

So we'll try now to add some event handlers. My Angular Todo component looks like this:

// src/components/todo.ts

interface ITodoProps {
  todo: ITodo;
}

@Component({
  host: {'class': 'todo'},
  selector: '[todo]',
  templateUrl: '/src/components/todo.html'
})
export class Todo implements ITodoProps {
  constructor(private todoActions: TodoActions) {}

  @Input() todo: ITodo;

  @HostBinding('class.done')
  private get isCompleted() {
    return this.todo.completed;
  }
}
// src/components/todo.html

<button (click)="todoActions.toggleTodo(todo.id)" class="toggle">
  {{ todo.text }}
</button>
<button (click)="todoActions.removeTodo(todo.id)" class="remove ion">
  Remove
</button>

We get ITodo entity as an input value. Output is a host element with two buttons. I have also bound static "todo" and conditional "done" css classes to the host element. Button clicks trigger corresponding methods of component's todoActions member. Not much magic. Only this (click)=… syntax may hurt purists' eyes. We can always drop sugar and use plain <button on-click=… > instead (or bind-anything=… instead of [anything]=…).

And React version:

interface ITodoProps {
  todo: ITodo;
  key: string;
}

export class Todo extends Component<ITodoProps, {}> {
  private getToggleAction(todo: ITodo) {
    return () => {
      todoActions.toggleTodo(todo.id);
    }
  }
  
  private removeTodo(todo: ITodo) {
    todoActions.removeTodo(this.props.todo.id);
  }
  
  public render(): JSX.Element {
    return (
      <li className={'todo' + (this.props.todo.completed ? ' done' : '')}>
        <button
          onClick={this.getToggleAction(this.props.todo)}
          className="toggle">
          {this.props.todo.text}
        </button>
        <button
          onClick={this.removeTodo.bind(this)}
          className="remove ion">
          Remove
        </button>
      </li>
    );
  }
}

We get ITodo entity as an input value. Output is an element with two buttons. The root element of returned XML tree has static "todo" and conditional "done" css classes. Until now it's quite similar. But I see here two quite irritating features of React:

  • in onClick handlers this context is lost - we have to do tricks (bind or curry)

  • class attribute has to be fed with its whole value - no CSS class management mechanics

I also don't like className and htmlFor properties. They have to be used because class and for are reserved words in JS. Looks like we have to pay somehow for mixing HTML with JS…

Combine: React

Todo

<li className={…}>
  <button onClick={…} className="toggle">{…}</button>
  <button onClick={…} className="remove ion">Remove</button>
</li>

TodoList

<ul className="todo-list">
  {todoList.map(todo => <Todo todo={todo}/>)}
</ul>

Somewhere higher in arborescence:

<TodoList todoList={this.props.todoList.todos} filter={this.props.filter}/>

And the output:

<ul class="todo-list" data-reactid=".0.1.1…">
  <li class="todo" data-reactid=".0.1.1…">
    <button class="toggle" data-reactid=".0.1.1…">Toggle</button>
    <button class="remove ion" data-reactid=".0.1.1…">Remove</button>
  </li></ul>

Combine: Angular2

Todo

@Component({
  host: {'class': 'todo'},
  selector: '[todo]',
  
})
export class Todo  {
  
  @HostBinding('class.done')
  private get isCompleted() {  }
}
<button (click)="…" class="toggle">{{ … }}</button>
<button (click)="…" class="remove ion"></button>

TodoList

@Component({
  host: {'class':'todo-list'},
  selector: '[todoList]',
  
})
export class TodoList  {  }
<template ngFor #todo [ngForOf]="todoList | todosFilter:filter">
  <li [todo]="todo"></li>
</template>

Somewhere higher in arborescence:

<ul [todoList]="todoList.todos" [filter]="filter"></ul>

And the output:

<ul class="todo-list">
  <!--template bindings={}-->
  <li class="todo">
    <button class="toggle">asd fasd f</button>
    <button class="remove ion">Remove</button>
  </li></ul>

Round I Verdict

React is a king of little, simple, one purpose components. If your application can be easily expressed as a tree of simple data-to-view transformations React seems to be the best answer. And it's undoubtedly the most functional view rendering system we can use. The more event handling and/or sophisticated UI the less Angular2 boilerplate is painful. As a matter of fact, Angular2 component configuration and binding annotations inconvenience tend to be inversely proportional to components complication. And furthermore it still has functional approach (not so pure, but sticks to idea data => view. Probably some day Reactangular or Angulareact framework will bring us possibility to efficiently utilize both approaches in one application. But we still have to choose.

Functional or not, it should make developer's work painless in any possible way. Readability of code is one of them and it seems HTML is crucial in this comparison.

Round II: View aesthetics

Let's prepare a new component with a more complex structure - the ListOfLists.

Angular2

<li *ngFor="#todoList of lists trackBy _byId"
  [class.active]="isCurrent(todoList)"
  class="todo-lists-list-item">
  <header [todo-list-header]="todoList"></header>
  <ul *ngIf="!isCurrent(todoList)" class="todo-list">
    <li *ngFor="#todo of todoList.todos"
      class="todo-preview"
      [class.done]="todo.completed">
      {{ todo.text }}
    </li>
  </ul>
  <editable-list *ngIf="isCurrent(todoList)" [todoList]="todoList">
  </editable-list>
</li>

React

<ul className="todo-lists-list">
  {this.props.lists.map(list => (
    <li key={list.id} className={this.listClassName(list)}>
      <TodoListHeader todoList={list}/>
      {list.id !== this.props.currentId ? (
        <ul className="todo-list">
          {list.todos.map(todo => (
            <li key={todo.id}className={this.todoClassName(todo)}>
              {todo.text}
            </li>
          ))}
        </ul>
      ) : (
        <EditableList todoList={list} filter={this.props.filter}/>
      )}
    </li>
  ))}
</ul>

Round II Verdict

Is there anything I should say? Choose what you like more!

Ok. In my opinion:

<li *ngFor="#item in list trackBy fn"><li>

is much more readable than:

{list.map((item, index) => (<li key={index}></li>))}

In some places mixing JS code with XML values is a mess.

A matter of taste.

Round III: Change detection

Let's state here one important thing - DOM update (after change was detected) is handled similarly in both frameworks. They update only the parts that actually should be changed. We'll see now what is the difference in detecting if a change have occurred.

React

React basic mechanics are simple. If state or props of component where changed - change handlers are called:

  • state triggers change when setState() method is called

  • props change occurs when parent component re-renders

There is also possibility to trigger change by calling forceUpdate(). Change detectors are triggered only in subtree where change occurred. Simple and optimal. The only little downside is that we would have to setState() manually in the root vertex of our arborescence.

You probably have noticed a key={todo.id} prop in examples. It's the mechanism used for detecting changes of list elements. If a value passed to key property has changed - the HTML representing a related element is re-rendered. Quite verbose, but sane. Downside - when using a strict typing in your projects, you'll be forced to define a key: string; additional property on all your "listed" components.

Angular

Angular team decided to choose a slightly different approach. They have incorporated zone.js to plug into asynchronous browser callbacks (setTimeout, setInterval, event handlers and XMLHttpRequest events). And when any of these is called Angular runs change detection. You can find great in-depth explanation here. What is more interesting - you can decide to choose a change detection strategy on any component of your application tree.

In the example application I used an OnPush strategy so all my components check for updates only when any of their @Input() properties have changed. So a change detection in my Angular application acts almost exactly as in React one. There are actually few more strategies: CheckOnce, Checked, CheckAlways, Detached, OnPush and Default. For some explanation see ChangeDetectionStrategy docs.

Just like React's key={…} Angular has its own way to handle list elements changes - NgFor.ngForTrackBy. In a code it looks like this:

<li *ngFor="#todoList of lists trackBy _byId" ></li>

The _byId passed here is a function of form list => list.id. So quite similar to React - we have to create some unique identifier for each element in the list so a change detector knows if it is still the same element. Maybe slightly more gentle than in React, but you have to learn more framework specific syntax.

Round III Verdict

While both solutions offer a quite sane out of the box approach React's default change detection is without a doubt better optimized. On the other hand Angular's under the hood detection is definitely a game changer in web development as it plugs into the native browser asynchronous layer so responsibility for broadcasting possible changes is taken away from the developer. Of course both ideas also have their downsides. React component's changes have to be triggered manually if data does not come from props, but straight from the store (basic Flux architecture). If you would like to make Angular application follow more functional approach, you will have to define proper detection strategy explicitly in each single component.

Nevertheless both are great. Angular gives more flexibility with cleaner configuration tools, so this time React looses.

Round IV: Extending HTML

Doing what?? Just adding some functionality to an HTML element. And making it easily reusable on many elements.

Angular

@Directive({selector: '[inp-alerter]'})
export class InpAlerter {

  @Input('inp-alerter') inpAlerter: string;

  @HostBinding() placeholder = 'Write something here';

  @HostListener('keyup.enter', ['$event.target.value'])
  onInput(val: string): void {  }
}

A simple example above shows how we can take some value from an input property, bind some other to element property or add an event listener to an element. And it will be applied to any HTML element with inp-alerter attribute. And this is only a small part of what can be done. Actually @Directive decorated classes have almost all superpowers that can be used in @Components, but do not have templates. Simple and powerful.

React

There is a possibility to use React mixins to achieve similar functionality. But it would be a great dose of overengineering and could be error prone. Anyway, a developer has to create a component for each set of element functionalities used in the application.

K.O.

Final round brought pain and gore. React's guts are flowing out and brain is smeared on the stage…

Thanks God it's only a metaphor.

In a world almost conquered by Angular 1.x and ready for Web Components a mechanism to extend HTML elements behavior is a MUST HAVE. Angular2 won this round by K.O. but I'm sure that React will do the homework.

The Curtain Falls

It's obvious that React and Angular2 share many similarities. They represent similar approaches to application architecture and handling data. On the other hand every functionality is implemented in a completely different way (ok, lifecycle hooks for components are almost identical). These differences do not however imply difference in difficulty of application development. Both solutions provide a sufficient set of robust tools to build a well organized, scalable and reactive application core.

Of course React is smaller and covers only view/component responsibility - and that is what I have compared here. Lack of engine to add simple functionalities to HTML elements is actually the only Reacts undisputed fault.

Angular2 is more mature, flexible and complete solution. But it's new - still in beta - and has advantage over the enemy because it has incorporated ideas from both Angular1 and React experience.

Further reading