SlideShare a Scribd company logo
1 of 167
Download to read offline
@markbates
@markbates
FLUENT2014
www.metacasts.tv
www.angularmasterclass.com
Angular
Fundamentals
Enough Angular
to be Dangerous!
What Will Cover?
What Will Cover?*
*hopefully
• Controllers!
• ngRoute!
• Templates!
• ngResource!
• Directives!
• Filters!
• Scope!
• Testing!
• Code Organization!
• Best Practices
Part 1
• Features/Why
Angular?!
• Getting Started/Setting
Up!
• Directives, Filters, and
Data Binding!
• Controllers,
Templates, and Scope!
• Modules!
• Routing!
• Custom Directives and
Event Handling!
• Testing
Part 2
We Code!
Part 1
Features
Features
• Plain JavaScript
• Data Binding
• Routing/PushState
• Testing
• Templates/Directives/
Controllers
• Modular
• Dependency Injection
• jqLite
• Lightweight
Why Angular?
Philosophies
Backbone.js
“minimal set of data-structure and view primitives 	

for building web application with JavaScript”
Ember
“framework for creating ambitious web applications”
AngularJS
“Toolset for building the framework
most suited to your application
development”
Weight
“production” versions (minified)	

w/ required dependencies
AngularJS Ember Backbone.js
base 109kb 264kb 6.5kb
templating
language
built-in
90kb	

(handlebars)
??
data adapter built-in
75kb	

(ember-data)
84kb	

(jQuery)
support N/A
84kb	

(jQuery)
17kb	

(json2.js)	

5.0kb	

(underscore.js
)
109kb 513kb 112.5kb
Mindshare
Google
AngularJS Backbone.js Ember
Watchers 2,155 1,442 824
Stars 21,408 17,291 9,570
Forks 6,670 3,783 2,044
Github
“Basic” Models
Backbone.jsclass	
  App.Beer	
  extends	
  
Backbone.Model	
  
	
  	
  
class	
  App.Beers	
  extends	
  
Backbone.Collection	
  
!
	
  model:	
  Beer
EmberApp.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
EmberApp.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")
Ember
DS.RESTAdapter.reopen	
  
	
  	
  namespace:	
  'api/v1'	
  
	
  	
  
App.Store	
  =	
  DS.Store.extend	
  
	
  	
  revision:	
  14	
  
	
  	
  adapter:	
  DS.RESTAdapter.create()
AngularJS
App.factory	
  "Beer",	
  ($resource)	
  -­‐>	
  
	
  	
  return	
  $resource	
  "/api/v1/
beers/:id",	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  {id:	
  "@id"},	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  {update:	
  {method:	
  
"PUT"}}
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"}
EmberApp.BreweryRoute	
  =	
  
Ember.Route.extend	
  
	
  	
  model:	
  (params)-­‐>	
  
	
  	
  	
  	
  
