Comparison of Angular 2 and React

Comparison of Angular 2 and React

By Mark Volkmann, OCI Partner & Principal Software Engineer and Lance Finney, OCI Principal Software Engineer 

January 2016


Introduction

This article compares two popular web app frameworks, Angular 2 (beta.0) and React (0.14). It does not provide detailed explanations on how to use them. There are many other web tutorials that provide that level of detail. The focus of this article is to show side-by-side comparisons of the corresponding pieces in a sample application.

Lance has been working with Angular 2 since August 2015. He provided the Angular 2 code here. Lance finds Angular 2 components to be syntactically simpler and semantically more powerful than Angular 1. Angular 2 is now in beta. There may be more breaking changes before the official release.

Mark started investigating React about the same time and has about two weeks of experience in actually using it. He provided the React code here. Mark has been using Angular 1 for almost three years, so he knows that well. He has only read about Angular 2, so he barely knows that.

The Angular team now refers to Angular 1 as "AngularJS" and Angular 2 as "Angular". So we will use "Angular" to refer to Angular 2 throughout the rest of this article.

This article compares the code for implementing a typical demo Todo app. Some may feel that this app is too small to use as a basis for comparison, but we think it demonstrates enough features to provide value. The main aspects it demonstrates are how components are defined and how events are handled.

We attempt to anticipate the things proponents of the two frameworks might say about the code. These are labeled with AP for "Angular Proponent" and RP for "React Proponent."

Angular Overview

The fundamental building blocks of an Angular app are directives. Directives with a view are called components, and these account for the majority of directives in most apps. The intention is that developers will be able to create easily reusable components for their applications that are also easy to reason about. Angular components will be largely interchangeable with Web Components so that they will be able to work well with other frameworks.

Angular components have input and output properties to allow interaction with other components. For example, a component could set up its view based on configuration it receives from its parent component and then be notified to change its appearance by an event it receives from a child or sibling component.

Angular is built with significant support for dependency injection (so a component can receive the necessary singleton services) and routing, but that is beyond the scope of this article.

React Overview

React emphasizes the use of stateless components that only use data that is passed to them via props. Props are values specified in JSX attributesJSX is an HTML-like XML syntax for specifying the DOM representation of components. It is embedded in JavaScript code, colocated with related logic such as component lifecycle methods and event handling methods. Event handling methods can make Ajax calls. They also typically create action objects that describe the event and dispatch them to a "store". A store manages making changes to data used by the application ("state") and notifies interested components when it changes.

One popular state management library, Redux, holds all app data in a single store. It is considered to be a variant of the Flux architecture. Pure functions called reducers are written to handle these actions. Reducers are passed an action object and the current state. They return the new state.

Components can register to listen for state changes. When they are notified, they typically re-render themselves. This is made efficient through the use of a virtual DOM, which is an in-memory representation of what the browser is currently displaying. React creates a new version of the virtual DOM, diffs it against the current version, and only makes the minimal set of required DOM modifications.

One of the most appealing features of React is the way it divides development into distinct tasks. Development of most components begins by only thinking about how to render data passed to it. Then, event handling is considered. This optionally makes Ajax calls and dispatches actions. Event handling does not update the UI. Finally, resolver functions are written to update the state based on a provided action.

Component and server function

Demo App Overview

The code for both versions is in Github at https://github.com/mvolkmann/react-examples. See the todo (React) and todo-ng2 (Angular) directories.

The Angular code uses TypeScript. Surveys of developers interested in Angular reveal that most plan to use TypeScript instead of JavaScript in future apps. The Angular code includes the file tsconfig.json which provides configuration for the TypeScript compiler, but we won't examine that file. TSLint is used to check for potential issues in the TypeScript code.

The React version uses Babel to transpile ES6 code to ES5. It uses ESLint to check for potential issues in the JavaScript code. See todo/webpack.config.js for details.

Both versions of the app use webpack to build the app into bundle files. See their todo/webpack.config.js files for details.

Here's a screenshot of the app:

Screenshot of app

