angularjs in practice
DESCRIPTION
Already seen the angular basics? Ready for a deeper dive? Lets dive into how angular is being used at Dealer.com and dive deep into $compile and what those magic directive symbols really mean. If you didn't make it to VT Code Camp to see this in person, then you should read this post which is the basis for the slides. http://goo.gl/JMf0ssTRANSCRIPT
©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