Angular vs React

An out-of-the-box comparison

Doguhan Uluca

How do we compare?

  • Purpose
  • Contexts
  • Features
  • Code architecture
  • Tooling
  • Support

Purpose

AdWords

2016Q4 Revenue - $22.4 billion

Facebook

2016 - 1.86 billion users

What problems do they address?

What are the appropriate use cases?

It depends

Contexts

  1. SPA
  2. Server-side
  3. Server-side MVC

SPA Context

  • Pros
    • Distributed execution
    • Fast and fluid
    • Native to the web
  • Cons
    • Can't control user experience
    • Compatibility issues

SPA Targets

  • Native web
  • Progressive web
  • Mobile
  • Desktop

Why?

Shadow

DOM

vs

Virtual

DOM

Lack of Native F12 Dev Tools Support

😢

😞

scoping

performance

Server-side Context

  • Pros
    • Consistent user experience
    • Consistent performance
  • Cons
    • Need server fire-power
    • More bandwidth usages

Server-side Targets

  • Static Web

Server-Side MVC Contexts

  • ASP.NET
  • Java Spring
  • Ruby on Rails
  • Node with ejs
  • Python Django
  • Similar pros/cons to Server-side

Server-side MVC Targets

  • Static Web

Server-Side MVC

  • Impossible to separate of concerns
  • Need experts for maintenance
  • Guarantees big-bang rewrite

Read MVVM vs MVC on DevPro here. (10/2013)

Just Don't Do It

✔

Features

DOM support

  • Shadow
  • Virtual

Targets

  • Static Web
  • Native web
  • Progressive web
  • Mobile
  • Desktop

Language Support

  • ES5
  • ES2015
  • ES2016
  • TypeScript

Components

  • Web Components
  • Encapsulation
  • Single Purpose
  • File organization
  • Reusability
  • Templating

Templating

  • HTML
    • Shadow DOM
  • JSX
    • HTML-like
    • Virtual DOM
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})

export class AppModule { }
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  // templateUrl: './app.component.html',
  // styleUrls: ['./app.component.css'],
   styles: [`
    .card {
      height: 70px;
      width: 100px;
    }
  `],
  encapsulation: ViewEncapsulation.Native
  // encapsulation: ViewEncapsulation.None
  // encapsulation: ViewEncapsulation.Emulated is default 
})

@View({
  template: `
  	<h1>
	  {{title}}
	</h1>
    `,
  directives: [CORE_DIRECTIVES, FORM_DIRECTIVES]
  })

export class AppComponent {
  title = 'app works!'
}
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './index.css';

ReactDOM.render(
  <App />,
  document.getElementById('root')
)
import React, { Component } from 'react'
import logo from './logo.svg'
import './App.css'

class App extends Component {
  render() {
    return (
      <div className="App">
        <div className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h2>Welcome to React</h2>
        </div>
        <p className="App-intro">
          Yay, it works!.
        </p>
      </div>
    )
  }
}
import React, { Component } from 'react'
import logo from './logo.svg'
import './App.css'

class App extends Component {
  render() {
    return 
      React.createElement('App', {className: 'App'},
        React.createElement('div', {className: 'App-header'},
          React.createElement('img', {src: this.props.logo, className: 'App-logo', alt: 'logo'},
            React.createElement('h2', {}, 'Welcome to React')
          )
        ),
        React.createElement('p', {className: 'App-intro'}, 'Yay, it works!')
      )
  }
}

Redux

... is a predictable state container

  • Single source of trust
  • State is read-only
  • Changes are made with Pure "Reducer" Functions

Reactive Extensions

... programming with asynchronous data streams

  • rxjs
  • Observables
  • Toolbox of functions to combine, create and filter the streams

Pub/Sub or Event-based

Event Source

Event Handler

user clicks

window.alert('Are you sure?')

onClick='confirmSave()'

Pub/Sub or Event-based

updated data