There are four things a user can do in this app.

  1. Create a new todo item.
  2. Mark (and unmark) a todo item as complete.
  3. Delete a todo item.
  4. Archive all todo items that have been marked as complete.

The instructions for running both versions are the same.

  1. npm install.
  2. npm start.

Dependencies

Both versions have dependencies on npm packages. Here are the package.json files that show these:

Angular React
{
  "name": "ng2-todo",
  "version": "1.0.0",
  "description": "Todo app in Angular",
  "scripts": {
    "start": "webpack-dev-server
      --content-base src --inline --port 8081"
  },
  "devDependencies": {
    "ts-loader": "^0.7.2",
    "tslint": "^3.0.0-dev.1",
    "typescript": "^1.7.3",
    "webpack": "^1.12.9",
    "webpack-dev-server": "^1.14.0"
  },
  "dependencies": {
    "angular2": "2.0.0-beta.0",
    "es6-promise": "3.0.2",
    "es6-shim": "0.33.3",
    "reflect-metadata": "0.1.2",
    "rxjs": "5.0.0-beta.0",
    "zone.js": "0.5.10"
  }
}
{
  "name": "react-todo",
  "version": "1.0.0",
  "description": "Todo app in React",
  "scripts": {
    "start": "webpack-dev-server
      --content-base . --inline"
  },
  "devDependencies": {
    "babel-core": "^6.1.2",
    "babel-loader": "^6.0.1",
    "babel-preset-es2015": "^6.1.18",
    "babel-preset-react": "^6.1.2",
    "eslint": "^1.6.0",
    "eslint-loader": "^1.0.0",
    "eslint-plugin-react": "^3.5.1",
    "webpack": "^1.12.9",
    "webpack-dev-server": "^1.14.0"
  },
  "dependencies": {
    "react": "^0.14.3",
    "react-dom": "^0.14.3"
  }
}

AP: “The React package.json file sure lists a lot of dev dependencies.”

RP: “One could view the Babel and ESLint dependencies as providing some of what TypeScript provides. Babel provides tranpilation of ES6 code to ES5. ESLint provides pre-runtime detection of certain coding issues. This is similar to TSLint which is used in the Angular version.”

Main HTML

The main HTML file for both apps is index.html. These files are very similar. Since both versions are built using webpack, the only JavaScript file they need to reference is bundle.js.

Angular React
<!DOCTYPE html>
<html>
  <head>
    <title>Angular Todo App</title>
    <link rel="stylesheet" href="todo.css">
  </head>
  <body>
    <todo-list>Loading...</todo-list>
    <script src="bundle.js"></script>
  </body>
</html>
<!DOCTYPE html>
<html>
  <head>
    <title>React Todo App</title>
    <link rel="stylesheet" href="todo.css">
  </head>
  <body>
    <div id="container"></div>
    <script src="build/bundle.js"></script>
  </body>
</html>

Styling

The CSS is the same for both versions of the app. Here is todo.css.

body {
  font-family: sans-serif;
  padding-left: 10px;
}

button {
  margin-left: 10px;
}

li {
  margin-top: 5px;
}

ul.unstyled {
  list-style: none;
  margin-left: 0;
  padding-left: 0;
}

.done-true {
  color: gray;
  text-decoration: line-through;
}

Components

Both the React and Angular versions use two JavaScript files, one for each of the components, Todo and TodoList.

Here are the source files for the TodoList component, which represents the entire UI for the app.

Angular todoListCmp.ts React todo-list.js
 
import 'angular2/bundles/angular2-polyfills';
import {Component} from 'angular2/core';
import {bootstrap} from 'angular2/platform/browser';
import {TodoCmp, ITodo} from "./todoCmp";
 
import React from 'react';
import ReactDOM from 'react-dom';
import Todo from './todo';
 
