@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!
• B...
Part 1
• Features/Why
Angular?!
• Getting Started/Setting
Up!
• Directives, Filters, and
Data Binding!
• Controllers,
Temp...
Part 2
We Code!
Part 1
Features
Features
• Plain JavaScript
• Data Binding
• Routing/PushState
• Testing
• Templates/Directives/
Controllers
• Modular
• D...
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-...
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.Collect...
EmberApp.Beer	
  =	
  DS.Model.extend	
  
	
  	
  title:	
  DS.attr("string")	
  
	
  	
  abv:	
  DS.attr("number")	
  
	
...
AngularJS
App.Beer	
  =	
  
{}
“Remote” Models
Backbone.jsclass	
  App.Beer	
  extends	
  Backbone.Model	
  
	
  	
  urlRoot:	
  "/api/v1/beers"	
  
	
  	
  	
  
class	
...
EmberApp.Beer	
  =	
  DS.Model.extend	
  
	
  	
  title:	
  DS.attr("string")	
  
	
  	
  abv:	
  DS.attr("number")	
  
	
...
Ember
DS.RESTAdapter.reopen	
  
	
  	
  namespace:	
  'api/v1'	
  
	
  	
  
App.Store	
  =	
  DS.Store.extend	
  
	
  	
  ...
AngularJS
App.factory	
  "Beer",	
  ($resource)	
  -­‐>	
  
	
  	
  return	
  $resource	
  "/api/v1/
beers/:id",	
  
	
  	...
Routers
Backbone.js@Router	
  =	
  Backbone.Router.extend	
  
	
  	
  
	
  	
  initialize:	
  -­‐>	
  
	
  	
  	
  	
  @countries	...
Ember
App.Router.map	
  -­‐>	
  
	
  	
  @resource	
  "brewery",	
  {path:	
  "brewery/:brewery_id"}
EmberApp.BreweryRoute	
  =	
  
Ember.Route.extend	
  
	
  	
  model:	
  (params)-­‐>	
  
	
  	
  	
  	
  