fetchDetails()

updateCache()

showToastMessage()

Pub/Sub or Event-based

import { connect } from 'react-redux'
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'

const getVisibleTodos = (todos, filter) => {
  switch (filter) {
    case 'SHOW_ALL':
      return todos
    case 'SHOW_COMPLETED':
      return todos.filter(t => t.completed)
    case 'SHOW_ACTIVE':
      return todos.filter(t => !t.completed)
  }
}

const mapStateToProps = (state) => {
  return {
    todos: getVisibleTodos(state.todos, state.visibilityFilter)
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    onTodoClick: (id) => {
      dispatch(toggleTodo(id))
    }
  }
}

const VisibleTodoList = connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)
import React, { PropTypes } from 'react'
import Todo from './Todo'

const TodoList = ({ todos, onTodoClick }) => (
  <ul>
    {todos.map(todo =>
      <Todo
        key={todo.id}
        {...todo}
        onClick={() => onTodoClick(todo.id)}
      />
    )}
  </ul>
)

TodoList.propTypes = {
  todos: PropTypes.arrayOf(PropTypes.shape({
    id: PropTypes.number.isRequired,
    completed: PropTypes.bool.isRequired,
    text: PropTypes.string.isRequired
  }).isRequired).isRequired,
  onTodoClick: PropTypes.func.isRequired
}

Reactive or Streaming

Event Source

mouse clicks

filter(x >= 2)

throttle(250ms)

map(list.length)

<li *ng-for="i in list | async">

window.alert('Are you sure?')

Reactive or Streaming

var button = document.querySelector('.this')
var clickStream = Rx.Observable.fromEvent(button, 'click')

var multiClickStream = clickStream
    .buffer(function() { return clickStream.throttle(250) }
    .map(function(list) { return list.length })
    .filter(function(x) { return x >= 2 })

multiClickStream.subscribe(function (numclicks) {
    window.alert('You double clicked!')
})

Reactive or Streaming

Code Architecture

Presentation

API

Business

Persistence

Best Practices

IDE

Patterns

Libraries

TESTING

View

View Model

Controller / Router

Services ($http, redux, logic)

Best Practices

IDE

Patterns

Libraries

TESTING

Anatomy

of an Angular App

Angular

app.ts

rootRouter

services

filters

directives

/a: default

/master

/detail

/b/...

/c

childRouter

/d

/e

/f

import { NgModule }       from '@angular/core'
import { BrowserModule }  from '@angular/platform-browser'
import { FormsModule }    from '@angular/forms'
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'

import { Router } from '@angular/router'

import { AppComponent }            from './app.component'
import { AppRoutingModule }        from './app-routing.module'

import { HeroesModule }            from './heroes/heroes.module'
import { ComposeMessageComponent } from './compose-message.component'
import { LoginRoutingModule }      from './login-routing.module'
import { LoginComponent }          from './login.component'
import { PageNotFoundComponent }   from './not-found.component'

import { DialogService }           from './dialog.service'

@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    HeroesModule,
    LoginRoutingModule,
    AppRoutingModule,
    BrowserAnimationsModule
  ],
  declarations: [
    AppComponent,
    ComposeMessageComponent,
    LoginComponent,
    PageNotFoundComponent
  ],
  providers: [
    DialogService
  ],
  bootstrap: [ AppComponent ]
})
export class AppModule {
  // Diagnostic only: inspect router configuration
  constructor(router: Router) {
    console.log('Routes: ', JSON.stringify(router.config, undefined, 2))
  }
}
import { NgModule }             from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

import { ComposeMessageComponent }  from './compose-message.component';
import { PageNotFoundComponent }    from './not-found.component';

import { CanDeactivateGuard }       from './can-deactivate-guard.service';
import { AuthGuard }                from './auth-guard.service';
import { SelectivePreloadingStrategy } from './selective-preloading-strategy';

