For a presentation given to the Angular DC Meetup on 3/19/14. (http://www.meetup.com/AngularJS-DC/events/169813802/) Part 2 of the "Angular from Scratch" series. Find part one at http://christianlilley.wordpress.com/2013/11/15/angular-from-scratch-slides-from-angularjs-meetup-dc/ . Find the accompanying demonstration files at https://github.com/xmlilley/ng-demos.
5. Fine. How about…
5(-ish) words:
Logic & Behavior For UI
“...a way to teach HTML new tricks.”
Anything in your app that touches DOM
Examples: event-handling, behavior
management, template pre-processing &
insertion, data-binding, ‘Collection
Views’, UI Widgets, conditional
display, i18n & localization, etc.
6. The only other Angular construct that
really touches the DOM is:
Angular Expressions.
The rest of it should be in
Directives. (Even the ng-view that
executes your routing is simply a
model-driven directive...)
Fine. How about…
7. Structurally speaking, a Directive is
just a function that’s attached to an
element.
But not just a function: a whole
execution environment. Really,
Directives are mini-applications.
You can think of them as little
robotic pilots that live on your DOM
elements & tell them what to do.
Fine. How about…
8.
9. They can BYO DOM, or just be declared
on inline DOM.
Fine. How about…
16. Why Declarative?
IMPERATIVE = YOUR PROBLEM
DECLARATIVE = SOMEBODY ELSE’S PROBLEM
Easier To Read, Maintain: Why scatter
event-listeners across 100 linked JS
files, then need to go search for
them to find out what’s happening on
an element.
17. Declarativeness ROCKS
You’re trying to find handlers for
this element:
Well, where are the event-handlers?
On ‘#1’? On ‘.B’? ‘.C’? On ‘button’?
What if it’s on ‘parentDiv>:first-
child’?
You can’t misunderstand what’s
happening with declarative directives:
<button id=”1” class=”B C”></button>
<button md-action-handler></button>
18. Extending HTML…
HTML is NOT a virgin bride or
hothouse flower.
The Semantics Wars are over. HTML is
a highly-abstracted, Object-Oriented
language for app interfaces and for
*presenting* documents. Docs
themselves are increasingly stored in
other formats, like markdown.
We’re not abandoning accessibility.
But it’s not a binary choice, anyway.
20. Reusability
It’s all about context-awareness,
data-binding & DI.
Directives know their own element and
local scope.
You can pass additional data into
directives as attributes, right on
the element.
24. Yes: ‘Local’
Sticks to a self-contained, modular
scope, which understands its context:
inside the directive, `element` is
like `this`.
Uses messages, models to affect
things elsewhere.
Easier to maintain, easier to read,
easier to scale.
But the challenge to all that is:
25. My Awesome Website
Sweet Product
Product Description: Lorem ipsum
dolor sit amet, consectetur
adipiscing elit. In erat mauris,
faucibus quis pharetra sit amet,
pretium ac libero. Etiam vehicula
eleifend bibendum. Morbi gravida
metus ut sapien condimentum sodales
mollis augue sodales. Vestibulum
quis quam at sem placerat aliquet.
Curabitur a felis at sapien
ullamcorper fermentum. Mauris
molestie arcu et lectus iaculis sit
amet eleifend eros posuere. Fusce
nec porta orci.
!
Integer vitae neque odio, a
sollicitudin lorem. Aenean orci
mauris, tristique luctus fermentum
$899.99 Buy Now!
Cart: 1 Item(s)
Clicking Here
Needs to
Change
Things Here
27. Directive Names
Angular uses a convention borrowed
from other JS projects: names in HTML
are hyphenated...
while identifiers in the JS are
camel-cased:
!
Expect Angular to do this conversion
automatically. Don’t fight it.
.directive(‘sampleDirective’, function(){})
<sample-directive></sample-directive>
28. How are custom directives
different from built-in?
They’re not.
At all.
No, really.
(Well, OK: they’re different in
naming conventions: don’t use ‘ng-’
in your custom directives.)
29. CREATION
.directive() is a method we call on
an angular.module(), either at
creation time or via reference,
passing a name and a factory function
The factory will return either a
function or an object containing a
function and other settings
angular
.module('moduleName', ['dependency1', 'dependency2'])
.directive('directiveName', factoryFunction() {})
30. Factories
(Note, when we talk about generic
‘factories’, we don’t mean $factory,
which is an Angular implementation
service.)
The factory pattern is all about
Functional Programming: using basic
Javascript functions to build and
return either naiive objects or other
functions.
35. You’ll See Later, But
Ignore For Today:
Returning only the Link
function
Link vs. Compile
Pre-Link vs. Post-Link
36.
37. Using a Config Object
angular.module('moduleName').
directive('sampleDirective', function(){
return {
link: function(scope, element, attrs) {
// this example binds a behavior to the
// mouseenter event
element.bind("mouseenter", function(){
... do stuff after mouseenter ...
}
},
restrict: ‘E’,
template: “<div>Hello, World!</div>”
}})
Everything but `link` is optional.
38. Link Function Args
.directive('sampleDirective', function(){
return {
link: function(scope, element, attrs) {
// this example binds a behavior to the
// mouseenter event
element.bind("mouseenter", function(){
... do stuff after mouseenter ...
}
},
restrict: ‘E’,
template: <div>Hello, World!</div>
}
})
39. Link Function Args
3 standard params for a link function.
(Plus optional 4th: controller.) They’re
supplied as args by the directive function,
if specified.
scope: whatever scope object is local
element: element declared on: `this`
attrs: an object containing the html
attributes defined on the element,
including the directive invocation itself
Supplied to the function not by name but in
order. Call them whatever you want.
40. jqLite:
your path to the DOM
Angular will defer to JQuery, if
present, but provides its own subset
of JQuery for basic DOM tasks.
You can’t just use $(), nor find
using selectors, unfortunately.
But all built-in `element` refs are
already pre-wrapped in jqlite object
Chain methods as you normally would
45. A Thought:
If angular.element() / jqlite doesn’t
support what you’re trying to do...
ask yourself: why not?
Because they’re lazy bastards?
Not so much. Think about other options.
Go with the grain, and Angular will
reward you.
46. Directive Templates
Templates can be stored as strings on
the `template:` property
They can also be loaded from a file,
using:
`templateUrl: path/to/file/template.html’
47. Templates
.directive('sampleDirective', function(){
return {
link: function(scope, element, attrs) {
// this example binds a behavior to the
// mouseenter event
element.bind("mouseenter", function(){
... do stuff after mouseenter ...
}
},
restrict: ‘E’,
template: ‘<div>Hello, World!</div>’
//or:
templateUrl: ‘path/to/file.html’
})
48. The Restrict Property
.directive('sampleDirective', function(){
return {
link: function(scope, element, attrs) {
// this example binds a behavior to the
// mouseenter event
element.bind("mouseenter", function(){
... do stuff after mouseenter ...
}
},
restrict: ‘E’,
template: <div>Hello, World!</div>
}
})
49. The Restrict Property
Remember that directives are re-usable
So, we can restrict the usage of a
directive to (a) specific context(s), so
that we don’t accidentally try to use it
in a situation it wasn’t designed for:
‘E’ = Element
‘A’ = Attribute
‘C’ = Class
‘M’ = Comment
Stack as a single string: ‘EACM’.
Defaults to ‘A’.
50. The Replace Property
By default, a directive element will
wrap the contents of a template. The
`element` object will be the outer
directive element.
To instead replace the directive
element (and object) with the contents
of the template, use {replace: true}
This is esp critical when declaring as
an element...
54. Why Model-Driven?
After all, the imperative
approach works fine...
...if you’re omniscient
and precognitive.
... and you really, really
like refactoring.
55. Comparative Click-
Handlers: Imperative
“Do a whole bunch of stuff in
response to that click. Remember all
the things it should affect. Update
all those things. Try hard (and fail)
to do so without reading state from
the UI. Try hard (and fail) to
decompose these changes into reusable
functions. Try hard (and fail) to
make them declarative so I can easily
understand and maintain what's
happening."
56. Comparative Click-
Handlers: Angular
“Update some data, and/or send out a
notification. Done.” (Everything else
will happen in directives on the
affected elements.)
In those other directives: “react to
received events, or to data-changes.
Change only what’s local to the
element.”
60. How Can Directives
React to Stuff that
Happens Far, Far Away?
Again, with models & $watch!
But sometimes, the inheritance
chain isn’t a good solution. For
those times...
Angular events!
$on(), $emit(), $broadcast()
61. Advanced Topic:
Inter-Scope Communication
Use $watch to monitor properties of
local $scope or one it inherits from
That works great when you only need
data to flow in one direction (up)
and only on one branch of the tree.
What about when you need to go
downwards, or sideways?
Or a whole bunch of places at once?
63. Angular Events to
The Rescue!!!
Just like how a ‘click’ event bubbles
up through the DOM tree, you can
$emit() an Angular event up the
$scope tree, from any starting point.
Better than the DOM, you can also
$broadcast() an event down the $scope
tree.
$broadcast()-ing from $rootScope gets
you the whole shebang.
64.
65. Angular Events to
The Rescue!!!
With events, there’s no need to
laboriously climb your way up the
$scope tree. You also eliminate the
chance of getting the wrong scope.
You also get full de-coupling of a
controller/directive from a
particular place in your app. Use it
anywhere, $broadcast() everywhere.
66. Angular Events to
The Rescue!!!
And you don’t even need to predict
who all the recipients will be. By
sending:
$rootScope.$broadcast(‘gameOver’)
your whole app gets the information,
You can consume the event as many
places as you like, with:
$scope.$on(‘gameOver’, handlerFunc)
70. `$scope` vs. `scope`
$scope is assignable, but should be
reserved for angular functions to pass
into a controller, other context.
$scope is a shorthand, by which we're
calling the $scopeProvider, which is
Dependency-Injecting the scope for us.
There is a long-form that looks like
AMD, which is necessary when minifying,
so identifiers don't get munged.
scope is just our own, customizable
reference for directive’s local scope.
71. Link & Compile
Angular’s process for assembling a
page divides the work into multiple
phases: compile & link (pre- & post-)
Directives have a function for each
step
Think of link & compile like events, &
your functions firing as handlers
We usually only need the link
function.
72. Link vs. Compile
Compile: “deals with transforming the
template DOM. Since most directives do not
do template transformation, it is not used
often. Examples that require compile are
directives that transform template DOM,
such as ngRepeat, or load the contents
asynchronously, such as ngView.” ie., it
grabs HTML assets and morphs them if
necessary. Rarely needed by users.
Link: “responsible for registering DOM
listeners as well as updating the DOM. It
is executed after the template has been
cloned.” ie., where everything else goes.
73. Using a Config Object
(compile version)
If we need to use a compile function,
that becomes the relevant property on
the config object, and we return the
link function from inside it.
If we provide a value for the
`compile:` property, any value
provided for `link:` will be ignored.
A compile function *must* return a link
function.
74. Using a Config Object
(compile version example)
.directive('sampleDirective', function(){
return {
compile: function(element, attrs) {
... do compile stuff ...
return function Link(scope, element, attrs)
{... do link stuff ...})
},
restrict: ‘E’,
template: <div>Hello, World!</div>
}
})
75. And just to add more
complexity to that
structure...
76. Subdividing the Link
(config object example)
.directive('sampleDirective', function(){
return {
compile: function(element, attrs) {
... do compile stuff ...
return {
pre: function(scope, element, attrs)
{... do pre-link stuff ...},
post: function(scope, element, attrs)
{... do post-link stuff ...}
}
}}})
The link event actually consists of two
sub-events: pre-link and post-link,
which you can target:
77. Pre- vs. Post- Link
PRE-LINK:
“Executed before the child elements are
linked. Not safe to do DOM transformation
since the compiler linking function will
fail to locate the correct elements for
linking.”
POST-LINK:
“Executed after the child elements are
linked. It is safe to do DOM transformation
in the post-linking function."
But: don’t worry about these for now. Just
be aware of their existence, in case you see
them.
79. Directive Config Objects can provide
an optional controller.
At first, you think: why?
One option: alternative to routing
Routes have controllers
Sometimes, you don’t want routes
Often Overlooked:
Directive Controllers
80. Often Overlooked:
Directive Controllers
With its own controller, a directive
is a full, standalone interface
component, with its own data context,
which can be built or torn-down on
demand.
The controller inherits, normally,
from the rest of the $scope tree.
81. Isolate Scope
We have the option, in directives, of
using either:
the local $scope (from our own
controller, possibly)
a new, per-instance, ‘isolate
scope’
Isolate scopes still have a parent
$scope, but they’re *encapsulated*: or,
detached from the inheritance chain.
This is especially useful with repeats,
so variables can be fully local to the
instance
82. Creating Isolate Scope
Creating isolate scope is as simple as
an object literal assigned to the
`scope:` property on the config object:
.directive('sampleDirective', function(){
return {
link: function(scope, element, attrs) {
element.bind("mouseenter", function(){
... do stuff after mouseenter ...
}
},
restrict: ‘E’,
scope: {name: “Bob”,
hobby: “@”}
}})
84. Isolate Scope Data-Binding
Angular provides us with ways to bind
the value of properties in isolate
scope to attributes on the element,
using special operators:
.directive('sampleDirective', function(){
return {
link: function(scope, element, attrs) {},
restrict: ‘E’,
scope: {name: “Bob”,
hobby: “@”}
//alt. form:{hobby: ‘@hobby’}
}})
<sample-directive hobby=”scuba-diving”>
85. Data-Binding Operators
By default, an operator alone will be
assumed to refer to a same-named attr
Alternately, use form ‘@hobby’ to specify
Options:
‘@’- binds the local scope property to
primitive value of the DOM attribute. Result
is always a string. (Attributes are strings.)
‘=’- binds the local scope property to a
parent scope property having same name as the
value of the DOM attribute.
‘&’- binds the local scope property to the
output of an expression defined in the DOM
attribute. It’s like a function-wrapper.
86. Isolate Scope Data-Binding
The special operators make it seem more
exotic than it really is. For instance:
…is basically the same as doing this in
your controller:
The difference is all in the fact that
isolate scope is disconnected from the
$scope tree, overrides local controller
$scope. Plus flexible eval()…
scope.hobby = attrs.hobby;
scope: {hobby: “@”}
87. Final Thoughts
Your directives can have directives.
(In them and on them.) Truly, it can
be directives all the way down…