Angular Directives from Scratch

88
Angular Directives by: Christian Lilley about.me/xml @xmlilley Demos Repo: github.com/xmlilley/ng-demos

description

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.

Transcript of Angular Directives from Scratch

Page 1: Angular Directives from Scratch

Angular Directives

!

by: Christian Lilley about.me/xml @xmlilley

Demos Repo: github.com/xmlilley/ng-demos

Page 2: Angular Directives from Scratch

What are Directives?

Page 3: Angular Directives from Scratch
Page 4: Angular Directives from Scratch
Page 5: Angular Directives from Scratch

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.

Page 6: Angular Directives from Scratch

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…

Page 7: Angular Directives from Scratch

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…

Page 8: Angular Directives from Scratch
Page 9: Angular Directives from Scratch

They can BYO DOM, or just be declared on inline DOM.

Fine. How about…

Page 10: Angular Directives from Scratch
Page 11: Angular Directives from Scratch

What AREN’T they?Directives.

Aren’t.

Just.

Where.

Your.

JQuery.

Goes!

Please,

God:

No.

Page 12: Angular Directives from Scratch

I

BUT...

Page 13: Angular Directives from Scratch

I

Moar

“The Superheroic

MVW framework.”

Page 14: Angular Directives from Scratch

Angular isn’t just another way to

organize the same old UI code!!!

Page 15: Angular Directives from Scratch

Opinionated Principles

1. Declarative, Model-Driven Behavior

Page 16: Angular Directives from Scratch

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.

Page 17: Angular Directives from Scratch

Declarativeness ROCKSYou’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>

Page 18: Angular Directives from Scratch

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.

Page 19: Angular Directives from Scratch

Opinionated Principles

1. Declarative, Model-Driven Behavior

2. Modularity, Reusability across contexts: Write Once, Run Anywhere

Page 20: Angular Directives from Scratch

ReusabilityIt’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.

Page 21: Angular Directives from Scratch

<div id="header_tabs"><a href="#/home" active-tab="1">HOME</a><a href="#/finance" active-tab="1">Finance</a><a href="#/hr" active-tab="1">Human Resources</a><a href="#/quarterly" active-tab="1">Quarterly</a>

</div>

AND...<div id="subnav_tabs"><a href="#/hr/pay" active-tab="2">Pay</a><a href="#/hr/benefits" active-tab="2">Benefits</a><a href="#/hr/help" active-tab="2">Help</a>

</div>

Page 22: Angular Directives from Scratch

1. Declarative, Model-Driven Behavior

2. Modularity, Reusability across contexts: Write Once, Run Anywhere

3. Keep it Local

Opinionated Principles

Page 23: Angular Directives from Scratch

No...

Page 24: Angular Directives from Scratch

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:

Page 25: Angular Directives from Scratch

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 eu, feugiat vel massa. Fusce sem

$899.99 Buy Now!

Cart: 1 Item(s)

Clicking Here

Needs to Change

Things Here

Page 26: Angular Directives from Scratch

Let’s Build Some!

Page 27: Angular Directives from Scratch

Directive NamesAngular 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>

Page 28: Angular Directives from Scratch

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.)

Page 29: Angular Directives from Scratch

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() {})

Page 30: Angular Directives from Scratch

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.

Page 31: Angular Directives from Scratch

What do We Do With That Factory Function?

Page 32: Angular Directives from Scratch

Two Basic Options: Return a

Config Object or a ‘Linking Function’

Page 33: Angular Directives from Scratch
Page 34: Angular Directives from Scratch
Page 35: Angular Directives from Scratch

You’ll See Later, But Ignore For Today:

Returning only the Link function

Link vs. Compile

Pre-Link vs. Post-Link

Page 36: Angular Directives from Scratch
Page 37: Angular Directives from Scratch

Using a Config Objectangular.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.

Page 38: Angular Directives from Scratch

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> }})

Page 39: Angular Directives from Scratch

Link Function Args3 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.

Page 40: Angular Directives from Scratch

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

Page 42: Angular Directives from Scratch

Using jqLite (angular.element) .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> }})

$(‘selector’).bind(‘mouseenter’, function() {})

