©2014 Dealertrack, Inc. All rights reserved.
Jon HoguetSr. UI [email protected]
AngularJS in Practice
©2014 Dealertrack, Inc. All rights reserved.
Jon Hoguet who?
©2014 Dealertrack, Inc. All rights reserved.
▪ Our Architecture▪ Overview▪ Metrics▪ File Structure▪ Lessons Learned
▪ Angular ▪ Controller▪ Directive▪ $compile▪ Directives and Isolate Scope▪ Lessons Learned
▪ Build▪ Grunt▪ Testing with Karma▪ Testing with Protractor
▪ Debugging▪ $log▪ in the browser
▪ I reading this online, you should check out the ▪ Correlating Blog Post
What to expect
©2014 Dealertrack, Inc. All rights reserved.
Our Architecture
©2014 Dealertrack, Inc. All rights reserved.
15 Angular Modules
43 Angular Directives
15 Angular Controllers
36 Angular Services
52 Angular Templates
13,669 lines of javascript
Focusing on Angular
©2014 Dealertrack, Inc. All rights reserved.
Some Humility
©2014 Dealertrack, Inc. All rights reserved.
File Structure
©2014 Dealertrack, Inc. All rights reserved.
Minimal Grails dependency
©2014 Dealertrack, Inc. All rights reserved.
Do organize around functionality
Build First
Don’t mix templating languages
Lessons Learned
©2014 Dealertrack, Inc. All rights reserved.
Don’t mix templating languages
<inventory:firedoor firedoorClass="search-firedoor lifecycle-criteria"
ngshow="lifecycles.targetLifecycle.isEditingCriteria" fluid="false"
customButtons="${[ [click:
'lifecycles.targetLifecycle.isEditingCriteria=false', icon: 'check', text: 'Done', customClass:
'btn-primary'] ]}">
<div class="form-group lifecycle-name-group">
<input type="text" class="form-control input-lg"
ng-model="lifecycles.targetLifecycle.model.name"
ng-change="lifecycles.targetLifecycle.dirty()"
placeholder="Enter a name...">
</div><div criteria-form title="Lifecycle Criteria"
criteria-model="lifecycles.targetLifecycle.model.criteria"
dirty="lifecycles.targetLifecycle.dirty">
</div>
</inventory:firedoor>
©2014 Dealertrack, Inc. All rights reserved.
Controllers vs Directives
©2014 Dealertrack, Inc. All rights reserved.
A controller is an abstraction of
a directive
©2014 Dealertrack, Inc. All rights reserved.
Consider...
©2014 Dealertrack, Inc. All rights reserved.
Controller right?
©2014 Dealertrack, Inc. All rights reserved.
Somewhere else in the dom...
©2014 Dealertrack, Inc. All rights reserved.
Controller abstraction of directive isn’t working… use a directive
©2014 Dealertrack, Inc. All rights reserved.
©2014 Dealertrack, Inc. All rights reserved.
$compile = BFF
$compile( markup / directives )( scope ) = live dom
fragment
1. Most important thing angular does2. composable3. makes testing easy4. makes experimentation easy
©2014 Dealertrack, Inc. All rights reserved.
live dom element
var ioc = angular.element('[ng-app]').injector(),
$rootScope = ioc.get('$rootScope'),
$compile = ioc.get('$compile');
var scope = $rootScope.$new(true);
scope.child = {
name : 'Lance',
age : 3
};
var markup = "<div>{{ child.name }} is {{ child.age }} years old!</div>";
var el;
scope.$apply(function(){
el = $compile(markup)(scope);
});
// el.html() === 'Lance is 3 years old!'
©2014 Dealertrack, Inc. All rights reserved.
live dom element
var ioc = angular.element('[ng-app]').injector(),
$rootScope = ioc.get('$rootScope'),
$compile = ioc.get('$compile');
var scope = $rootScope.$new(true);
scope.child = {
name : 'Lance',
age : 3
};
var markup = "<div>{{ child.name }} is {{ child.age }} years old!</div>";
var el;
scope.$apply(function(){
el = $compile(markup)(scope);
});
// el.html() === 'Lance is 3 years old!'
©2014 Dealertrack, Inc. All rights reserved.
live dom element
var ioc = angular.element('[ng-app]').injector(),
$rootScope = ioc.get('$rootScope'),
$compile = ioc.get('$compile');
var scope = $rootScope.$new(true);
scope.child = {
name : 'Lance',
age : 3
};
var markup = "<div>{{ child.name }} is {{ child.age }} years old!</div>";
var el;
scope.$apply(function(){
el = $compile(markup)(scope);
});
// el.html() === 'Lance is 3 years old!'
©2014 Dealertrack, Inc. All rights reserved.
live dom element
var ioc = angular.element('[ng-app]').injector(),
$rootScope = ioc.get('$rootScope'),
$compile = ioc.get('$compile');
var scope = $rootScope.$new(true);
scope.child = {
name : 'Lance',
age : 3
};
var markup = "<div>{{ child.name }} is {{ child.age }} years old!</div>";
var el;
scope.$apply(function(){
el = $compile(markup)(scope);
});
// el.html() === 'Lance is 3 years old!'
©2014 Dealertrack, Inc. All rights reserved.
live dom element
var ioc = angular.element('[ng-app]').injector(),
$rootScope = ioc.get('$rootScope'),
$compile = ioc.get('$compile');
var scope = $rootScope.$new(true);
scope.child = {
name : 'Lance',
age : 3
};
var markup = "<div>{{ child.name }} is {{ child.age }} years old!</div>";
var el;
scope.$apply(function(){
el = $compile(markup)(scope);
});
// el.html() === 'Lance is 3 years old!'
©2014 Dealertrack, Inc. All rights reserved.
live dom element
var ioc = angular.element('[ng-app]').injector(),
$rootScope = ioc.get('$rootScope'),
$compile = ioc.get('$compile');
var scope = $rootScope.$new(true);
scope.child = {
name : 'Lance',
age : 3
};
var markup = "<div>{{ child.name }} is {{ child.age }} years old!</div>";
var el;
scope.$apply(function(){
el = $compile(markup)(scope);
});
// el.html() === 'Lance is 3 years old!'
©2014 Dealertrack, Inc. All rights reserved.
What do you mean live?
scope.$apply(function(){
scope.child = {
name : 'Lana',
age : 2
};
});
//el.html() === 'Lana is 2 years old!'
©2014 Dealertrack, Inc. All rights reserved.
Live meaning 2 way
markup = "<input type=\"text\" ng-model=\"child.name\" />";
var input;
// note we are applying the same scope to this markup
scope.$apply(function(){
input = $compile(markup)(scope);
});
input.val('Lana Hoguet');
input.change();
// scope.name === 'Lana Hoguet'
// el.html() === 'Lana Hoguet is 2 years old!'
©2014 Dealertrack, Inc. All rights reserved.
Live meaning 2 way
markup = "<input type=\"text\" ng-model=\"child.name\" />";
var input;
// note we are applying the same scope to this markup
scope.$apply(function(){
input = $compile(markup)(scope);
});
input.val('Lana Hoguet');
input.change();
// scope.name === 'Lana Hoguet'
// el.html() === 'Lana Hoguet is 2 years old!'
©2014 Dealertrack, Inc. All rights reserved.
Live meaning 2 way
markup = "<input type=\"text\" ng-model=\"child.name\" />";
var input;
// note we are applying the same scope to this markup
scope.$apply(function(){
input = $compile(markup)(scope);
});
input.val('Lana Hoguet');
input.change();
// scope.child.name === 'Lana Hoguet'
// el.html() === 'Lana Hoguet is 2 years old!'
©2014 Dealertrack, Inc. All rights reserved.
Live meaning 2 way
markup = "<input type=\"text\" ng-model=\"child.name\" />";
var input;
// note we are applying the same scope to this markup
scope.$apply(function(){
input = $compile(markup)(scope);
});
input.val('Lana Hoguet');
input.change();
// scope.child.name === 'Lana Hoguet'
// el.html() === 'Lana Hoguet is 2 years old!'
©2014 Dealertrack, Inc. All rights reserved.
Hurt yet?
©2014 Dealertrack, Inc. All rights reserved.
Directives Refresher
©2014 Dealertrack, Inc. All rights reserved.
Directives Refresher
= for two way
@ for one way
& for functions
©2014 Dealertrack, Inc. All rights reserved.
one way / two way misnomer
©2014 Dealertrack, Inc. All rights reserved.
one way / two way misnomer$compile source code
©2014 Dealertrack, Inc. All rights reserved.
one way / two way misnomer$compile source code
©2014 Dealertrack, Inc. All rights reserved.
one way / two way misnomer
Note: $interpolate and $parse are not coupled to $scope
©2014 Dealertrack, Inc. All rights reserved.
= for two way uses $parse
@ for one way uses $interpolate
& for functions
©2014 Dealertrack, Inc. All rights reserved.
& demystified
©2014 Dealertrack, Inc. All rights reserved.
& demystified
$compile source code
©2014 Dealertrack, Inc. All rights reserved.
= for two way uses $parse
@ for one way uses $interpolate
& for functions wraps $parse in fn
©2014 Dealertrack, Inc. All rights reserved.
= for two way uses $parse and watches
@ for one way uses $interpolate and watches
& for functions wraps $parse in fn
©2014 Dealertrack, Inc. All rights reserved.
©2014 Dealertrack, Inc. All rights reserved.
Beware the ng-model monster
fiddle
©2014 Dealertrack, Inc. All rights reserved.
©2014 Dealertrack, Inc. All rights reserved.
©2014 Dealertrack, Inc. All rights reserved.
angular-templates
Not proposing you use inline scripts - just simple example
©2014 Dealertrack, Inc. All rights reserved.
angular-templates
$templateCache
$http
Not proposing you use inline scripts - just simple example
©2014 Dealertrack, Inc. All rights reserved.
angular-templates
ng-include, ng-view, ui-view, or templateUrl
$http $templateCache
©2014 Dealertrack, Inc. All rights reserved.
angular-templates
npm install grunt-angular-templates --save-dev
©2014 Dealertrack, Inc. All rights reserved.
angular-templates
©2014 Dealertrack, Inc. All rights reserved.
angular-modules
©2014 Dealertrack, Inc. All rights reserved.
angular-modules
©2014 Dealertrack, Inc. All rights reserved.
©2014 Dealertrack, Inc. All rights reserved.
Unit testing directives is easy!
©2014 Dealertrack, Inc. All rights reserved.
Protractor
Not Just for Angular
But has angular specific hooks built in
Promises!!
©2014 Dealertrack, Inc. All rights reserved.
Debugging - Log More
©2014 Dealertrack, Inc. All rights reserved.
Debugging - In the Browser
You can get anything out of IOC
©2014 Dealertrack, Inc. All rights reserved.
Debugging - In the Browser
You can inspect any scope
©2014 Dealertrack, Inc. All rights reserved.
Debugging - In the Browser
You can mutate the scope and verify how the view reacts
©2014 Dealertrack, Inc. All rights reserved.
Conclusion