const appRoutes: Routes = [
  {
    path: 'compose',
    component: ComposeMessageComponent,
    outlet: 'popup'
  },
  {
    path: 'admin',
    loadChildren: 'app/admin/admin.module#AdminModule',
    canLoad: [AuthGuard]
  },
  {
    path: 'crisis-center',
    loadChildren: 'app/crisis-center/crisis-center.module#CrisisCenterModule',
    data: { preload: true }
  },
  { path: '',   redirectTo: '/heroes', pathMatch: 'full' },
  { path: '**', component: PageNotFoundComponent }
];

@NgModule({
  imports: [
    RouterModule.forRoot(
      appRoutes,
      { preloadingStrategy: SelectivePreloadingStrategy }
    )
  ],
  exports: [
    RouterModule
  ],
  providers: [
    CanDeactivateGuard,
    SelectivePreloadingStrategy
  ]
})
export class AppRoutingModule { }
import { Component } from '@angular/core';

@Component({
  selector: 'my-app',
  template: `
    <h1 class="title">Angular Router</h1>
    <nav>
      <a routerLink="/crisis-center" routerLinkActive="active">Crisis Center</a>
      <a routerLink="/heroes" routerLinkActive="active">Heroes</a>
      <a routerLink="/admin" routerLinkActive="active">Admin</a>
      <a routerLink="/login" routerLinkActive="active">Login</a>
      <a [routerLink]="[{ outlets: { popup: ['compose'] } }]">Contact</a>
    </nav>
    <router-outlet></router-outlet>
    <router-outlet name="popup"></router-outlet>
  `
})
export class AppComponent {
}

Anatomy

of a React App

React

App.js

Presentational

Container

Provider

Router

Component Legend

react-router

react-redux

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './index.css';

ReactDOM.render(
  <App />,
  document.getElementById('root')
)
import React from 'react'
import {
  BrowserRouter as Router,
  Route,
  Link
} from 'react-router-dom'

import { Home } from '../components/home'
import { About } from '../components/about'
import { Topics } from '../components/topics'

const App = () => (
  <Router>
    <div>
      <ul>
        <li><Link to="/">Home</Link></li>
        <li><Link to="/about">About</Link></li>
        <li><Link to="/topics">Topics</Link></li>
      </ul>

      <hr/>

      <Route exact path="/" component={Home}/>
      <Route path="/about" component={About}/>
      <Route path="/topics" component={Topics}/>
    </div>
  </Router>
)

export default App

Tooling

  • CLI
  • Transpilers
  • Code Editors

create-react-app

npm install -g create-react-app

create-react-app my-app
cd my-app/
npm start

ng

npm install -g @angular/cli

ng new my-dream-app

cd my-dream-app

ng serve

ng

ng generate component my-new-component
ng g component my-new-component # using the alias

# components support relative path generation
# if in the directory src/app/feature/ and you run
ng g component new-cmp
# your component will be generated in src/app/feature/new-cmp
# but if you were to run
ng g component ../newer-cmp
# your component will be generated in src/app/newer-cmp

Transpilers

  • Converts ES2015+ code to ES5 or lower
  • Babel
  • TypeScript

Code Editors

  • Sublime Text
  • Atom
  • Visual Studio Code
  • WebStorm

Tools

Support

Contributors

  • Google
  • Microsoft
  • Facebook

Docs & Tutorials

Licensing

  • Facebook - BSD
  • Google - MIT

Community

  • Confs
  • Twitter
  • GitHub
  • Meetups

Learning Resources

Summary

  • Purpose
  • Contexts
  • Features
  • Code architecture
  • Tooling
  • Support

Best Feature of Angular 4

Angular vs React

So, which one is better?

      Slides

TheJavaScriptPromise.com

    Follow

@duluca

Resources

{{under construction}}

Angular vs React

By Doguhan Uluca

Angular vs React

What are the similarities and differences between Angular and React? Why and when should you use one versus the other?

  • 3,677