Page 43: Angular Directives from Scratch

ACK! THPPPT!!! !

.bind() is ancient! !

Where’s .live() ?!? !

.on() ?!?

Page 44: Angular Directives from Scratch
Page 45: Angular Directives from Scratch

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.

Page 46: Angular Directives from Scratch

Directive TemplatesTemplates can be stored as strings on the `template:` property

They can also be loaded from a file, using: `templateUrl: path/to/file/template.html’

Page 47: Angular Directives from Scratch

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’})

Page 48: Angular Directives from Scratch

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> }})

Page 49: Angular Directives from Scratch

The Restrict PropertyRemember that directives are re-usableSo, 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’.

Page 50: Angular Directives from Scratch

The Replace PropertyBy 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...

Page 51: Angular Directives from Scratch

Directives DEMO BREAKDOWN 1:

Enter-Leave

Page 52: Angular Directives from Scratch

So, about that: Model-Driven & Local Directive Design...

Page 53: Angular Directives from Scratch

Specifically, the Model-Driven

part...

Page 54: Angular Directives from Scratch

Why Model-Driven?

After all, the imperative approach works fine...

...if you’re omniscient and precognitive.

... and you really, really like refactoring.

Page 55: Angular Directives from Scratch

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."

Page 56: Angular Directives from Scratch

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.”

Page 57: Angular Directives from Scratch

How Can Directives React to Models?

With $watch!

Page 58: Angular Directives from Scratch

Directives DEMO BREAKDOWN 2:

CLOCK

(haha! Get it? We’re going to use a clock to demo $watch...)

Page 59: Angular Directives from Scratch

Keeping Directive Design Local

Page 60: Angular Directives from Scratch

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()

Page 61: Angular Directives from Scratch

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?

Page 62: Angular Directives from Scratch

$rootScope

Main/Nav Controller

$scope

View 1 Controller

$scope

View 2 Controller

$scope

Directive A-1$scope

Directive B$scope

Directive A-2$scope

Directive C$scope

???

??????

???

???

Page 63: Angular Directives from Scratch

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.

Page 64: Angular Directives from Scratch
Page 65: Angular Directives from Scratch

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.

Page 66: Angular Directives from Scratch

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)

Page 67: Angular Directives from Scratch

Directives DEMO BREAKDOWN 3:

ActiveTab

Page 68: Angular Directives from Scratch

Directives DEMO BREAKDOWN 4:

ClickDetect

Page 69: Angular Directives from Scratch

And if we have time…

Page 70: Angular Directives from Scratch

`$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.

Page 71: Angular Directives from Scratch

Link & CompileAngular’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.

Page 72: Angular Directives from Scratch

Link vs. CompileCompile: “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.

Page 73: Angular Directives from Scratch

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.

Page 74: Angular Directives from Scratch

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> }})

Page 75: Angular Directives from Scratch

And just to add more complexity to that

structure...

Page 76: Angular Directives from Scratch

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:

Page 77: Angular Directives from Scratch

Pre- vs. Post- LinkPRE-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.

Page 78: Angular Directives from Scratch

Back to challenging but useful stuff...

Page 79: Angular Directives from Scratch

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

Page 80: Angular Directives from Scratch

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.

Page 81: Angular Directives from Scratch

Isolate ScopeWe 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

Page 82: Angular Directives from Scratch

Creating Isolate ScopeCreating 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: “@”}}})

Page 83: Angular Directives from Scratch

Cool! But what’s that ‘@’ doing there?

Page 84: Angular Directives from Scratch

Isolate Scope Data-BindingAngular 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”>

Page 85: Angular Directives from Scratch

Data-Binding OperatorsBy default, an operator alone will be assumed to refer to a same-named attrAlternately, use form ‘@hobby’ to specifyOptions:

‘@’- 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.

Page 86: Angular Directives from Scratch

Isolate Scope Data-BindingThe 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: “@”}

Page 87: Angular Directives from Scratch

Final ThoughtsYour directives can have directives. (In them and on them.) Truly, it can be directives all the way down…

Page 88: Angular Directives from Scratch

Thank You! !

about.me/XML