AngularJS vs. Ember.js vs. Backbone.js

Post on 11-May-2015

1,360 views 2 download

Tags:

description

The world of JavaScript client-side frameworks is overflowing with contenders vying for the crown, but which one do you choose for your next project? Which one has what it takes? In this talk we’ll look at the “Big 3”, AngularJS, Ember.js, and Backbone.js. We’ll compare them head to head, toe to toe. We’ll look at the pros and cons of each one. How do they handle form bindings? Talking to APIs? Code organization? Routing? Etc? Who’ll come out victorious in this battle of the JavaScript frameworks, or will we all just come out bloodied and bruised on the other side? Guess we’ll find out!

transcript

@markbates

hire me* (for rent, not sale)

KODIO13www.metacasts.tv

AngularJS vs. Ember vs. Backbone.js

Knockout.js

philosophies

Backbone.js

“minimal set of data-structure and view primitives for building web application with JavaScript”

Backbone.js

do what you want, however you want to do it

(we won’t tell anyone)

Ember

“framework for creating ambitious web applications”

Ember

convention over configuration

AngularJS

“Toolset for building the framework most suited to your application development”

AngularJS

plain old JavaScript

Round 1

weight

“production” versions (minified) w/ required dependencies

AngularJS Ember Backbone.js

base 81kb 235kb 6.4kb

templating language built-in73kb

(handlebars) ??

data adapter built-in210kb

(ember-data)32kb

(jQuery)

support N/A32kb

(jQuery)

17kb (json2.js)

4.9kb (underscore.js)

81kb 550kb 60.3kb

Round 2

“basic” models

Backbone.js

class  App.Beer  extends  Backbone.Model      class  App.Beers  extends  Backbone.Collection  !

 model:  Beer

Ember

App.Beer  =  DS.Model.extend      title:  DS.attr("string")      abv:  DS.attr("number")      country_id:  DS.attr("number")      brewery_id:  DS.attr("number")      brewery:  DS.belongsTo("App.Brewery")      country:  DS.belongsTo("App.Country")

AngularJS

App.Beer  =  {}

“remote” models

Backbone.jsclass  App.Beer  extends  Backbone.Model      urlRoot:  "/api/v1/beers"        class  App.Beers  extends  Backbone.Collection          url:  -­‐>          if  @brewery_id?              return  "/api/v1/breweries/#{@brewery_id}/beers"          else              return  "/api/v1/beers"          model:  Beer

Ember

App.Beer  =  DS.Model.extend      title:  DS.attr("string")      abv:  DS.attr("number")      country_id:  DS.attr("number")      brewery_id:  DS.attr("number")      brewery:  DS.belongsTo("App.Brewery")      country:  DS.belongsTo("App.Country")

EmberDS.RESTAdapter.reopen      namespace:  'api/v1'      App.Store  =  DS.Store.extend      revision:  11      adapter:  DS.RESTAdapter.create()

AngularJS

App.factory  "Beer",  ($resource)  -­‐>      return  $resource  "/api/v1/beers/:id",                                        {id:  "@id"},                                        {update:  {method:  "PUT"}}

Round 3

routers

Backbone.js@Router  =  Backbone.Router.extend          initialize:  -­‐>          @countries  =  new  Countries()          routes:          "breweries/:brewery_id":  "brewery"          "breweries/:brewery_id/edit":  "breweryEdit"          brewery:  (brewery_id)  -­‐>          @changeView(new  BreweryView(collection:  @countries,  model:  new  Brewery(id:  brewery_id)))          breweryEdit:  (brewery_id)  -­‐>          @changeView(new  BreweryEditView(collection:  @countries,  model:  new  Brewery(id:  brewery_id)))          changeView:  (view)  =>          @currentView?.remove()          @currentView  =  view          $("#outlet").html(@currentView.el)

Ember

App.Router.map  -­‐>      @resource  "brewery",  {path:  "brewery/:brewery_id"}

Ember

App.BreweryRoute  =  Ember.Route.extend      model:  (params)-­‐>          App.Brewery.find(params.brewery_id)

AngularJSApp.config  ($routeProvider)  -­‐>          $routeProvider          .when("/breweries/:id",  {              templateUrl:  "/assets/brewery.html",              controller:  "BreweryController"          })          .when("/breweries/:id/edit",  {              templateUrl:  "/assets/edit_brewery.html",              controller:  "EditBreweryController"          })

Round 4

