Choosing the right Frontend Javascript Framework
React
Polymer
Angular
Ember
Vue
Aurelia
The Why
- There are a lot!
- Different problems, different solutions
- Complexity Matters
- No silver bullet (yet?!)
jQuery
(Cue Gasp)
Not Actually The Devil
A push for speed, reduction in bloat!
$.get('http://myResource/api/getThings')
.then(() => {})
.catch(console.error);
3.0 is A+ Promise Standard Compliant
Offers reasonable slim-version, plus pick your-own modules variable
Feel free to ignore this screenshot of patchnotes
Super useful for lightweight work
$(async () => {
const starterData = await $.get('a/resource');
$('#app .dataContainer').text(starterData);
});
Animation Improvements
"jQuery 3.0, if the browser supports it, uses requestAnimationFrame API to perform animations. The API uses GPU to perform animations. This method is powerful because it’s faster and smoother while performing animations, and on mobile devices, it’s a battery saver."- jQuery 3.0 Release Notes
Custom Selectors Improvement
"jQuery offers some custom selectors (like :visible and :hidden) that apparently look like a CSS selector, but are resolved by jQuery selector engine.
The developers found that with these selectors, some extra work can be skipped if used multiple times in the same document. After they optimized it in jQuery 3, the result is now ~17 times faster." - jQuery 3.0 Release Notes
Overall?
Has a place. Just a small and niche one.
Knockout.js
2-Way Data Binding Galore
Still Relevant?
Oldie but goodie
Simple API
<div data-bind="text: potato" />
ko.applyBindings({
potato: 'I am a binding!'
});
Easy Template Helpers
<div data-bind="foreach: things">
<span data-bind="if: showThing && $data % 2 === 0">
<div data-bind="text: 'Thing!: ' + $data" />
</span>
</div>
ko.applyBindings({
showThing: true,
things: ko.observableArray([1,2,3,4])
});
Reasonable Complexity Scale
var viewModelOne = {
some: ko.observable('data')
};
var viewModelTwo = {
some: ko.observable('other data')
};
window.addEventListener('hashchange', () => {
ko.cleanNode(window);
if(location.hash === '1')
ko.applyBindings(viewModelOne);
else if(location.hash === '2')
ko.applyBindings(viewModelTwo);
});
IE6 Compatible
:/
Unopinionated
var viewModelOne = function() {
this.core = 'core';
};
var viewModelTwo = {
core: 'core'
};
ko.applyBindings(
new viewModelOne()
);
ko.applyBindings(
viewModelTwo
);
2-Way Binding
<input
type="text"
data-bind="textInput: username" />
var viewModel = {
username: ko.observable('')
};
ko.applyBindings(viewModel);
Overall?
Useful for small -> medium projects that require older browser support
Not a framework for ambitious web applications
Backbone.js
Irrelevant?
Old Framework is Old
Template Syntax is 'unique'
var TodoView = Backbone.View.extend({
template: _.template($('#item-template').html())
});
Clunky API
var Books = Backbone.Collection.extend({
url: '/books'
});
Designed around CRUD
Older style is more rigid
var object = {};
_.extend(object, Backbone.Events);
object.on("alert", function(msg) {
alert("Triggered " + msg);
});
object.trigger("alert", "an event");
You leverage and extend Backbone objects
Does not abstract intuitively
Overall?
Irrelevant, clunky, and outta date
Angular 1.x
Overall?
No
Angular2
The Best Thing Since Sliced Bread!
Not Really
They're keeping the Angular 1.x branding, and angularjs.com still takes you to the 1.x site.
Typescript!
class Greeter {
greeting: string;
constructor (message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
Angular2 Components
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
template: `
<h1>{{title}}</h1>
<h2>My favorite hero is: {{myHero}}</h2>
`
})
export class AppComponent {
title = 'Tour of Heroes';
myHero = 'Windstorm';
}
Angular CLI
ng serve --host 0.0.0.0 --port 4201
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
Why OOP and DI?
public description = 'DI';
constructor(public engine: Engine, public tires: Tires) { }
public engine: Engine;
public tires: Tires;
public description = 'No DI';
constructor() {
this.engine = new Engine();
this.tires = new Tires();
}
// Simple car with 4 cylinders and Flintstone tires.
let car = new Car(new Engine(), new Tires());
Templates
<p>{{myVar}}</p> //Good!
<button (click)="deleteHero()">Delete hero</button> //What?
//Weird
<button (click)="onSave($event)">Save</button>
<button *ngFor="let hero of heroes" (click)="deleteHero(hero)">{{hero.name}}</button>
<form #heroForm (ngSubmit)="onSubmit(heroForm)"> ... </form>
//Please stop
<!-- Bind button disabled state to `isUnchanged` property -->
<button [disabled]="isUnchanged">Save</button>
//No why
<button [attr.aria-label]="actionName">{{actionName}} with Aria</button>
Two-Way Binding?
//Component.ts
import { Component, EventEmitter, Input, Output } from '@angular/core';
@Component({
selector: 'my-sizer',
template: `
<div>
<button (click)="dec()" title="smaller">-</button>
<button (click)="inc()" title="bigger">+</button>
<label [style.font-size.px]="size">FontSize: {{size}}px</label>
</div>`
})
export class SizerComponent {
@Input() size: number | string;
@Output() sizeChange = new EventEmitter<number>();
dec() { this.resize(-1); }
inc() { this.resize(+1); }
resize(delta: number) {
this.size = Math.min(40, Math.max(8, +this.size + delta));
this.sizeChange.emit(this.size);
}
}
//In Use
<my-sizer [(size)]="fontSizePx"></my-sizer> //Note the [()] syntax for 2-way binding
<div [style.font-size.px]="fontSizePx">Resizable Text</div>
Overall?
- Strong community and corporate backing
- Patterns outside the current norm
- Angular
3?4?
Polymer 2.0
Drinking the ES6+ Koolaid
Welcome to `class`
- Leans very heavily on ES6 classes
- Have to manually declare the <dom-element />
- Based on native web-component spec
DIY like a box of legos
// Define the class for a new element called custom-element
class CustomElement extends Polymer.Element {
static get is() { return "custom-element"; }
constructor() {
super();
this.textContent = "I'm a custom-element.";
}
}
// Register the new element with the browser
customElements.define(CustomElement.is, CustomElement);
Create your own Shadow-DOM!
<dom-module id="dom-element">
<template>
<p>I'm a DOM element. This is my local DOM!</p>
</template>
<script>
class DomElement extends Polymer.Element {
static get is() { return "dom-element"; }
}
customElements.define(DomElement.is, DomElement);
</script>
</dom-module>
Now I'm just Copy and Pasting
<dom-module id="picture-frame">
<template>
<!-- scoped CSS for this element -->
<style>
div {
display: inline-block;
background-color: #ccc;
border-radius: 8px;
padding: 4px;
}
</style>
<div>
<!-- any children are rendered here -->
<slot></slot>
</div>
</template>
<script>
class PictureFrame extends Polymer.Element {
static get is() { return "picture-frame"; }
}
customElements.define(PictureFrame.is, PictureFrame);
</script>
</dom-module>
I heard you like JSX...
Mustache-like syntax for variables in templates
<template>
<!-- bind to the "owner" property -->
This is <b>{{owner}}</b>'s name-tag element.
</template>
2-Way Binding... and more <template> tags
<link rel="import" href="https://polygit.org/polymer+2.0.0-rc.2/iron-input+polymerelements+:2.0-preview/shadycss+webcomponents+1.0.0-rc.2/components/iron-input/iron-input.html">
<template>
<iron-input bind-value="{{owner}}">
<input is="iron-input" placeholder="Your name here...">
</iron-input>
</template>
You have to import iron-input :/
Building the App
- Has a vulcanizer, which is okay
- HTML imports are ugly now, but will be better with HTTP/2
- Vulcanizer will multi-import things, doesn't know how to cleverly resolve same dependencies
Overall?
Avoid for now
Ember.js
Hamsters Everywhere
Like an overbearing family member
Ember CLI
npm install -g ember-cli@2.12
npm install -g bower
brew install watchman (OSX Only)
npm install -g phantomjs-prebuilt
Then...
ember new my-new-app
cd my-new-app
ember server
Hyper Opinionated
- Forced, effective, abstractions
- Ember-way or the high-way
- Easily maintainable long term
- Easy to bring new Ember devs
Handlebars
<p>
Hello,
<strong> {{firstName}} {{lastName}}</strong>!
</p>
import Ember from 'ember';
export default Ember.Controller.extend({
firstName: 'Trek',
lastName: 'Glowacki'
});
Ember Store
//route.js
export default Ember.Route.extend({
model(params) {
return this.store.findRecord('user', params.id);
}
});
//model.js
import DS from 'ember-data';
export default DS.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string'),
articles: DS.hasMany(),
fullName: Ember.computed('firstName', 'lastName', function() {
return `${this.get('firstName')} ${this.get('lastName')}`;
}),
});
Glimmer Rendering Engine
- Reasonable fast on initial render
- Ultra-fast on re-render
- Isolated, can be used like React
Overall?
- Powerful, Robust, but Very Opinionated
- High Learning Curve
- "Fattest" framework next to Angular 2
- Hamsters
Vue.js
That thing you've been hearing about!
Faster Than React!!1!
0.02 seconds faster than React
Quick to get going
<script src="https://unpkg.com/vue"></script>
<div id="app">
{{ message }}
</div>
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
Templates are okay
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
<div v-bind:id="'list-' + id"></div>
- Single expression support
- Mustache
- v-*
Two-Way Data Binding
<div id="app">
<p>{{ message }}</p>
<input v-model="message">
</div>
var app6 = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
});
Optional
Abstracting?
var data = { counter: 0 }
Vue.component('simple-counter', {
template: '<button v-on:click="counter += 1">{{ counter }}</button>',
// data is technically a function, so Vue won't
// complain, but we return the same object
// reference for each component instance
data: function () {
return data
}
})
new Vue({
el: '#example-2'
})
Overall?
Kinda meh.
CLI?
Young.
Aurelia
What?
Oh yeah, we had a talk on it!
Components and Views!
export class Todo {
constructor(description) {
this.description = description;
this.done = false;
}
}
export class App {
constructor() {
this.heading = "Todos";
this.todos = [];
this.todoDescription = '';
}
addTodo() {
if (this.todoDescription) {
this.todos.push(new Todo(this.todoDescription));
this.todoDescription = '';
}
}
removeTodo(todo) {
let index = this.todos.indexOf(todo);
if (index !== -1) {
this.todos.splice(index, 1);
}
}
}
<template>
<h1>${heading}</h1>
<form submit.trigger="addTodo()">
<input type="text" value.bind="todoDescription">
<button type="submit">Add Todo</button>
</form>
<ul>
<li repeat.for="todo of todos">
<input type="checkbox" checked.bind="todo.done">
<span>
${todo.description}
</span>
<button click.trigger="removeTodo(todo)">
Remove
</button>
</li>
</ul>
</template>
-
Aurelia-specific dom event bindings
- ${variable} in templates :/
- Unidirectional Data Flow
Website Panders Paid Resources
-
DOM, not Shadow-DOM
-
Web-Components+
-
No weird licenses
I stole this slide
Overall?
Kinda Young
Keep an eye out
React
If you're not using React, you're not living
I swear I'm not a shill!
Quickstart?
Just install...
- Webpack
- Babel
- All necessary presets
- Transformers
- Plugins
JSX
class App extends Component {
render() {
return (
<div>
<MyComponent />
</div>
);
}
}
Component all the things
Unidirectional Data Flow
- All data stored in a single object
- Create updated state
- Send state to React
- React updates view & state
No two-way binding at all!
Can't share state between components
Solutions?
- Flux
- Redux
- MobX
- Cerebral
- GborthagubX
Beloved Overlord Facebook
"Weird License"
Javascript!
class Core extends Component {
render() {
return (
<div className='coreContainer'>
{this.props.children}
{
[1,2,3,4].map(i => <div key={i}>Counter: {i}</div>)
}
</div>
);
}
}
export default Core;
Overall?
- Popular and Powerful
- Limited base tooling
- Very Javascript-y
Inferno
It's React
But Faster!
- Faster than react, omg
- React knowledge transfers
- No weird Facebook licensing
- Simplified API compared with react
- Inbuilt state-managers like Redux and MobX
- Built-in Isomorphism
Honorable Mentions
- Meteor.js
- hyperapp
- Preact
Contact
/krishnaglick on Github
@krishnaglick on Twitter
@prometheus on ODevs Slack
Choosing the right Frontend Javascript Framework
By Krishna Glick