App.Brewery.find...
AngularJSApp.config	
  ($routeProvider)	
  -­‐>	
  
	
  	
  
	
  	
  $routeProvider	
  
	
  	
  	
  	
  .when("/breweries/...
Controllers/Views
Backbone.jsclass	
  @BreweryEditView	
  extends	
  Backbone.View	
  
	
  	
  
	
  	
  template:	
  "brewery_edit"	
  
	
  ...
Ember
App.BreweryController	
  =	
  Ember.ObjectController.extend	
  
	
  	
  
	
  save:	
  -­‐>	
  
	
  	
  	
  	
  @stor...
AngularJS@EditBreweryController	
  =	
  ($scope,	
  $routeParams,	
  $location,	
  
Brewery)	
  -­‐>	
  
	
  	
  
	
  	
  ...
Templates
Backbone.js
<h2><%=	
  @model.displayName()	
  %></h2>	
  
	
  	
  
<form>	
  
	
  	
  
	
  	
  <div	
  class="control-­‐g...
Backbone.js
<h2><%=	
  @model.displayName()	
  %></h2>	
  
	
  	
  
<form>	
  
	
  	
  
	
  	
  <div	
  class="control-­‐g...
Backbone.js<h2><%=	
  @model.displayName()	
  %></h2>	
  
	
  	
  
<form>	
  
	
  	
  
	
  	
  <div	
  class="control-­‐gr...
<div	
  class='span12'>	
  
	
  	
  <h2>{{displayName}}</h2>	
  
	
  	
  <h3>	
  
	
  	
  	
  	
  {{cityState}}	
  
	
  	
...
<div	
  class='span12'>	
  
	
  	
  <h2>{{displayName}}</h2>	
  
	
  	
  <h3>	
  
	
  	
  	
  	
  {{cityState}}	
  
	
  	
...
<div	
  class='span12'>	
  
	
  	
  <h2>{{displayName}}</h2>	
  
	
  	
  <h3>	
  
	
  	
  	
  	
  {{cityState}}	
  
	
  	
...
<form>	
  
	
  	
  <h3>{{brewery.title}}</h3>	
  
	
  	
  <div	
  ng-­‐include='"/assets/_errors.html"'></div>	
  
	
  	
 ...
AngularJS<form>	
  
	
  	
  <h3>{{brewery.title}}</h3>	
  
	
  	
  <div	
  ng-­‐include='"/assets/_errors.html"'></div>	
 ...
<form>	
  
	
  	
  <h3>{{brewery.title}}</h3>	
  
	
  	
  <div	
  ng-­‐include='"/assets/_errors.html"'></div>	
  
	
  	
 ...
Pros/Cons
Backbone.js
• Too simple	

• Not opinionated enough	

• “Memory” management	

• Unstructured	

• Spaghetti code
• Lightwei...
Ember
• Too complex	

• Overly opinionated	

• Heavyweight	

• ember-data - not production ready (very buggy)	

• Little t...
AngularJS
• Difficult to read source
code	

• jQuery plugins require
custom directives	

• Large apps requiring self-
impos...
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>!...
Directives
<body ng-app>!
<ng-view></ng-view>!
<ul>!
<li ng-repeat='comment in comments'>!
{{comment.body}}!
</li>!
</ul>!...
Directives
<body ng-app>!
<ng-view></ng-view>!
<ul>!
<li ng-repeat='comment in comments'>!
{{comment.body}}!
</li>!
</ul>!...
Directives
<body ng-app>!
<ng-view></ng-view>!
<ul>!
<li ng-repeat='comment in comments'>!
{{comment.body}}!
</li>!
</ul>!...
Directives
<body ng-app>!
<ng-view></ng-view>!
<ul>!
<li ng-repeat='comment in comments'>!
{{comment.body}}!
</li>!
</ul>!...
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('/post...
App.config(function($routeProvider, $locationProvider) {!
$locationProvider.html5Mode(true);!
!
$routeProvider.when('/post...
App.config(function($routeProvider, $locationProvider) {!
$locationProvider.html5Mode(true);!
!
$routeProvider.when('/post...
App.config(function($routeProvider, $locationProvider) {!
$locationProvider.html5Mode(true);!
!
$routeProvider.when('/post...
App.config(function($routeProvider, $locationProvider) {!
$locationProvider.html5Mode(true);!
!
$routeProvider.when('/post...
App.config(function($routeProvider, $locationProvider) {!
$locationProvider.html5Mode(true);!
!
$routeProvider.when('/post...
App.config(function($routeProvider, $locationProvider) {!
$locationProvider.html5Mode(true);!
!
$routeProvider.when('/post...
App.config(function($routeProvider, $locationProvider) {!
$locationProvider.html5Mode(true);!
!
$routeProvider.when('/post...
App.config(function($routeProvider, $locationProvider) {!
$locationProvider.html5Mode(true);!
!
$routeProvider.when('/post...
App.config(function($routeProvider, $locationProvider) {!
$locationProvider.html5Mode(true);!
!
$routeProvider.when('/post...
App.config(function($routeProvider, $locationProvider) {!
$locationProvider.html5Mode(true);!
!
$routeProvider.when('/post...
Demo
Custom Directives
and Event Handling
App.directive("upCaser", function() {!
return {!
link: function(scope, el, attrs) {!
$(el).find('p').each(function(i, p) {...
DirectivesApp.directive("upCaser", function() {!
return {!
link: function(scope, el, attrs) {!
$(el).find('p').each(functi...
App.directive("upCaser", function() {!
return {!
link: function(scope, el, attrs) {!
$(el).find('p').each(function(i, p) {...
App.directive("upCaser", function() {!
return {!
link: function(scope, el, attrs) {!
$(el).find('p').each(function(i, p) {...
App.directive("upCaser", function() {!
return {!
link: function(scope, el, attrs) {!
$(el).find('p').each(function(i, p) {...
App.directive("upCaser", function() {!
return {!
link: function(scope, el, attrs) {!
$(el).find('p').each(function(i, p) {...
App.directive("upCaser", function() {!
return {!
link: function(scope, el, attrs) {!
$(el).find('p').each(function(i, p) {...
App.directive("upCaser", function() {!
return {!
link: function(scope, el, attrs) {!
$(el).find('p').each(function(i, p) {...
<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(...
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').eac...
Demo
Events
Scope
Events
$broadcast
$emit
App.directive("alerter", function() {!
return {!
restrict: 'E',!
link: function(scope, el, attrs) {!
setTimeout(function()...
App.directive("alerter", function() {!
return {!
restrict: 'E',!
link: function(scope, el, attrs) {!
setTimeout(function()...
App.directive("alerter", function() {!
return {!
restrict: 'E',!
link: function(scope, el, attrs) {!
setTimeout(function()...
App.directive("alerter", function() {!
return {!
restrict: 'E',!
link: function(scope, el, attrs) {!
setTimeout(function()...
App.directive("alerter", function() {!
return {!
restrict: 'E',!
link: function(scope, el, attrs) {!
setTimeout(function()...
App.directive("alerter", function() {!
return {!
restrict: 'E',!
link: function(scope, el, attrs) {!
setTimeout(function()...
App.controller('SomeController', function($scope) {!
$scope.$on("up", function(data, message) {!
$scope.up_message = messa...
App.controller('SomeController', function($scope) {!
$scope.$on("up", function(data, message) {!
$scope.up_message = messa...
App.controller('SomeController', function($scope) {!
$scope.$on("up", function(data, message) {!
$scope.up_message = messa...
App.controller('SomeController', function($scope) {!
$scope.$on("up", function(data, message) {!
$scope.up_message = messa...
App.controller('SomeController', function($scope) {!
$scope.$on("up", function(data, message) {!
$scope.up_message = messa...
App.controller('SomeController', function($scope) {!
$scope.$on("up", function(data, message) {!
$scope.up_message = messa...
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...
App.directive("alerter", function() {!
return {!
restrict: 'E',!
link: function(scope, el, attrs) {!
scope.$watch('pressed...
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...
describe('FooController', function() {!
!
beforeEach(function() {module('app')});!
!
beforeEach(inject(function($rootScope...
describe('FooController', function() {!
!
beforeEach(function() {module('app')});!
!
beforeEach(inject(function($rootScope...
describe('FooController', function() {!
!
beforeEach(function() {module('app')});!
!
beforeEach(inject(function($rootScope...
describe('FooController', function() {!
!
beforeEach(function() {module('app')});!
!
beforeEach(inject(function($rootScope...
describe('FooController', function() {!
!
beforeEach(function() {module('app')});!
!
beforeEach(inject(function($rootScope...
describe('FooController', function() {!
!
beforeEach(function() {module('app')});!
!
beforeEach(inject(function($rootScope...
describe('FooController', function() {!
!
beforeEach(function() {module('app')});!
!
beforeEach(inject(function($rootScope...
describe('FooController', function() {!
!
beforeEach(function() {module('app')});!
!
beforeEach(inject(function($rootScope...
describe('FooController', function() {!
!
beforeEach(function() {module('app')});!
!
beforeEach(inject(function($rootScope...
describe('FooController', function() {!
!
beforeEach(function() {module('app')});!
!
beforeEach(inject(function($rootScope...
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
Angular.js Fundamentals
Angular.js Fundamentals
Angular.js Fundamentals
Angular.js Fundamentals
Angular.js Fundamentals
Angular.js Fundamentals
Angular.js Fundamentals
Angular.js Fundamentals
Angular.js Fundamentals
Angular.js Fundamentals
Angular.js Fundamentals
Upcoming SlideShare
Loading in …5
×

Angular.js Fundamentals

1,435 views
1,365 views

Published on

As present at FluentConf 2014 on March 11th, 2014.

AngularJS is one of the most popular, and powerful, JavaScript frameworks for building rich client-side applications. AngularJS is both simultaneously both simple to use and extremely full featured. With AngularJS a little goes a long way, but to make the most of it, you need to know what you’re doing.

In this workshop we will build a complex application to help exercise all of the salient points of the AngularJS framework.

Topics covered include, ngResource, directives, fitlers, routing, templates, controllers, testing, and more.

Code can be found at: https://github.com/markbates/fluent-2014

Published in: Technology
0 Comments
4 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
1,435
On SlideShare
0
From Embeds
0
Number of Embeds
2
Actions
Shares
0
Downloads
37
Comments
0
Likes
4
Embeds 0
No embeds

No notes for slide

Angular.js Fundamentals

  1. 1. @markbates
  2. 2. @markbates
  3. 3. FLUENT2014 www.metacasts.tv
  4. 4. www.angularmasterclass.com
  5. 5. Angular Fundamentals
  6. 6. Enough Angular to be Dangerous!
  7. 7. What Will Cover?
  8. 8. What Will Cover?* *hopefully
  9. 9. • Controllers! • ngRoute! • Templates! • ngResource! • Directives! • Filters! • Scope! • Testing! • Code Organization! • Best Practices
  10. 10. 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
  11. 11. Part 2 We Code!
  12. 12. Part 1
  13. 13. Features
  14. 14. Features • Plain JavaScript • Data Binding • Routing/PushState • Testing • Templates/Directives/ Controllers • Modular • Dependency Injection • jqLite • Lightweight
  15. 15. Why Angular?
  16. 16. Philosophies
  17. 17. Backbone.js “minimal set of data-structure and view primitives for building web application with JavaScript”
  18. 18. Ember “framework for creating ambitious web applications”
  19. 19. AngularJS “Toolset for building the framework most suited to your application development”
  20. 20. Weight
  21. 21. “production” versions (minified) w/ required dependencies
  22. 22. 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
  23. 23. Mindshare
  24. 24. Google
  25. 25. 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
  26. 26. “Basic” Models
  27. 27. Backbone.jsclass  App.Beer  extends   Backbone.Model       class  App.Beers  extends   Backbone.Collection   !  model:  Beer
  28. 28. 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")
  29. 29. AngularJS App.Beer  =   {}
  30. 30. “Remote” Models
  31. 31. 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
  32. 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")
  33. 33. Ember DS.RESTAdapter.reopen      namespace:  'api/v1'       App.Store  =  DS.Store.extend      revision:  14      adapter:  DS.RESTAdapter.create()
  34. 34. AngularJS App.factory  "Beer",  ($resource)  -­‐>      return  $resource  "/api/v1/ beers/:id",                                        {id:  "@id"},                                        {update:  {method:   "PUT"}}
  35. 35. Routers
  36. 36. 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)
  37. 37. Ember App.Router.map  -­‐>      @resource  "brewery",  {path:  "brewery/:brewery_id"}
  38. 38. EmberApp.BreweryRoute  =   Ember.Route.extend      model:  (params)-­‐>           App.Brewery.find(params.brewery_i d)
  39. 39. AngularJSApp.config  ($routeProvider)  -­‐>          $routeProvider          .when("/breweries/:id",  {              templateUrl:  "/assets/ brewery.html",              controller:  "BreweryController"          })          .when("/breweries/:id/edit",  {              templateUrl:  "/assets/ edit_brewery.html",              controller:   "EditBreweryController"          })
  40. 40. Controllers/Views
  41. 41. 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
  42. 42. Ember App.BreweryController  =  Ember.ObjectController.extend        save:  -­‐>          @store.commit()          #  further  code  omitted
  43. 43. 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
  44. 44. Templates
  45. 45. 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>
  46. 46. 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>
  47. 47. 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>
  48. 48. <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
  49. 49. <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
  50. 50. <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
  51. 51. <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
  52. 52. 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>
  53. 53. <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
  54. 54. Pros/Cons
  55. 55. Backbone.js • Too simple • Not opinionated enough • “Memory” management • Unstructured • Spaghetti code • Lightweight • Not opinionated • Simple • Easy to read source • “widget” development Pros Cons
  56. 56. 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
  57. 57. 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
  58. 58. Getting Started
  59. 59. 5 Minute Break
  60. 60. Directives, Filters, and Data Binding
  61. 61. Directives <body ng-app>! <ng-view></ng-view>! <ul>! <li ng-repeat='comment in comments'>! {{comment.body}}! </li>! </ul>! </body>
  62. 62. Directives <body ng-app>! <ng-view></ng-view>! <ul>! <li ng-repeat='comment in comments'>! {{comment.body}}! </li>! </ul>! </body>
  63. 63. Directives <body ng-app>! <ng-view></ng-view>! <ul>! <li ng-repeat='comment in comments'>! {{comment.body}}! </li>! </ul>! </body>
  64. 64. Directives <body ng-app>! <ng-view></ng-view>! <ul>! <li ng-repeat='comment in comments'>! {{comment.body}}! </li>! </ul>! </body>
  65. 65. Directives <body ng-app>! <ng-view></ng-view>! <ul>! <li ng-repeat='comment in comments'>! {{comment.body}}! </li>! </ul>! </body>
  66. 66. Demo
  67. 67. Filters <ul>! <li ng-repeat='c in comments | orderBy:"date"'>! {{c.author | uppercase}}! </li>! </ul>
  68. 68. Filters <ul>! <li ng-repeat='c in comments | orderBy:"date"'>! {{c.author | uppercase}}! </li>! </ul>
  69. 69. Filters <ul>! <li ng-repeat='c in comments | orderBy:"date"'>! {{c.author | uppercase}}! </li>! </ul>
  70. 70. <ul>! <li ng-repeat='c in comments | orderBy:"date"'>! {{c.author | uppercase}}! </li>! </ul> Filters
  71. 71. Demo
  72. 72. Controllers, Templates, and Scope
  73. 73. Controllers, Templates and Scope Template
  74. 74. Controllers, Templates and Scope Template Controller
  75. 75. Controllers, Templates and Scope Template Controller
  76. 76. Controllers, Templates and Scope Template Controller
  77. 77. Demo
  78. 78. Modules
  79. 79. Module angular.module('app', []);
  80. 80. Module Config angular.module('app', []);
  81. 81. Module Config Controller angular.module('app', []);
  82. 82. Module Config Controller Factories angular.module('app', []);
  83. 83. Module Config Controller Factories Directives angular.module('app', []);
  84. 84. Module Config Controller Factories Directives Filters angular.module('app', []);
  85. 85. Module Config Controller Factories Directives Filters Routes angular.module('app', []);
  86. 86. Demo
  87. 87. Routing
  88. 88. 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
  89. 89. 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
  90. 90. 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
  91. 91. 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
  92. 92. 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
  93. 93. 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
  94. 94. 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
  95. 95. 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
  96. 96. 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
  97. 97. 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
  98. 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. 99. Demo
  100. 100. Custom Directives and Event Handling
  101. 101. 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
  102. 102. DirectivesApp.directive("upCaser", function() {! return {! link: function(scope, el, attrs) {! $(el).find('p').each(function(i, p) {! p = $(p);! p.text(p.text().toUpperCase());! });! }! };! });
  103. 103. 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
  104. 104. 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
  105. 105. 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
  106. 106. 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
  107. 107. 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
  108. 108. 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
  109. 109. <div up-caser>! <p>some text</p>! <p>some other text</p>! </div> Directives
  110. 110. Directives <up-caser>! <p>some text</p>! <p>some other text</p>! </up-caser>
  111. 111. 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
  112. 112. Directives <up-caser>! <p>some text</p>! <p>some other text</p>! </up-caser>
  113. 113. 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
  114. 114. Demo
  115. 115. Events
  116. 116. Scope
  117. 117. Events $broadcast $emit
  118. 118. 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
  119. 119. 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
  120. 120. 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
  121. 121. 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
  122. 122. 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
  123. 123. 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
  124. 124. App.controller('SomeController', function($scope) {! $scope.$on("up", function(data, message) {! $scope.up_message = message;! });! }); Events
  125. 125. App.controller('SomeController', function($scope) {! $scope.$on("up", function(data, message) {! $scope.up_message = message;! });! }); Events
  126. 126. App.controller('SomeController', function($scope) {! $scope.$on("up", function(data, message) {! $scope.up_message = message;! });! }); Events
  127. 127. App.controller('SomeController', function($scope) {! $scope.$on("up", function(data, message) {! $scope.up_message = message;! });! }); Events
  128. 128. App.controller('SomeController', function($scope) {! $scope.$on("up", function(data, message) {! $scope.up_message = message;! });! }); Events
  129. 129. App.controller('SomeController', function($scope) {! $scope.$on("up", function(data, message) {! $scope.up_message = message;! });! }); Events
  130. 130. Demo
  131. 131. App.controller('Main', function($scope) {! $scope.clicker = function() {! $scope.pressed = true;! };! }); Events
  132. 132. App.directive("alerter", function() {! return {! restrict: 'E',! link: function(scope, el, attrs) {! scope.$watch('pressed', function() {! if (scope.pressed) {! $(el).text('Oi!');! }! });! }! };! }); Events
  133. 133. App.directive("alerter", function() {! return {! restrict: 'E',! link: function(scope, el, attrs) {! scope.$watch('pressed', function() {! if (scope.pressed) {! $(el).text('Oi!');! }! });! }! };! }); Events
  134. 134. Demo
  135. 135. Testing
  136. 136. App.controller('FooController', function($scope) {! ! $scope.setFoo = function(val) {! $scope.foo = val;! };! ! }); Testing
  137. 137. 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
  138. 138. 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
  139. 139. 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
  140. 140. 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
  141. 141. 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
  142. 142. 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
  143. 143. 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
  144. 144. 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
  145. 145. 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
  146. 146. 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
  147. 147. 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
  148. 148. Demo
  149. 149. Part 2
  150. 150. Setup!
  151. 151. Base Project! github.com/markbates/ fluent-2014
  152. 152. Node.js! http://nodejs.org
  153. 153. Lineman.js! npm install -g lineman@0.27.2
  154. 154. Install Modules! npm install
  155. 155. Code Time!!
  156. 156. Thanks! @markbates www.angularmasterclass.com

×