// Used for type of state property in TodoListCmp class.
// Both properties are optional.
interface IState {
  todos?: ITodo[];
  todoText?: string;
}
 
 
// This syntax is called a Decorator and is similar
// to Annotations in other languages.  Decorators are
// under consideration to be included in ES2016.
// The Component decorator indicates that
// TodoListCmp is a reusable UI building block.
@Component({
  // other components used here
  directives: [TodoCmp],
  // TodoListCmp's HTML element selector
  selector: 'todo-list',
  template: // see value to left of
            // React render method below
})
 
 
export class TodoListCmp {
  private static lastId: number = 0;
  private state: IState;

  constructor() {
    this.state = {
      todos: [
        TodoListCmp.createTodo('learn Angular', true),
        TodoListCmp.createTodo('build an Angular app')
      ]
    };
  }
 
let lastId = 0; // no static class properties in ES6

class TodoList extends React.Component {
  constructor() {
    super(); // must call before accessing "this"
    this.state = {
      todos: [
        TodoList.createTodo('learn React', true),
        TodoList.createTodo('build a React app')
      ]
    };
  }
 
  static createTodo(
    text: string, done: boolean = false): ITodo {
    return {id: ++TodoListCmp.lastId, text, done};
  }
 
  static createTodo(text, done = false) {
    return {id: ++TodoList.lastId, text, done};
  }
 
  get uncompletedCount(): number {
    return this.state.todos.reduce(
      (count: number, todo: ITodo) =>
        todo.done ? count : count + 1,
      0);
  }
 
  get uncompletedCount() {
    return this.state.todos.reduce(
      (count, todo) =>
        todo.done ? count : count + 1,
      0);
  }
 
  onAddTodo(): void {
    const newTodo: ITodo =
      TodoListCmp.createTodo(this.state.todoText);
    this.state.todoText = '';
    this.state.todos = this.state.todos.concat(newTodo);
  }
 
  onAddTodo() {
    const newTodo =
      TodoList.createTodo(this.state.todoText);
    this.setState({
      todoText: '',
      todos: this.state.todos.concat(newTodo)
    });
  }
 
  onArchiveCompleted(): void {
    this.state.todos =
      this.state.todos.filter((t: ITodo) => !t.done);
  }
 
  onArchiveCompleted() {
    this.setState({
      todos: this.state.todos.filter(t => !t.done)
    });
  }
 
  onChange(newText: string): void {
    this.state.todoText = newText;
  }
 
  onChange(name, event) {
    this.setState({[name]: event.target.value});
  }
 
  onDeleteTodo(todoId: number): void {
    this.state.todos = this.state.todos.filter(
      (t: ITodo) => t.id !== todoId);
  }
 
  onDeleteTodo(todoId) {
    this.setState({
      todos: this.state.todos.filter(
        t => t.id !== todoId)
    });
  }
 
  onToggleDone(todo: ITodo): void {
    const id: number = todo.id;
    this.state.todos = this.state.todos.map(
      (t: ITodo) => t.id === id ?
        {id, text: todo.text, done: !todo.done} :
        t);
  }
 
  onToggleDone(todo) {
    const id = todo.id;
    const todos = this.state.todos.map(t =>
      t.id === id ?
        {id, text: todo.text, done: !todo.done} :
        t);
    this.setState({todos});
  }
 
  // This is the value of the @Component template
  // property above.  It was moved here for
  // easy comparison with the React render method.
  `<div>
    <h2>To Do List</h2>
    <div>
      {{uncompletedCount}} of
      {{state.todos.length}} remaining
      <!-- Clicking this button invokes the
           component method onArchiveCompleted. -->
      <button (click)="onArchiveCompleted()">
        Archive Completed
      </button>
    </div>
    <br/>
    <form>
      <!-- [ngModel] tells where to get the value. -->
      <!-- (input) tells what to do on value change. -->
      <input type="text" size="30" autoFocus
        placeholder="enter new todo here"
        [ng-model]="state.todoText"
        (input)="onChange($event.target.value)"/>
      <button [disabled]="!state.todoText"
        (click)="onAddTodo()">
        Add
      </button>
    </form>
    <ul>
      <!--
        This uses a for loop to generate TodoCmps.
        #todo defines a variable to use within the loop.
        [todo] sets a property on each TodoCmp.
        (onDeleteTodo) and (onToggleDone) are outputs
        from the TodoCmp, and they call functions
        on TodoListCmp with emitted values.
        deleteTodo receives a type of number.
        toggleDone receives a type of ITodo.
      -->
      <todo *ngFor="#todo of state.todos" [todo]="todo"
        (on-delete-todo)="onDeleteTodo($event)"
        (on-toggle-done)="onToggleDone($event)"></todo>
    </ul>
  </div>`
 