controllers/views

Backbone.jsclass  @BreweryEditView  extends  Backbone.View          template:  "brewery_edit"          events:          "click  #save-­‐button":  "saveClicked"          "keypress  #brewery-­‐title":  "titleEdited"          initialize:  -­‐>          super          @countriesView  =  new  CountriesView(collection:  @collection)          @$el.html(@countriesView.el)          @model.on  "change",  @render          @model.fetch()          render:  =>          @$("#country-­‐outlet").html(@renderTemplate())          return  @

   saveClicked:  (e)  =>          e?.preventDefault()          attrs  =              title:  @$("#brewery-­‐title").val()              synonyms:  @$("#brewery-­‐synonyms").val()              address:  @$("#brewery-­‐address").val()          @model.save  attrs,              success:  (model,  response,  options)  =>                  App.navigate("/breweries/#{@model.id}",  trigger:  true)              error:  (model,  xhr,  options)  -­‐>                  errors  =  []                  for  key,  value  of  xhr.responseJSON.errors                      errors.push  "#{key}:  #{value.join(",  ")}"                  alert  errors.join("\n")          titleEdited:  (e)  =>          title  =  @$("#brewery-­‐title").val()          @$("h2").text(title)  !    #  further  code  omitted

Ember

App.BreweryController  =  Ember.ObjectController.extend        save:  -­‐>          @store.commit()          #  further  code  omitted

AngularJS

@EditBreweryController  =  ($scope,  $routeParams,  $location,  Brewery)  -­‐>          $scope.brewery  =  Brewery.get(id:  $routeParams.id)          $scope.save  =  -­‐>          success  =  -­‐>              $location.path("/breweries/#{$routeParams.id}")              $scope.errors  =  null          failure  =  (object)-­‐>              $scope.errors  =  object.data.errors          $scope.brewery.$update  {},  success,  failure

Round 5

templates

Backbone.js<h2><%=  @model.displayName()  %></h2>      <form>          <div  class="control-­‐group">          <label  class="control-­‐label"  for="title">Title</label>          <div  class="controls">              <input  type='text'  class='input-­‐xxlarge'  value='<%=  @model.get("title")  %>'id='brewery-­‐title'>          </div>      </div>          <div  class="control-­‐group">          <label  class="control-­‐label"  for="synonyms">Synonyms</label>          <div  class="controls">              <input  type='text'  class='input-­‐xxlarge'  value='<%=  @model.get("synonyms")  %>'id='brewery-­‐synonyms'>          </div>      </div>          <div  class="control-­‐group">          <label  class="control-­‐label"  for="address">Address</label>          <div  class="controls">              <textarea  class='input-­‐xxlarge'  id='brewery-­‐address'><%=  @model.get("address")  %></textarea>          </div>      </div>          <button  class='btn  btn-­‐primary'  id='save-­‐button'>Save</button>      <a  href="/breweries/<%=  @model.id  %>"  class='btn'>Cancel</a>      </form>

Backbone.js<h2><%=  @model.displayName()  %></h2>      <form>          <div  class="control-­‐group">          <label  class="control-­‐label"  for="title">Title</label>          <div  class="controls">  

           <input  type='text'  class='input-­‐xxlarge'  value='<%=  @model.get("title")  %>'id='brewery-­‐title'>          </div>      </div>          <div  class="control-­‐group">          <label  class="control-­‐label"  for="synonyms">Synonyms</label>          <div  class="controls">              <input  type='text'  class='input-­‐xxlarge'  value='<%=  @model.get("synonyms")  %>'id='brewery-­‐synonyms'>          </div>      </div>          <div  class="control-­‐group">          <label  class="control-­‐label"  for="address">Address</label>          <div  class="controls">              <textarea  class='input-­‐xxlarge'  id='brewery-­‐address'><%=  @model.get("address")  %></textarea>          </div>      </div>          <button  class='btn  btn-­‐primary'  id='save-­‐button'>Save</button>      <a  href="/breweries/<%=  @model.id  %>"  class='btn'>Cancel</a>      </form>

           <input  type='text'  class='input-­‐xxlarge'  value='<%=  @model.get("title")  %>'id='brewery-­‐title'>