App.Brewery.find(params.brewery_i
d)
AngularJSApp.config	
  ($routeProvider)	
  -­‐>	
  
	
  	
  
	
  	
  $routeProvider	
  
	
  	
  	
  	
  .when("/breweries/:id",	
  {	
  
	
  	
  	
  	
  	
  	
  templateUrl:	
  "/assets/
brewery.html",	
  
	
  	
  	
  	
  	
  	
  controller:	
  "BreweryController"	
  
	
  	
  	
  	
  })	
  
	
  	
  	
  	
  .when("/breweries/:id/edit",	
  {	
  
	
  	
  	
  	
  	
  	
  templateUrl:	
  "/assets/
edit_brewery.html",	
  
	
  	
  	
  	
  	
  	
  controller:	
  
"EditBreweryController"	
  
	
  	
  	
  	
  })
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
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>
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>
<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
<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
<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>
<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
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 (very 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
Getting Started
5 Minute Break
Directives, Filters,
and Data Binding
Directives
<body ng-app>!
<ng-view></ng-view>!
<ul>!
<li ng-repeat='comment in comments'>!
{{comment.body}}!
</li>!
</ul>!
</body>
Directives
<body ng-app>!
<ng-view></ng-view>!
<ul>!
<li ng-repeat='comment in comments'>!
{{comment.body}}!
</li>!
</ul>!
</body>
Directives
<body ng-app>!
<ng-view></ng-view>!
<ul>!
<li ng-repeat='comment in comments'>!
{{comment.body}}!
</li>!
</ul>!
</body>
Directives
<body ng-app>!
<ng-view></ng-view>!
<ul>!
<li ng-repeat='comment in comments'>!
{{comment.body}}!
</li>!
</ul>!
</body>
Directives
<body ng-app>!
<ng-view></ng-view>!
<ul>!
<li ng-repeat='comment in comments'>!
{{comment.body}}!
</li>!
</ul>!
</body>
Demo
Filters
<ul>!
<li ng-repeat='c in comments |
orderBy:"date"'>!
{{c.author | uppercase}}!
</li>!
</ul>
Filters
<ul>!
<li ng-repeat='c in comments |
orderBy:"date"'>!
{{c.author | uppercase}}!
</li>!
</ul>
Filters
<ul>!
<li ng-repeat='c in comments |
orderBy:"date"'>!
{{c.author | uppercase}}!
</li>!
</ul>
<ul>!
<li ng-repeat='c in comments |
orderBy:"date"'>!
{{c.author | uppercase}}!
</li>!
</ul>
Filters
Demo
Controllers,
Templates, and Scope
Controllers, Templates and
Scope
Template
Controllers, Templates and
Scope
Template Controller
Controllers, Templates and
Scope
Template Controller
Controllers, Templates and
Scope
Template Controller
Demo
Modules
Module
angular.module('app', []);
Module
Config
angular.module('app', []);
Module
Config Controller
angular.module('app', []);
Module
Config Controller Factories
angular.module('app', []);
Module
Config Controller Factories Directives
angular.module('app', []);
Module
Config Controller Factories Directives Filters
angular.module('app', []);
Module
Config Controller Factories Directives Filters
Routes
angular.module('app', []);
Demo
Routing
App.config(function($routeProvider, $locationProvider) {!
$locationProvider.html5Mode(true);!
!
$routeProvider.when('/posts', {!
controller: 'PostsIndexController',!
templateUrl: 'posts/index.html'!
})!
.when('/posts/:id',{!
controller: 'PostsShowController',!
templateUrl: 'posts/show.html'!
})!
.otherwise({!
redirectTo: '/posts'!
});!
});
Routing
App.config(function($routeProvider, $locationProvider) {!
$locationProvider.html5Mode(true);!
!
$routeProvider.when('/posts', {!
controller: 'PostsIndexController',!
templateUrl: 'posts/index.html'!
})!
.when('/posts/:id',{!
controller: 'PostsShowController',!
templateUrl: 'posts/show.html'!
})!
.otherwise({!
redirectTo: '/posts'!
});!
});
Routing
App.config(function($routeProvider, $locationProvider) {!
$locationProvider.html5Mode(true);!
!
$routeProvider.when('/posts', {!
controller: 'PostsIndexController',!
templateUrl: 'posts/index.html'!
})!
.when('/posts/:id',{!
controller: 'PostsShowController',!
templateUrl: 'posts/show.html'!
})!
.otherwise({!
redirectTo: '/posts'!
});!
});
Routing
App.config(function($routeProvider, $locationProvider) {!
$locationProvider.html5Mode(true);!
!
$routeProvider.when('/posts', {!
controller: 'PostsIndexController',!
templateUrl: 'posts/index.html'!
})!
.when('/posts/:id',{!
controller: 'PostsShowController',!
templateUrl: 'posts/show.html'!
})!
.otherwise({!
redirectTo: '/posts'!
});!
});
Routing
App.config(function($routeProvider, $locationProvider) {!
$locationProvider.html5Mode(true);!
!
$routeProvider.when('/posts', {!
controller: 'PostsIndexController',!
templateUrl: 'posts/index.html'!
})!
.when('/posts/:id',{!
controller: 'PostsShowController',!
templateUrl: 'posts/show.html'!
})!
.otherwise({!
redirectTo: '/posts'!
});!
});
Routing
App.config(function($routeProvider, $locationProvider) {!
$locationProvider.html5Mode(true);!
!
$routeProvider.when('/posts', {!
controller: 'PostsIndexController',!
templateUrl: 'posts/index.html'!
})!
.when('/posts/:id',{!
controller: 'PostsShowController',!
templateUrl: 'posts/show.html'!
})!
.otherwise({!
redirectTo: '/posts'!
});!
});
Routing
App.config(function($routeProvider, $locationProvider) {!
$locationProvider.html5Mode(true);!
!
$routeProvider.when('/posts', {!
controller: 'PostsIndexController',!
templateUrl: 'posts/index.html'!
})!
.when('/posts/:id',{!
controller: 'PostsShowController',!
templateUrl: 'posts/show.html'!
})!
.otherwise({!
redirectTo: '/posts'!
});!
});
Routing
App.config(function($routeProvider, $locationProvider) {!
$locationProvider.html5Mode(true);!
!
$routeProvider.when('/posts', {!
controller: 'PostsIndexController',!
templateUrl: 'posts/index.html'!
})!
.when('/posts/:id',{!
controller: 'PostsShowController',!
templateUrl: 'posts/show.html'!
})!
.otherwise({!
redirectTo: '/posts'!
});!
});
Routing
App.config(function($routeProvider, $locationProvider) {!
$locationProvider.html5Mode(true);!
!
$routeProvider.when('/posts', {!
controller: 'PostsIndexController',!
templateUrl: 'posts/index.html'!
})!
.when('/posts/:id',{!
controller: 'PostsShowController',!
templateUrl: 'posts/show.html'!
})!
.otherwise({!
redirectTo: '/posts'!
});!
});
Routing
App.config(function($routeProvider, $locationProvider) {!
$locationProvider.html5Mode(true);!
!
$routeProvider.when('/posts', {!
controller: 'PostsIndexController',!
templateUrl: 'posts/index.html'!
})!
.when('/posts/:id',{!
controller: 'PostsShowController',!
templateUrl: 'posts/show.html'!
})!
.otherwise({!
redirectTo: '/posts'!
});!
});
Routing
App.config(function($routeProvider, $locationProvider) {!
$locationProvider.html5Mode(true);!
!
$routeProvider.when('/posts', {!
controller: 'PostsIndexController',!
templateUrl: 'posts/index.html'!
})!
.when('/posts/:id',{!
controller: 'PostsShowController',!
templateUrl: 'posts/show.html'!
})!
.otherwise({!
redirectTo: '/posts'!
});!
});
Routing
Demo
Custom Directives
and Event Handling
App.directive("upCaser", function() {!
return {!
link: function(scope, el, attrs) {!
$(el).find('p').each(function(i, p) {!
p = $(p);!
p.text(p.text().toUpperCase());!
});!
}!
};!
});
Directives
DirectivesApp.directive("upCaser", function() {!
return {!
link: function(scope, el, attrs) {!
$(el).find('p').each(function(i, p) {!
p = $(p);!
p.text(p.text().toUpperCase());!
});!
}!
};!
});
App.directive("upCaser", function() {!
return {!
link: function(scope, el, attrs) {!
$(el).find('p').each(function(i, p) {!
p = $(p);!
p.text(p.text().toUpperCase());!
});!
}!
};!
});
Directives
App.directive("upCaser", function() {!
return {!
link: function(scope, el, attrs) {!
$(el).find('p').each(function(i, p) {!
p = $(p);!
p.text(p.text().toUpperCase());!
});!
}!
};!
});
Directives
App.directive("upCaser", function() {!
return {!
link: function(scope, el, attrs) {!
$(el).find('p').each(function(i, p) {!
p = $(p);!
p.text(p.text().toUpperCase());!
});!
}!
};!
});
Directives
App.directive("upCaser", function() {!
return {!
link: function(scope, el, attrs) {!
$(el).find('p').each(function(i, p) {!
p = $(p);!
p.text(p.text().toUpperCase());!
});!
}!
};!
});
Directives
App.directive("upCaser", function() {!
return {!
link: function(scope, el, attrs) {!
$(el).find('p').each(function(i, p) {!
p = $(p);!
p.text(p.text().toUpperCase());!
});!
}!
};!
});
Directives
App.directive("upCaser", function() {!
return {!
link: function(scope, el, attrs) {!
$(el).find('p').each(function(i, p) {!
p = $(p);!
p.text(p.text().toUpperCase());!
});!
}!
};!
});
Directives
<div up-caser>!
<p>some text</p>!
<p>some other text</p>!
</div>
Directives
Directives
<up-caser>!
<p>some text</p>!
<p>some other text</p>!
</up-caser>
App.directive("upCaser", function() {!
return {!
restrict: 'E',!
link: function(scope, el, attrs) {!
$(el).find('p').each(function(i, p) {!
p = $(p);!
p.text(p.text().toUpperCase());!
});!
}!
};!
});
Directives
Directives
<up-caser>!
<p>some text</p>!
<p>some other text</p>!
</up-caser>
App.directive("upCaser", function() {!
return {!
restrict: 'AEC',!
link: function(scope, el, attrs) {!
$(el).find('p').each(function(i, p) {!
p = $(p);!
p.text(p.text().toUpperCase());!
});!
}!
};!
});
Directives
Demo
Events
Scope
Events
$broadcast
$emit
App.directive("alerter", function() {!
return {!
restrict: 'E',!
link: function(scope, el, attrs) {!
setTimeout(function() {!
scope.$emit("up", "Hello Up!");!
scope.$broadcast("down", "Hello Down!");!
scope.$apply();!
}, 3000);!
}!
};!
});
Events
App.directive("alerter", function() {!
return {!
restrict: 'E',!
link: function(scope, el, attrs) {!
setTimeout(function() {!
scope.$emit("up", "Hello Up!");!
scope.$broadcast("down", "Hello Down!");!
scope.$apply();!
}, 3000);!
}!
};!
});
Events
App.directive("alerter", function() {!
return {!
restrict: 'E',!
link: function(scope, el, attrs) {!
setTimeout(function() {!
scope.$emit("up", "Hello Up!");!
scope.$broadcast("down", "Hello Down!");!
scope.$apply();!
}, 3000);!
}!
};!
});
Events
App.directive("alerter", function() {!
return {!
restrict: 'E',!
link: function(scope, el, attrs) {!
setTimeout(function() {!
scope.$emit("up", "Hello Up!");!
scope.$broadcast("down", "Hello Down!");!
scope.$apply();!
}, 3000);!
}!
};!
});
Events
App.directive("alerter", function() {!
return {!
restrict: 'E',!
link: function(scope, el, attrs) {!
setTimeout(function() {!
scope.$emit("up", "Hello Up!");!
scope.$broadcast("down", "Hello Down!");!
scope.$apply();!
}, 3000);!
}!
};!
});
Events
App.directive("alerter", function() {!
return {!
restrict: 'E',!
link: function(scope, el, attrs) {!
setTimeout(function() {!
scope.$emit("up", "Hello Up!");!
scope.$broadcast("down", "Hello Down!");!
scope.$apply();!
}, 3000);!
}!
};!
});
Events
App.controller('SomeController', function($scope) {!
$scope.$on("up", function(data, message) {!
$scope.up_message = message;!
});!
});
Events
App.controller('SomeController', function($scope) {!
$scope.$on("up", function(data, message) {!
$scope.up_message = message;!
});!
});
Events
App.controller('SomeController', function($scope) {!
$scope.$on("up", function(data, message) {!
$scope.up_message = message;!
});!
});
Events
App.controller('SomeController', function($scope) {!
$scope.$on("up", function(data, message) {!
$scope.up_message = message;!
});!
});
Events
App.controller('SomeController', function($scope) {!
$scope.$on("up", function(data, message) {!
$scope.up_message = message;!
});!
});
Events
App.controller('SomeController', function($scope) {!
$scope.$on("up", function(data, message) {!
$scope.up_message = message;!
});!
});
Events
Demo
App.controller('Main', function($scope) {!
$scope.clicker = function() {!
$scope.pressed = true;!
};!
});
Events
App.directive("alerter", function() {!
return {!
restrict: 'E',!
link: function(scope, el, attrs) {!
scope.$watch('pressed', function() {!
if (scope.pressed) {!
$(el).text('Oi!');!
}!
});!
}!
};!
});
Events
App.directive("alerter", function() {!
return {!
restrict: 'E',!
link: function(scope, el, attrs) {!
scope.$watch('pressed', function() {!
if (scope.pressed) {!
$(el).text('Oi!');!
}!
});!
}!
};!
});
Events
Demo
Testing
App.controller('FooController', function($scope) {!
!
$scope.setFoo = function(val) {!
$scope.foo = val;!
};!
!
});
Testing
describe('FooController', function() {!
!
beforeEach(function() {module('app')});!
!
beforeEach(inject(function($rootScope, $controller)
{!
this.scope = $rootScope.$new();!
$controller('FooController', {$scope: this.scope})!
}));!
!
describe('setFoo()', function() {!
!
it('sets the foo value', function() {!
expect(this.scope.foo).not.toBeDefined();!
this.scope.setFoo('Bar');!
expect(this.scope.foo).toEqual('Bar');!
});!
});!
});
Testing
describe('FooController', function() {!
!
beforeEach(function() {module('app')});!
!
beforeEach(inject(function($rootScope, $controller)
{!
this.scope = $rootScope.$new();!
$controller('FooController', {$scope: this.scope})!
}));!
!
describe('setFoo()', function() {!
!
it('sets the foo value', function() {!
expect(this.scope.foo).not.toBeDefined();!
this.scope.setFoo('Bar');!
expect(this.scope.foo).toEqual('Bar');!
});!
});!
});
Testing
describe('FooController', function() {!
!
beforeEach(function() {module('app')});!
!
beforeEach(inject(function($rootScope, $controller)
{!
this.scope = $rootScope.$new();!
$controller('FooController', {$scope: this.scope})!
}));!
!
describe('setFoo()', function() {!
!
it('sets the foo value', function() {!
expect(this.scope.foo).not.toBeDefined();!
this.scope.setFoo('Bar');!
expect(this.scope.foo).toEqual('Bar');!
});!
});!
});
Testing
describe('FooController', function() {!
!
beforeEach(function() {module('app')});!
!
beforeEach(inject(function($rootScope, $controller)
{!
this.scope = $rootScope.$new();!
$controller('FooController', {$scope: this.scope})!
}));!
!
describe('setFoo()', function() {!
!
it('sets the foo value', function() {!
expect(this.scope.foo).not.toBeDefined();!
this.scope.setFoo('Bar');!
expect(this.scope.foo).toEqual('Bar');!
});!
});!
});
Testing
describe('FooController', function() {!
!
beforeEach(function() {module('app')});!
!
beforeEach(inject(function($rootScope, $controller)
{!
this.scope = $rootScope.$new();!
$controller('FooController', {$scope: this.scope})!
}));!
!
describe('setFoo()', function() {!
!
it('sets the foo value', function() {!
expect(this.scope.foo).not.toBeDefined();!
this.scope.setFoo('Bar');!
expect(this.scope.foo).toEqual('Bar');!
});!
});!
});
Testing
describe('FooController', function() {!
!
beforeEach(function() {module('app')});!
!
beforeEach(inject(function($rootScope, $controller)
{!
this.scope = $rootScope.$new();!
$controller('FooController', {$scope: this.scope})!
}));!
!
describe('setFoo()', function() {!
!
it('sets the foo value', function() {!
expect(this.scope.foo).not.toBeDefined();!
this.scope.setFoo('Bar');!
expect(this.scope.foo).toEqual('Bar');!
});!
});!
});
Testing
describe('FooController', function() {!
!
beforeEach(function() {module('app')});!
!
beforeEach(inject(function($rootScope, $controller)
{!
this.scope = $rootScope.$new();!
$controller('FooController', {$scope: this.scope})!
}));!
!
describe('setFoo()', function() {!
!
it('sets the foo value', function() {!
expect(this.scope.foo).not.toBeDefined();!
this.scope.setFoo('Bar');!
expect(this.scope.foo).toEqual('Bar');!
});!
});!
});
Testing
describe('FooController', function() {!
!
beforeEach(function() {module('app')});!
!
beforeEach(inject(function($rootScope, $controller)
{!
this.scope = $rootScope.$new();!
$controller('FooController', {$scope: this.scope})!
}));!
!
describe('setFoo()', function() {!
!
it('sets the foo value', function() {!
expect(this.scope.foo).not.toBeDefined();!
this.scope.setFoo('Bar');!
expect(this.scope.foo).toEqual('Bar');!
});!
});!
});
Testing
describe('FooController', function() {!
!
beforeEach(function() {module('app')});!
!
beforeEach(inject(function($rootScope, $controller)
{!
this.scope = $rootScope.$new();!
$controller('FooController', {$scope: this.scope})!
}));!
!
describe('setFoo()', function() {!
!
it('sets the foo value', function() {!
expect(this.scope.foo).not.toBeDefined();!
this.scope.setFoo('Bar');!
expect(this.scope.foo).toEqual('Bar');!
});!
});!
});
Testing
describe('FooController', function() {!
!
beforeEach(function() {module('app')});!
!
beforeEach(inject(function($rootScope, $controller)
{!
this.scope = $rootScope.$new();!
$controller('FooController', {$scope: this.scope})!
}));!
!
describe('setFoo()', function() {!
!
it('sets the foo value', function() {!
expect(this.scope.foo).not.toBeDefined();!
this.scope.setFoo('Bar');!
expect(this.scope.foo).toEqual('Bar');!
});!
});!
});
Testing
describe('FooController', function() {!
!
beforeEach(function() {module('app')});!
!
beforeEach(inject(function($rootScope, $controller)
{!
this.scope = $rootScope.$new();!
$controller('FooController', {$scope: this.scope})!
}));!
!
describe('setFoo()', function() {!
!
it('sets the foo value', function() {!
expect(this.scope.foo).not.toBeDefined();!
this.scope.setFoo('Bar');!
expect(this.scope.foo).toEqual('Bar');!
});!
});!
});
Testing
Demo
Part 2
Setup!
Base Project!
github.com/markbates/
fluent-2014
Node.js!
http://nodejs.org
Lineman.js!
npm install -g
lineman@0.27.2
Install Modules!
npm install
Code Time!!
Thanks!
@markbates	

www.angularmasterclass.com

More Related Content

What's hot

Remy Sharp The DOM scripting toolkit jQuery
Remy Sharp The DOM scripting toolkit jQueryRemy Sharp The DOM scripting toolkit jQuery
Remy Sharp The DOM scripting toolkit jQuery
deimos
 
ApacheCon NA11 - Apache Celix, Universal OSGi?
ApacheCon NA11 - Apache Celix, Universal OSGi?ApacheCon NA11 - Apache Celix, Universal OSGi?
ApacheCon NA11 - Apache Celix, Universal OSGi?
abroekhuis
 
Hidden Treasures in Project Wonder
Hidden Treasures in Project WonderHidden Treasures in Project Wonder
Hidden Treasures in Project Wonder
WO Community
 

What's hot (20)

Remy Sharp The DOM scripting toolkit jQuery
Remy Sharp The DOM scripting toolkit jQueryRemy Sharp The DOM scripting toolkit jQuery
Remy Sharp The DOM scripting toolkit jQuery
 
Desenvolvendo APIs usando Rails - Guru SC 2012
Desenvolvendo APIs usando Rails - Guru SC 2012Desenvolvendo APIs usando Rails - Guru SC 2012
Desenvolvendo APIs usando Rails - Guru SC 2012
 
Prototype & jQuery
Prototype & jQueryPrototype & jQuery
Prototype & jQuery
 
AngularJS Compile Process
AngularJS Compile ProcessAngularJS Compile Process
AngularJS Compile Process
 
Nodejs do teste de unidade ao de integração
Nodejs  do teste de unidade ao de integraçãoNodejs  do teste de unidade ao de integração
Nodejs do teste de unidade ao de integração
 
Meet Magento Sweden - Magento 2 Layout and Code Compilation for Performance
Meet Magento Sweden - Magento 2 Layout and Code Compilation for PerformanceMeet Magento Sweden - Magento 2 Layout and Code Compilation for Performance
Meet Magento Sweden - Magento 2 Layout and Code Compilation for Performance
 
Testing Web Applications with GEB
Testing Web Applications with GEBTesting Web Applications with GEB
Testing Web Applications with GEB
 
JavaScript JQUERY AJAX
JavaScript JQUERY AJAXJavaScript JQUERY AJAX
JavaScript JQUERY AJAX
 
I Phone On Rails
I Phone On RailsI Phone On Rails
I Phone On Rails
 
Play!ng with scala
Play!ng with scalaPlay!ng with scala
Play!ng with scala
 
jQuery from the very beginning
jQuery from the very beginningjQuery from the very beginning
jQuery from the very beginning
 
Workshop 6: Designer tools
Workshop 6: Designer toolsWorkshop 6: Designer tools
Workshop 6: Designer tools
 
Hacking Movable Type
Hacking Movable TypeHacking Movable Type
Hacking Movable Type
 
jQuery
jQueryjQuery
jQuery
 
Presentation
PresentationPresentation
Presentation
 
06 jQuery #burningkeyboards
06 jQuery  #burningkeyboards06 jQuery  #burningkeyboards
06 jQuery #burningkeyboards
 
ApacheCon NA11 - Apache Celix, Universal OSGi?
ApacheCon NA11 - Apache Celix, Universal OSGi?ApacheCon NA11 - Apache Celix, Universal OSGi?
ApacheCon NA11 - Apache Celix, Universal OSGi?
 
Jquery introduction
Jquery introductionJquery introduction
Jquery introduction
 
jQuery Essentials
jQuery EssentialsjQuery Essentials
jQuery Essentials
 
Hidden Treasures in Project Wonder
Hidden Treasures in Project WonderHidden Treasures in Project Wonder
Hidden Treasures in Project Wonder
 

Viewers also liked

Angular js meetup
Angular js meetupAngular js meetup
Angular js meetup
Anton Kropp
 
Design pattern in an expressive language java script
Design pattern in an expressive language java scriptDesign pattern in an expressive language java script
Design pattern in an expressive language java script
Amit Thakkar
 
Practical AngularJS
Practical AngularJSPractical AngularJS
Practical AngularJS
Wei Ru
 
Benefits of developing single page web applications using angular js
Benefits of developing single page web applications using angular jsBenefits of developing single page web applications using angular js
Benefits of developing single page web applications using angular js
Harbinger Systems - HRTech Builder of Choice
 
Oldcastle Precast Spokane - Urban Modular Construction - The Grand Hotel
Oldcastle Precast Spokane - Urban Modular Construction - The Grand HotelOldcastle Precast Spokane - Urban Modular Construction - The Grand Hotel
Oldcastle Precast Spokane - Urban Modular Construction - The Grand Hotel
Blake Johnson
 

Viewers also liked (16)

A different thought angular js part-2
A different thought   angular js part-2A different thought   angular js part-2
A different thought angular js part-2
 
Angular js meetup
Angular js meetupAngular js meetup
Angular js meetup
 
Design pattern in an expressive language java script
Design pattern in an expressive language java scriptDesign pattern in an expressive language java script
Design pattern in an expressive language java script
 
MongoDB - An Agile NoSQL Database
MongoDB - An Agile NoSQL DatabaseMongoDB - An Agile NoSQL Database
MongoDB - An Agile NoSQL Database
 
A different thought angular js part-3
A different thought   angular js part-3A different thought   angular js part-3
A different thought angular js part-3
 
Design a landing page
Design a landing pageDesign a landing page
Design a landing page
 
A different thought AngularJS
A different thought AngularJSA different thought AngularJS
A different thought AngularJS
 
AngularJS Basics
AngularJS BasicsAngularJS Basics
AngularJS Basics
 
How AngularJS Embraced Traditional Design Patterns
How AngularJS Embraced Traditional Design PatternsHow AngularJS Embraced Traditional Design Patterns
How AngularJS Embraced Traditional Design Patterns
 
Design patterns in java script, jquery, angularjs
Design patterns in java script, jquery, angularjsDesign patterns in java script, jquery, angularjs
Design patterns in java script, jquery, angularjs
 
Practical AngularJS
Practical AngularJSPractical AngularJS
Practical AngularJS
 
076 Modular Construction
076 Modular Construction076 Modular Construction
076 Modular Construction
 
Benefits of developing single page web applications using angular js
Benefits of developing single page web applications using angular jsBenefits of developing single page web applications using angular js
Benefits of developing single page web applications using angular js
 
Building modular enterprise scale angular js applications
Building modular enterprise scale angular js applicationsBuilding modular enterprise scale angular js applications
Building modular enterprise scale angular js applications
 
Angular js for enteprise application
Angular js for enteprise applicationAngular js for enteprise application
Angular js for enteprise application
 
Oldcastle Precast Spokane - Urban Modular Construction - The Grand Hotel
Oldcastle Precast Spokane - Urban Modular Construction - The Grand HotelOldcastle Precast Spokane - Urban Modular Construction - The Grand Hotel
Oldcastle Precast Spokane - Urban Modular Construction - The Grand Hotel
 

Similar to Angular.js Fundamentals

Rails 3: Dashing to the Finish
Rails 3: Dashing to the FinishRails 3: Dashing to the Finish
Rails 3: Dashing to the Finish
Yehuda Katz
 
Rails 3 overview
Rails 3 overviewRails 3 overview
Rails 3 overview
Yehuda Katz
 
Spca2014 hillier 3rd party_javascript_libraries
Spca2014 hillier 3rd party_javascript_librariesSpca2014 hillier 3rd party_javascript_libraries
Spca2014 hillier 3rd party_javascript_libraries
NCCOMMS
 
Desenvolvimento web com Ruby on Rails (parte 2)
Desenvolvimento web com Ruby on Rails (parte 2)Desenvolvimento web com Ruby on Rails (parte 2)
Desenvolvimento web com Ruby on Rails (parte 2)
Joao Lucas Santana
 
Javascript MVC & Backbone Tips & Tricks
Javascript MVC & Backbone Tips & TricksJavascript MVC & Backbone Tips & Tricks
Javascript MVC & Backbone Tips & Tricks
Hjörtur Hilmarsson
 
Single Page Web Applications with CoffeeScript, Backbone and Jasmine
Single Page Web Applications with CoffeeScript, Backbone and JasmineSingle Page Web Applications with CoffeeScript, Backbone and Jasmine
Single Page Web Applications with CoffeeScript, Backbone and Jasmine
Paulo Ragonha
 
WebcampZG - Rails 4
WebcampZG - Rails 4WebcampZG - Rails 4
WebcampZG - Rails 4
shnikola
 
Resource and view
Resource and viewResource and view
Resource and view
Papp Laszlo
 

Similar to Angular.js Fundamentals (20)

AngularJS vs. Ember.js vs. Backbone.js
AngularJS vs. Ember.js vs. Backbone.jsAngularJS vs. Ember.js vs. Backbone.js
AngularJS vs. Ember.js vs. Backbone.js
 
The Rails Way
The Rails WayThe Rails Way
The Rails Way
 
Rails 3: Dashing to the Finish
Rails 3: Dashing to the FinishRails 3: Dashing to the Finish
Rails 3: Dashing to the Finish
 
Rails 3 overview
Rails 3 overviewRails 3 overview
Rails 3 overview
 
Ruby on Rails + AngularJS + Twitter Bootstrap
Ruby on Rails + AngularJS + Twitter BootstrapRuby on Rails + AngularJS + Twitter Bootstrap
Ruby on Rails + AngularJS + Twitter Bootstrap
 
Rails MVC by Sergiy Koshovyi
Rails MVC by Sergiy KoshovyiRails MVC by Sergiy Koshovyi
Rails MVC by Sergiy Koshovyi
 
Enjoy the vue.js
Enjoy the vue.jsEnjoy the vue.js
Enjoy the vue.js
 
Spca2014 hillier 3rd party_javascript_libraries
Spca2014 hillier 3rd party_javascript_librariesSpca2014 hillier 3rd party_javascript_libraries
Spca2014 hillier 3rd party_javascript_libraries
 
Desenvolvimento web com Ruby on Rails (parte 2)
Desenvolvimento web com Ruby on Rails (parte 2)Desenvolvimento web com Ruby on Rails (parte 2)
Desenvolvimento web com Ruby on Rails (parte 2)
 
Javascript MVC & Backbone Tips & Tricks
Javascript MVC & Backbone Tips & TricksJavascript MVC & Backbone Tips & Tricks
Javascript MVC & Backbone Tips & Tricks
 
Play vs Rails
Play vs RailsPlay vs Rails
Play vs Rails
 
Prateek dayal backbonerails-110528024926-phpapp02
Prateek dayal backbonerails-110528024926-phpapp02Prateek dayal backbonerails-110528024926-phpapp02
Prateek dayal backbonerails-110528024926-phpapp02
 
Single Page Web Apps with Backbone.js and Rails
Single Page Web Apps with Backbone.js and RailsSingle Page Web Apps with Backbone.js and Rails
Single Page Web Apps with Backbone.js and Rails
 
Working with Javascript in Rails
Working with Javascript in RailsWorking with Javascript in Rails
Working with Javascript in Rails
 
Get AngularJS Started!
Get AngularJS Started!Get AngularJS Started!
Get AngularJS Started!
 
Single Page Web Applications with CoffeeScript, Backbone and Jasmine
Single Page Web Applications with CoffeeScript, Backbone and JasmineSingle Page Web Applications with CoffeeScript, Backbone and Jasmine
Single Page Web Applications with CoffeeScript, Backbone and Jasmine
 
WebcampZG - Rails 4
WebcampZG - Rails 4WebcampZG - Rails 4
WebcampZG - Rails 4
 
EVOLVE'14 | Enhance | Gabriel Walt | Sightly Component Development
EVOLVE'14 | Enhance | Gabriel Walt | Sightly Component DevelopmentEVOLVE'14 | Enhance | Gabriel Walt | Sightly Component Development
EVOLVE'14 | Enhance | Gabriel Walt | Sightly Component Development
 
Resource and view
Resource and viewResource and view
Resource and view
 
Basics of AngularJS
Basics of AngularJSBasics of AngularJS
Basics of AngularJS
 

More from Mark

More from Mark (17)

Go(lang) for the Rubyist
Go(lang) for the RubyistGo(lang) for the Rubyist
Go(lang) for the Rubyist
 
Mangling Ruby with TracePoint
Mangling Ruby with TracePointMangling Ruby with TracePoint
Mangling Ruby with TracePoint
 
A Big Look at MiniTest
A Big Look at MiniTestA Big Look at MiniTest
A Big Look at MiniTest
 
A Big Look at MiniTest
A Big Look at MiniTestA Big Look at MiniTest
A Big Look at MiniTest
 
GET /better
GET /betterGET /better
GET /better
 
CoffeeScript
CoffeeScriptCoffeeScript
CoffeeScript
 
Testing Your JavaScript & CoffeeScript
Testing Your JavaScript & CoffeeScriptTesting Your JavaScript & CoffeeScript
Testing Your JavaScript & CoffeeScript
 
Building an API in Rails without Realizing It
Building an API in Rails without Realizing ItBuilding an API in Rails without Realizing It
Building an API in Rails without Realizing It
 
5 Favorite Gems (Lightning Talk(
5 Favorite Gems (Lightning Talk(5 Favorite Gems (Lightning Talk(
5 Favorite Gems (Lightning Talk(
 
CoffeeScript for the Rubyist
CoffeeScript for the RubyistCoffeeScript for the Rubyist
CoffeeScript for the Rubyist
 
RubyMotion
RubyMotionRubyMotion
RubyMotion
 
Testing JavaScript/CoffeeScript with Mocha and Chai
Testing JavaScript/CoffeeScript with Mocha and ChaiTesting JavaScript/CoffeeScript with Mocha and Chai
Testing JavaScript/CoffeeScript with Mocha and Chai
 
CoffeeScript for the Rubyist
CoffeeScript for the RubyistCoffeeScript for the Rubyist
CoffeeScript for the Rubyist
 
Testing Rich Client Side Apps with Jasmine
Testing Rich Client Side Apps with JasmineTesting Rich Client Side Apps with Jasmine
Testing Rich Client Side Apps with Jasmine
 
DRb and Rinda
DRb and RindaDRb and Rinda
DRb and Rinda
 
CoffeeScript - A Rubyist's Love Affair
CoffeeScript - A Rubyist's Love AffairCoffeeScript - A Rubyist's Love Affair
CoffeeScript - A Rubyist's Love Affair
 
Distributed Programming with Ruby/Rubyconf 2010
Distributed Programming with Ruby/Rubyconf 2010Distributed Programming with Ruby/Rubyconf 2010
Distributed Programming with Ruby/Rubyconf 2010
 

Recently uploaded

EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
Earley Information Science
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slide
vu2urc
 

Recently uploaded (20)

Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organization
 
Strategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a FresherStrategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a Fresher
 
Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024
 
Boost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityBoost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivity
 
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
 
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
 
GenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationGenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day Presentation
 
presentation ICT roal in 21st century education
presentation ICT roal in 21st century educationpresentation ICT roal in 21st century education
presentation ICT roal in 21st century education
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processors
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slide
 
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
 
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
 
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdf
 
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men
 
What Are The Drone Anti-jamming Systems Technology?
What Are The Drone Anti-jamming Systems Technology?What Are The Drone Anti-jamming Systems Technology?
What Are The Drone Anti-jamming Systems Technology?
 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonets
 
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
 
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdfUnderstanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
 

Angular.js Fundamentals

  • 3.
  • 4.
  • 8. Enough Angular to be Dangerous!
  • 11. • Controllers! • ngRoute! • Templates! • ngResource! • Directives! • Filters! • Scope! • Testing! • Code Organization! • Best Practices
  • 12. Part 1 • Features/Why Angular?! • Getting Started/Setting Up! • Directives, Filters, and Data Binding! • Controllers, Templates, and Scope! • Modules! • Routing! • Custom Directives and Event Handling! • Testing
  • 16. Features • Plain JavaScript • Data Binding • Routing/PushState • Testing • Templates/Directives/ Controllers • Modular • Dependency Injection • jqLite • Lightweight
  • 19. Backbone.js “minimal set of data-structure and view primitives for building web application with JavaScript”
  • 20. Ember “framework for creating ambitious web applications”
  • 21. AngularJS “Toolset for building the framework most suited to your application development”
  • 22.
  • 23.
  • 26. AngularJS Ember Backbone.js base 109kb 264kb 6.5kb templating language built-in 90kb (handlebars) ?? data adapter built-in 75kb (ember-data) 84kb (jQuery) support N/A 84kb (jQuery) 17kb (json2.js) 5.0kb (underscore.js ) 109kb 513kb 112.5kb
  • 29. AngularJS Backbone.js Ember Watchers 2,155 1,442 824 Stars 21,408 17,291 9,570 Forks 6,670 3,783 2,044 Github
  • 31. Backbone.jsclass  App.Beer  extends   Backbone.Model       class  App.Beers  extends   Backbone.Collection   !  model:  Beer
  • 32. EmberApp.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")
  • 35. 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
  • 36. EmberApp.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")
  • 37. Ember DS.RESTAdapter.reopen      namespace:  'api/v1'       App.Store  =  DS.Store.extend      revision:  14      adapter:  DS.RESTAdapter.create()
  • 38. AngularJS App.factory  "Beer",  ($resource)  -­‐>      return  $resource  "/api/v1/ beers/:id",                                        {id:  "@id"},                                        {update:  {method:   "PUT"}}
  • 40. 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)
  • 41. Ember App.Router.map  -­‐>      @resource  "brewery",  {path:  "brewery/:brewery_id"}
  • 42. EmberApp.BreweryRoute  =   Ember.Route.extend      model:  (params)-­‐>           App.Brewery.find(params.brewery_i d)
  • 43. AngularJSApp.config  ($routeProvider)  -­‐>          $routeProvider          .when("/breweries/:id",  {              templateUrl:  "/assets/ brewery.html",              controller:  "BreweryController"          })          .when("/breweries/:id/edit",  {              templateUrl:  "/assets/ edit_brewery.html",              controller:   "EditBreweryController"          })
  • 45. 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
  • 46. Ember App.BreweryController  =  Ember.ObjectController.extend        save:  -­‐>          @store.commit()          #  further  code  omitted
  • 47. 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
  • 49. 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>
  • 50. 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>
  • 51. 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>
  • 52. <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
  • 53. <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
  • 54. <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
  • 55. <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
  • 56. 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>
  • 57. <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
  • 59. Backbone.js • Too simple • Not opinionated enough • “Memory” management • Unstructured • Spaghetti code • Lightweight • Not opinionated • Simple • Easy to read source • “widget” development Pros Cons
  • 60. Ember • Too complex • Overly opinionated • Heavyweight • ember-data - not production ready (very buggy) • Little to no mind-share outside of Rails • Difficult to read source code • Structured • Highly opinionated • “less” code • “large” apps Pros Cons
  • 61. 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
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 71. Directives <body ng-app>! <ng-view></ng-view>! <ul>! <li ng-repeat='comment in comments'>! {{comment.body}}! </li>! </ul>! </body>
  • 72. Directives <body ng-app>! <ng-view></ng-view>! <ul>! <li ng-repeat='comment in comments'>! {{comment.body}}! </li>! </ul>! </body>
  • 73. Directives <body ng-app>! <ng-view></ng-view>! <ul>! <li ng-repeat='comment in comments'>! {{comment.body}}! </li>! </ul>! </body>
  • 74. Directives <body ng-app>! <ng-view></ng-view>! <ul>! <li ng-repeat='comment in comments'>! {{comment.body}}! </li>! </ul>! </body>
  • 75. Directives <body ng-app>! <ng-view></ng-view>! <ul>! <li ng-repeat='comment in comments'>! {{comment.body}}! </li>! </ul>! </body>
  • 76. Demo
  • 77. Filters <ul>! <li ng-repeat='c in comments | orderBy:"date"'>! {{c.author | uppercase}}! </li>! </ul>
  • 78. Filters <ul>! <li ng-repeat='c in comments | orderBy:"date"'>! {{c.author | uppercase}}! </li>! </ul>
  • 79. Filters <ul>! <li ng-repeat='c in comments | orderBy:"date"'>! {{c.author | uppercase}}! </li>! </ul>
  • 80. <ul>! <li ng-repeat='c in comments | orderBy:"date"'>! {{c.author | uppercase}}! </li>! </ul> Filters
  • 81. Demo
  • 87. Demo
  • 93. Module Config Controller Factories Directives angular.module('app', []);
  • 94. Module Config Controller Factories Directives Filters angular.module('app', []);
  • 95. Module Config Controller Factories Directives Filters Routes angular.module('app', []);
  • 96. Demo
  • 98. App.config(function($routeProvider, $locationProvider) {! $locationProvider.html5Mode(true);! ! $routeProvider.when('/posts', {! controller: 'PostsIndexController',! templateUrl: 'posts/index.html'! })! .when('/posts/:id',{! controller: 'PostsShowController',! templateUrl: 'posts/show.html'! })! .otherwise({! redirectTo: '/posts'! });! }); Routing
  • 99. App.config(function($routeProvider, $locationProvider) {! $locationProvider.html5Mode(true);! ! $routeProvider.when('/posts', {! controller: 'PostsIndexController',! templateUrl: 'posts/index.html'! })! .when('/posts/:id',{! controller: 'PostsShowController',! templateUrl: 'posts/show.html'! })! .otherwise({! redirectTo: '/posts'! });! }); Routing
  • 100. App.config(function($routeProvider, $locationProvider) {! $locationProvider.html5Mode(true);! ! $routeProvider.when('/posts', {! controller: 'PostsIndexController',! templateUrl: 'posts/index.html'! })! .when('/posts/:id',{! controller: 'PostsShowController',! templateUrl: 'posts/show.html'! })! .otherwise({! redirectTo: '/posts'! });! }); Routing
  • 101. App.config(function($routeProvider, $locationProvider) {! $locationProvider.html5Mode(true);! ! $routeProvider.when('/posts', {! controller: 'PostsIndexController',! templateUrl: 'posts/index.html'! })! .when('/posts/:id',{! controller: 'PostsShowController',! templateUrl: 'posts/show.html'! })! .otherwise({! redirectTo: '/posts'! });! }); Routing
  • 102. App.config(function($routeProvider, $locationProvider) {! $locationProvider.html5Mode(true);! ! $routeProvider.when('/posts', {! controller: 'PostsIndexController',! templateUrl: 'posts/index.html'! })! .when('/posts/:id',{! controller: 'PostsShowController',! templateUrl: 'posts/show.html'! })! .otherwise({! redirectTo: '/posts'! });! }); Routing
  • 103. App.config(function($routeProvider, $locationProvider) {! $locationProvider.html5Mode(true);! ! $routeProvider.when('/posts', {! controller: 'PostsIndexController',! templateUrl: 'posts/index.html'! })! .when('/posts/:id',{! controller: 'PostsShowController',! templateUrl: 'posts/show.html'! })! .otherwise({! redirectTo: '/posts'! });! }); Routing
  • 104. App.config(function($routeProvider, $locationProvider) {! $locationProvider.html5Mode(true);! ! $routeProvider.when('/posts', {! controller: 'PostsIndexController',! templateUrl: 'posts/index.html'! })! .when('/posts/:id',{! controller: 'PostsShowController',! templateUrl: 'posts/show.html'! })! .otherwise({! redirectTo: '/posts'! });! }); Routing
  • 105. App.config(function($routeProvider, $locationProvider) {! $locationProvider.html5Mode(true);! ! $routeProvider.when('/posts', {! controller: 'PostsIndexController',! templateUrl: 'posts/index.html'! })! .when('/posts/:id',{! controller: 'PostsShowController',! templateUrl: 'posts/show.html'! })! .otherwise({! redirectTo: '/posts'! });! }); Routing
  • 106. App.config(function($routeProvider, $locationProvider) {! $locationProvider.html5Mode(true);! ! $routeProvider.when('/posts', {! controller: 'PostsIndexController',! templateUrl: 'posts/index.html'! })! .when('/posts/:id',{! controller: 'PostsShowController',! templateUrl: 'posts/show.html'! })! .otherwise({! redirectTo: '/posts'! });! }); Routing
  • 107. App.config(function($routeProvider, $locationProvider) {! $locationProvider.html5Mode(true);! ! $routeProvider.when('/posts', {! controller: 'PostsIndexController',! templateUrl: 'posts/index.html'! })! .when('/posts/:id',{! controller: 'PostsShowController',! templateUrl: 'posts/show.html'! })! .otherwise({! redirectTo: '/posts'! });! }); Routing
  • 108. App.config(function($routeProvider, $locationProvider) {! $locationProvider.html5Mode(true);! ! $routeProvider.when('/posts', {! controller: 'PostsIndexController',! templateUrl: 'posts/index.html'! })! .when('/posts/:id',{! controller: 'PostsShowController',! templateUrl: 'posts/show.html'! })! .otherwise({! redirectTo: '/posts'! });! }); Routing
  • 109. Demo
  • 111. App.directive("upCaser", function() {! return {! link: function(scope, el, attrs) {! $(el).find('p').each(function(i, p) {! p = $(p);! p.text(p.text().toUpperCase());! });! }! };! }); Directives
  • 112. DirectivesApp.directive("upCaser", function() {! return {! link: function(scope, el, attrs) {! $(el).find('p').each(function(i, p) {! p = $(p);! p.text(p.text().toUpperCase());! });! }! };! });
  • 113. App.directive("upCaser", function() {! return {! link: function(scope, el, attrs) {! $(el).find('p').each(function(i, p) {! p = $(p);! p.text(p.text().toUpperCase());! });! }! };! }); Directives
  • 114. App.directive("upCaser", function() {! return {! link: function(scope, el, attrs) {! $(el).find('p').each(function(i, p) {! p = $(p);! p.text(p.text().toUpperCase());! });! }! };! }); Directives
  • 115. App.directive("upCaser", function() {! return {! link: function(scope, el, attrs) {! $(el).find('p').each(function(i, p) {! p = $(p);! p.text(p.text().toUpperCase());! });! }! };! }); Directives
  • 116. App.directive("upCaser", function() {! return {! link: function(scope, el, attrs) {! $(el).find('p').each(function(i, p) {! p = $(p);! p.text(p.text().toUpperCase());! });! }! };! }); Directives
  • 117. App.directive("upCaser", function() {! return {! link: function(scope, el, attrs) {! $(el).find('p').each(function(i, p) {! p = $(p);! p.text(p.text().toUpperCase());! });! }! };! }); Directives
  • 118. App.directive("upCaser", function() {! return {! link: function(scope, el, attrs) {! $(el).find('p').each(function(i, p) {! p = $(p);! p.text(p.text().toUpperCase());! });! }! };! }); Directives
  • 119. <div up-caser>! <p>some text</p>! <p>some other text</p>! </div> Directives
  • 121. App.directive("upCaser", function() {! return {! restrict: 'E',! link: function(scope, el, attrs) {! $(el).find('p').each(function(i, p) {! p = $(p);! p.text(p.text().toUpperCase());! });! }! };! }); Directives
  • 123. App.directive("upCaser", function() {! return {! restrict: 'AEC',! link: function(scope, el, attrs) {! $(el).find('p').each(function(i, p) {! p = $(p);! p.text(p.text().toUpperCase());! });! }! };! }); Directives
  • 124. Demo
  • 125. Events
  • 126. Scope
  • 128. App.directive("alerter", function() {! return {! restrict: 'E',! link: function(scope, el, attrs) {! setTimeout(function() {! scope.$emit("up", "Hello Up!");! scope.$broadcast("down", "Hello Down!");! scope.$apply();! }, 3000);! }! };! }); Events
  • 129. App.directive("alerter", function() {! return {! restrict: 'E',! link: function(scope, el, attrs) {! setTimeout(function() {! scope.$emit("up", "Hello Up!");! scope.$broadcast("down", "Hello Down!");! scope.$apply();! }, 3000);! }! };! }); Events
  • 130. App.directive("alerter", function() {! return {! restrict: 'E',! link: function(scope, el, attrs) {! setTimeout(function() {! scope.$emit("up", "Hello Up!");! scope.$broadcast("down", "Hello Down!");! scope.$apply();! }, 3000);! }! };! }); Events
  • 131. App.directive("alerter", function() {! return {! restrict: 'E',! link: function(scope, el, attrs) {! setTimeout(function() {! scope.$emit("up", "Hello Up!");! scope.$broadcast("down", "Hello Down!");! scope.$apply();! }, 3000);! }! };! }); Events
  • 132. App.directive("alerter", function() {! return {! restrict: 'E',! link: function(scope, el, attrs) {! setTimeout(function() {! scope.$emit("up", "Hello Up!");! scope.$broadcast("down", "Hello Down!");! scope.$apply();! }, 3000);! }! };! }); Events
  • 133. App.directive("alerter", function() {! return {! restrict: 'E',! link: function(scope, el, attrs) {! setTimeout(function() {! scope.$emit("up", "Hello Up!");! scope.$broadcast("down", "Hello Down!");! scope.$apply();! }, 3000);! }! };! }); Events
  • 134. App.controller('SomeController', function($scope) {! $scope.$on("up", function(data, message) {! $scope.up_message = message;! });! }); Events
  • 135. App.controller('SomeController', function($scope) {! $scope.$on("up", function(data, message) {! $scope.up_message = message;! });! }); Events
  • 136. App.controller('SomeController', function($scope) {! $scope.$on("up", function(data, message) {! $scope.up_message = message;! });! }); Events
  • 137. App.controller('SomeController', function($scope) {! $scope.$on("up", function(data, message) {! $scope.up_message = message;! });! }); Events
  • 138. App.controller('SomeController', function($scope) {! $scope.$on("up", function(data, message) {! $scope.up_message = message;! });! }); Events
  • 139. App.controller('SomeController', function($scope) {! $scope.$on("up", function(data, message) {! $scope.up_message = message;! });! }); Events
  • 140. Demo
  • 141. App.controller('Main', function($scope) {! $scope.clicker = function() {! $scope.pressed = true;! };! }); Events
  • 142. App.directive("alerter", function() {! return {! restrict: 'E',! link: function(scope, el, attrs) {! scope.$watch('pressed', function() {! if (scope.pressed) {! $(el).text('Oi!');! }! });! }! };! }); Events
  • 143. App.directive("alerter", function() {! return {! restrict: 'E',! link: function(scope, el, attrs) {! scope.$watch('pressed', function() {! if (scope.pressed) {! $(el).text('Oi!');! }! });! }! };! }); Events
  • 144. Demo
  • 146.
  • 147. App.controller('FooController', function($scope) {! ! $scope.setFoo = function(val) {! $scope.foo = val;! };! ! }); Testing
  • 148. describe('FooController', function() {! ! beforeEach(function() {module('app')});! ! beforeEach(inject(function($rootScope, $controller) {! this.scope = $rootScope.$new();! $controller('FooController', {$scope: this.scope})! }));! ! describe('setFoo()', function() {! ! it('sets the foo value', function() {! expect(this.scope.foo).not.toBeDefined();! this.scope.setFoo('Bar');! expect(this.scope.foo).toEqual('Bar');! });! });! }); Testing
  • 149. describe('FooController', function() {! ! beforeEach(function() {module('app')});! ! beforeEach(inject(function($rootScope, $controller) {! this.scope = $rootScope.$new();! $controller('FooController', {$scope: this.scope})! }));! ! describe('setFoo()', function() {! ! it('sets the foo value', function() {! expect(this.scope.foo).not.toBeDefined();! this.scope.setFoo('Bar');! expect(this.scope.foo).toEqual('Bar');! });! });! }); Testing
  • 150. describe('FooController', function() {! ! beforeEach(function() {module('app')});! ! beforeEach(inject(function($rootScope, $controller) {! this.scope = $rootScope.$new();! $controller('FooController', {$scope: this.scope})! }));! ! describe('setFoo()', function() {! ! it('sets the foo value', function() {! expect(this.scope.foo).not.toBeDefined();! this.scope.setFoo('Bar');! expect(this.scope.foo).toEqual('Bar');! });! });! }); Testing
  • 151. describe('FooController', function() {! ! beforeEach(function() {module('app')});! ! beforeEach(inject(function($rootScope, $controller) {! this.scope = $rootScope.$new();! $controller('FooController', {$scope: this.scope})! }));! ! describe('setFoo()', function() {! ! it('sets the foo value', function() {! expect(this.scope.foo).not.toBeDefined();! this.scope.setFoo('Bar');! expect(this.scope.foo).toEqual('Bar');! });! });! }); Testing
  • 152. describe('FooController', function() {! ! beforeEach(function() {module('app')});! ! beforeEach(inject(function($rootScope, $controller) {! this.scope = $rootScope.$new();! $controller('FooController', {$scope: this.scope})! }));! ! describe('setFoo()', function() {! ! it('sets the foo value', function() {! expect(this.scope.foo).not.toBeDefined();! this.scope.setFoo('Bar');! expect(this.scope.foo).toEqual('Bar');! });! });! }); Testing
  • 153. describe('FooController', function() {! ! beforeEach(function() {module('app')});! ! beforeEach(inject(function($rootScope, $controller) {! this.scope = $rootScope.$new();! $controller('FooController', {$scope: this.scope})! }));! ! describe('setFoo()', function() {! ! it('sets the foo value', function() {! expect(this.scope.foo).not.toBeDefined();! this.scope.setFoo('Bar');! expect(this.scope.foo).toEqual('Bar');! });! });! }); Testing
  • 154. describe('FooController', function() {! ! beforeEach(function() {module('app')});! ! beforeEach(inject(function($rootScope, $controller) {! this.scope = $rootScope.$new();! $controller('FooController', {$scope: this.scope})! }));! ! describe('setFoo()', function() {! ! it('sets the foo value', function() {! expect(this.scope.foo).not.toBeDefined();! this.scope.setFoo('Bar');! expect(this.scope.foo).toEqual('Bar');! });! });! }); Testing
  • 155. describe('FooController', function() {! ! beforeEach(function() {module('app')});! ! beforeEach(inject(function($rootScope, $controller) {! this.scope = $rootScope.$new();! $controller('FooController', {$scope: this.scope})! }));! ! describe('setFoo()', function() {! ! it('sets the foo value', function() {! expect(this.scope.foo).not.toBeDefined();! this.scope.setFoo('Bar');! expect(this.scope.foo).toEqual('Bar');! });! });! }); Testing
  • 156. describe('FooController', function() {! ! beforeEach(function() {module('app')});! ! beforeEach(inject(function($rootScope, $controller) {! this.scope = $rootScope.$new();! $controller('FooController', {$scope: this.scope})! }));! ! describe('setFoo()', function() {! ! it('sets the foo value', function() {! expect(this.scope.foo).not.toBeDefined();! this.scope.setFoo('Bar');! expect(this.scope.foo).toEqual('Bar');! });! });! }); Testing
  • 157. describe('FooController', function() {! ! beforeEach(function() {module('app')});! ! beforeEach(inject(function($rootScope, $controller) {! this.scope = $rootScope.$new();! $controller('FooController', {$scope: this.scope})! }));! ! describe('setFoo()', function() {! ! it('sets the foo value', function() {! expect(this.scope.foo).not.toBeDefined();! this.scope.setFoo('Bar');! expect(this.scope.foo).toEqual('Bar');! });! });! }); Testing
  • 158. describe('FooController', function() {! ! beforeEach(function() {module('app')});! ! beforeEach(inject(function($rootScope, $controller) {! this.scope = $rootScope.$new();! $controller('FooController', {$scope: this.scope})! }));! ! describe('setFoo()', function() {! ! it('sets the foo value', function() {! expect(this.scope.foo).not.toBeDefined();! this.scope.setFoo('Bar');! expect(this.scope.foo).toEqual('Bar');! });! });! }); Testing
  • 159. Demo
  • 160. Part 2
  • 161. Setup!