  render() {
    // Can assign part of the generated UI
    // to a variable and refer to it later.
    const todos = this.state.todos.map(todo =>
      <Todo key={todo.id} todo={todo}
        onDeleteTodo=
          {this.onDeleteTodo.bind(this, todo.id)}
        onToggleDone=
          {this.onToggleDone.bind(this, todo)}/>);

    return (
      <div>
        <h2>To Do List</h2>
        <div>
          {this.uncompletedCount} of
          {this.state.todos.length} remaining
          <button
            onClick={() => this.onArchiveCompleted()}>
            Archive Completed
          </button>
        </div>
        <br/>
        <form>
          <input type="text" size="30" autoFocus
            placeholder="enter new todo here"
            value={this.state.todoText}
            onChange=
              {e => this.onChange('todoText', e)}/>
          <button disabled={!this.state.todoText}
            onClick={() => this.onAddTodo()}>
            Add
          </button>
        </form>
        <ul className="unstyled">{todos}</ul>
      </div>
    );
  }
 
} // end of TodoListCmp class

// Each Angular app needs a bootstrap call
// to explictly specify the root component.
// In larger apps, bootstrapping is usually in
// a separate file with more configuration.
bootstrap(TodoListCmp);
 
} // end of TodoList class

// This renders a TodoList component
// inside a specified DOM element.
// If TodoList was used in more than one place,
// this would be moved to a different JavaScript file.
ReactDOM.render(
  <TodoList/>,
  document.getElementById('container'));

AP: “I see that the render method embeds HTML directly in the JavaScript code. What's up with that? Shouldn't there be a separation between the HTML being rendered and the component logic?”

RP: "That's not HTML, it is JSX. JSX is an HTML-like syntax extension to JavaScript that React components use to specify the DOM to be rendered. The React philosophy is that separating component HTML from its JavaScript code is merely a separation of technologies, not a separation of concerns. These belong together."

RP: "JSX looks very similar to HTML, but there are minor differences. It is XML, so all tags must be properly terminated. Names that contain multiple words in HTML are camelcased in JSX (ex. onClick). Two HTML attributes have different names in JSX, class becomes className and for (used on label elements) becomes htmlFor. This is done because class and for are reserved words in JavaScript. XML comments are not supported, but multi-line JavaScript comments are."


RP: “I see that the Angular2 version uses a decorator to specify several things about the component. React doesn't use an equivalent of "selector" because the name used in the JSX is the same as the name of the component. React doesn't use an equivalent of the decorator property "directives" because it doesn't have to declare other components that it uses."

AP: “Using selectors simplifies defining styles in CSS or using jQuery to target a particular component.”


RP: “React embeds JSX in components. Is it customary in Angular to embed HTML in JavaScript files using the template property of the @Component decorator?”

AP: “The @Component decorator supports both a template property to define the markup inline, and a templateUrl property to define the markup in a separate file. Generally template is used for shorter components, and templateUrl is used for longer components.”


AP: “React's need to use bind for event handling function isn't intuitive. The Angular way is easier to read.”

RP: “That is needed because the value of event handling attributes needs to be a function reference, not a call to a function. However, I have to agree that it makes the code look a little confusing. Function bind is a part of JavaScript not often used by developers, but it must be learned in order to use React.”


RP: “There seems to be a fair amount of Angular-specific syntax in the template. Why are some attribute names surrounded with square brackets and parentheses? What's with the * and # characters? Why is it necessary to precede variables with # inside the *ng-for expression, but not when they are referenced outside of that?”

