More Related Content Similar to Angular.js Fundamentals (20) Angular.js Fundamentals11. • 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
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
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")
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)
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
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
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
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