An out-of-the-box comparison
Doguhan Uluca
AdWords
2016Q4 Revenue - $22.4 billion
2016 - 1.86 billion users
What problems do they address?
What are the appropriate use cases?
Shadow
DOM
vs
Virtual
DOM
Lack of Native F12 Dev Tools Support
😢
😞
scoping
performance
Read MVVM vs MVC on DevPro here. (10/2013)
Just Don't Do It
✔
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!')
)
}
}
... is a predictable state container
... programming with asynchronous data streams
Event Source
Event Handler
user clicks
window.alert('Are you sure?')
onClick='confirmSave()'
updated data
fetchDetails()
updateCache()
showToastMessage()
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
}
Event Source
mouse clicks
filter(x >= 2)
throttle(250ms)
map(list.length)
<li *ng-for="i in list | async">
window.alert('Are you sure?')
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!')
})
Presentation
API
Business
Persistence
Best Practices
IDE
Patterns
Libraries
View
View Model
Controller / Router
Services ($http, redux, logic)
Best Practices
IDE
Patterns
Libraries
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 {
}
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
npm install -g create-react-app
create-react-app my-app
cd my-app/
npm start
npm install -g @angular/cli
ng new my-dream-app
cd my-dream-app
ng serve
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
So, which one is better?
   Slides
  Follow