AP: "Yes, there is a log of specific syntax, with a comprehensive developer guide on its use. Here is a summary:


RP:"I see that much of the extra code is related to type checking.”

AP: “Yes, it takes more code to specify the types, but the tradeoff is worthwhile. TypeScript catches many more coding issues at compile time than ESLint does.”


Here are the source files for the Todo component that represents a single item in the list.

Angular todoCmp.ts React todo.js
 
import {Component, Input, Output, EventEmitter}
  from 'angular2/angular2';

export interface ITodo {
  id: number;
  text: string;
  done: boolean;
}

@Component({
  selector: 'todo',
  template: `
    <li>
      <input type="checkbox"
        [checked]="todo.done"
        (change)="toggleDone()"/>
      <span [ng-class]="'done-' + todo.done">
        {{todo.text}}
      </span>
      <button (click)="deleteTodo()">Delete</button>
    </li>`
})
export class TodoCmp {
  // @Input allows this component to receive
  // initialization values from the containing component.
  @Input() todo: ITodo;
  // @Output allows this component to
  // publish values to the containing component.
  @Output() onDeleteTodo:
    EventEmitter<number> = new EventEmitter<number>();
  @Output() onToggleDone:
    EventEmitter<ITodo> = new EventEmitter<ITodo>();

  deleteTodo(): void {
    this.onDeleteTodo.next(this.todo.id);
  }

  toggleDone(): void {
    this.onToggleDone.next(this.todo);
  }
}
 
import React from 'react';

// There are three ways to define React components.
// This is the stateless function component form
// which only receives data through "props".
// A props object is passed to this function
// and destructured.
const Todo = ({onDeleteTodo, onToggleDone, todo}) =>
  <li>
    <input type="checkbox"
      checked={todo.done}
      onChange={onToggleDone}/>
    <span className={'done-' + todo.done}>
      {todo.text}
    </span>
    <button onClick={onDeleteTodo}>Delete</button>
  </li>;

// Optional validation of props.
const PropTypes = React.PropTypes;
Todo.propTypes = {
  todo: PropTypes.object.isRequired,
  onDeleteTodo: PropTypes.func.isRequired,
  onToggleDone: PropTypes.func.isRequired
};

export default Todo;

AP: “I think that React's stateless function is a really nice feature. The code is significantly simpler than the Angular code.”

RP: “Yeah, I like that too!”

Other Topics

AP: “Angular is a framework that supplies most of the functionality needed by serious web applications. React is more like a library. It only supports the view layer. Applications that use React have to choose and install libraries to handle concerns such as routing, REST, and data management."

RP: “All that is true. However, there are options for all of these that are well tested and commonly used in React projects. These include react-router for routing, Fetch or axios for REST, and Redux for data management."

Learning

If you choose to use Angular, you will need to learn:

If you choose to use React, you will need to learn:

RP: Angular built in directives like NgFor and pipes are handled with plain JavaScript in React. This is why some people say that React feels more like coding in JavaScript than Angular and doesn't require learning as many framework-specific concepts. For example, if we have an array of user objects and want to output their last names in all uppercase, we can do this in Angular:

{{user.lastName | uppercase}}

and this in React:

users.map(user =>
{user.lastName.toUpperCase()}
)

AP: "I don't really see one approach is inherently better or worse than the other. There are some features in the Angular template syntax that I think are nice (like [class.disabled]="disabledProperty" to set the disabled class programmatically), but the same can be done in JavaScript. Additionally, the Angular team plans to add syntactic sugar as the framework evolves through the Beta period, so it's likely that there will be Angular template constructs that are clearly advantageous by the time the framework is fully released."

Summary

Both the Angular and React frameworks are strong contenders with sizable user communities and large corporate backing. They have somewhat different philosophies, and these differences will likely cause you to lean toward one over the other. There are many other JavaScript-based web frameworks that could be considered, but none that approach the popularity and support of these two.
 

References

Software Engineering Tech Trends (SETT) is a regular publication featuring emerging trends in software engineering.