Backbone.js<h2><%=  @model.displayName()  %></h2>      <form>          <div  class="control-­‐group">          <label  class="control-­‐label"  for="title">Title</label>          <div  class="controls">              <input  type='text'  class='input-­‐xxlarge'  value='<%=  @model.get("title")  %>'id='brewery-­‐title'>          </div>      </div>          <div  class="control-­‐group">          <label  class="control-­‐label"  for="synonyms">Synonyms</label>          <div  class="controls">              <input  type='text'  class='input-­‐xxlarge'  value='<%=  @model.get("synonyms")  %>'id='brewery-­‐synonyms'>          </div>      </div>          <div  class="control-­‐group">          <label  class="control-­‐label"  for="address">Address</label>          <div  class="controls">  

           <textarea  class='input-­‐xxlarge'  id='brewery-­‐address'><%=  @model.get("address")  %></textarea>          </div>      </div>          <button  class='btn  btn-­‐primary'  id='save-­‐button'>Save</button>      <a  href="/breweries/<%=  @model.id  %>"  class='btn'>Cancel</a>      </form>

           <textarea  class='input-­‐xxlarge'  id='brewery-­‐address'><%=  @model.get("address")  %></textarea>

<div  class='span12'>      <h2>{{displayName}}</h2>      <h3>          {{cityState}}          {{#linkTo  "country"  country}}              {{country.title}}          {{/linkTo}}      </h3>      {{#if  isEditing}}          <form>                  <div  class="control-­‐group">                  <label  class="control-­‐label"  for="title">Title</label>                  <div  class="controls">                      {{view  Ember.TextField  valueBinding="title"  class='input-­‐xxlarge'}}                  </div>              </div>                  <div  class="control-­‐group">                  <label  class="control-­‐label"  for="synonyms">Synonyms</label>                  <div  class="controls">                      {{view  Ember.TextField  valueBinding="synonyms"  class='input-­‐xxlarge'}}                  </div>              </div>                  <div  class="control-­‐group">                  <label  class="control-­‐label"  for="synonyms">Synonyms</label>                  <div  class="controls">                      {{view  Ember.TextArea  valueBinding="address"  class='input-­‐xxlarge'}}                  </div>              </div>                  <button  class='btn  btn-­‐primary'  {{action  "save"}}>Save</button>              </form>      {{  else  }}          {{  partial  "brewery/show"  }}      {{/if}}  </div>

Ember

<div  class='span12'>      <h2>{{displayName}}</h2>      <h3>          {{cityState}}          {{#linkTo  "country"  country}}              {{country.title}}          {{/linkTo}}      </h3>      {{#if  isEditing}}          <form>                  <div  class="control-­‐group">                  <label  class="control-­‐label"  for="title">Title</label>                  <div  class="controls">  

                   {{view  Ember.TextField  valueBinding="title"  class='input-­‐xxlarge'}}                  </div>              </div>                  <div  class="control-­‐group">                  <label  class="control-­‐label"  for="synonyms">Synonyms</label>                  <div  class="controls">                      {{view  Ember.TextField  valueBinding="synonyms"  class='input-­‐xxlarge'}}                  </div>              </div>                  <div  class="control-­‐group">                  <label  class="control-­‐label"  for="synonyms">Synonyms</label>                  <div  class="controls">                      {{view  Ember.TextArea  valueBinding="address"  class='input-­‐xxlarge'}}                  </div>              </div>                  <button  class='btn  btn-­‐primary'  {{action  "save"}}>Save</button>              </form>      {{  else  }}          {{  partial  "brewery/show"  }}      {{/if}}  </div>

Ember

                   {{view  Ember.TextField  valueBinding="title"  class='input-­‐xxlarge'}}

<div  class='span12'>      <h2>{{displayName}}</h2>      <h3>          {{cityState}}          {{#linkTo  "country"  country}}              {{country.title}}          {{/linkTo}}      </h3>      {{#if  isEditing}}          <form>                  <div  class="control-­‐group">                  <label  class="control-­‐label"  for="title">Title</label>                  <div  class="controls">                      {{view  Ember.TextField  valueBinding="title"  class='input-­‐xxlarge'}}                  </div>              </div>                  <div  class="control-­‐group">                  <label  class="control-­‐label"  for="synonyms">Synonyms</label>                  <div  class="controls">                      {{view  Ember.TextField  valueBinding="synonyms"  class='input-­‐xxlarge'}}                  </div>              </div>                  <div  class="control-­‐group">                  <label  class="control-­‐label"  for="synonyms">Synonyms</label>                  <div  class="controls">                      {{view  Ember.TextArea  valueBinding="address"  class='input-­‐xxlarge'}}                  </div>              </div>      

           <button  class='btn  btn-­‐primary'  {{action  "save"}}>Save</button>              </form>      {{  else  }}          {{  partial  "brewery/show"  }}      {{/if}}  </div>

Ember

           <button  class='btn  btn-­‐primary'  {{action  "save"}}>Save</button>

<form>      <h3>{{brewery.title}}</h3>      <div  ng-­‐include='"/assets/_errors.html"'></div>      <div  class="control-­‐group">          <label  class="control-­‐label"  for="title">Title</label>          <div  class="controls">              <input  type='text'  class='input-­‐xxlarge'  ng-­‐model='brewery.title'>          </div>      </div>          <div  class="control-­‐group">          <label  class="control-­‐label"  for="synonyms">Synonyms</label>          <div  class="controls">              <input  type='text'  class='input-­‐xxlarge'  ng-­‐model='brewery.synonyms'>          </div>      </div>          <div  class="control-­‐group">          <label  class="control-­‐label"  for="address">Address</label>          <div  class="controls">              <textarea  class='input-­‐xxlarge'  ng-­‐model='brewery.address'></textarea>          </div>      </div>          <button  class='btn  btn-­‐primary'  ng-­‐click='save()'>Save</button>      </form>

AngularJS

AngularJS<form>      <h3>{{brewery.title}}</h3>      <div  ng-­‐include='"/assets/_errors.html"'></div>      <div  class="control-­‐group">          <label  class="control-­‐label"  for="title">Title</label>          <div  class="controls">              <input  type='text'  class='input-­‐xxlarge'  ng-­‐model='brewery.title'>          </div>      </div>          <div  class="control-­‐group">          <label  class="control-­‐label"  for="synonyms">Synonyms</label>          <div  class="controls">              <input  type='text'  class='input-­‐xxlarge'  ng-­‐model='brewery.synonyms'>          </div>      </div>          <div  class="control-­‐group">          <label  class="control-­‐label"  for="address">Address</label>          <div  class="controls">              <textarea  class='input-­‐xxlarge'  ng-­‐model='brewery.address'></textarea>          </div>      </div>          <button  class='btn  btn-­‐primary'  ng-­‐click='save()'>Save</button>      </form>

           <input  type='text'  class='input-­‐xxlarge'  ng-­‐model='brewery.title'>

<form>      <h3>{{brewery.title}}</h3>      <div  ng-­‐include='"/assets/_errors.html"'></div>      <div  class="control-­‐group">          <label  class="control-­‐label"  for="title">Title</label>          <div  class="controls">              <input  type='text'  class='input-­‐xxlarge'  ng-­‐model='brewery.title'>          </div>      </div>          <div  class="control-­‐group">          <label  class="control-­‐label"  for="synonyms">Synonyms</label>          <div  class="controls">              <input  type='text'  class='input-­‐xxlarge'  ng-­‐model='brewery.synonyms'>          </div>      </div>          <div  class="control-­‐group">          <label  class="control-­‐label"  for="address">Address</label>          <div  class="controls">              <textarea  class='input-­‐xxlarge'  ng-­‐model='brewery.address'></textarea>          </div>      </div>          <button  class='btn  btn-­‐primary'  ng-­‐click='save()'>Save</button>      </form>

AngularJS

   <button  class='btn  btn-­‐primary'  ng-­‐click='save()'>Save</button>

Round 6

pros/cons

Backbone.js

• Too simple

• Not opinionated enough

• “Memory” management

• Unstructured

• Spaghetti code

• Lightweight

• Not opinionated

• Simple

• Easy to read source

• “widget” development

Pros Cons

Ember• Too complex

• Overly opinionated

• Heavyweight

• ember-data - not production ready

• API always in flux

• Buggy

• Little to no mind-share outside of Rails

• Difficult to read source code

• Structured

• Highly opinionated

• “less” code

• “large” apps

Pros Cons

AngularJS

• Difficult to read source code

• jQuery plugins require custom directives

• Large apps requiring self-imposed structure

• Lightly structured

• Lightly opinionated

• “less” code

• Plain JavaScript

• Simple/Powerful

• Easy to test

• Lightweight

• small, medium, or large apps

Pros Cons

Summary

think before you choose

don’t cargo cult

don’t use Backbone.js!

play with them all

watch the metacasts.tv episodes on them ;)

@markbates http://www.metacasts.tv