Slide 1

Slide 1 text

Lightweight AngularJS Minko Gechev

Slide 2

Slide 2 text

About me Freelancer, passionate about the web! github.com/mgechev twitter.com/mgechev { "name": "Minko Gechev", "job": "Software Engineer", "responsibilities": ["development", "oo design", "trainings", “consultancy”], "interests": ["javascript", "webrtc", "algorithms", "data structures", "design patterns", "karate"] }

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

Table of Contents • Introduction to AngularJS • Light AngularJS • DOM compiler • Provider • Scope • Services • Directives

Slide 5

Slide 5 text

Purpose of the presentation • Learn how AngularJS works by building a simplified version of it • Understand design decisions behind the implementation of AngularJS

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

Introduction to AngularJS

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

AngularJS AngularJS is a JavaScript framework developed by Google. It intends to provide a solid base for the development of CRUD Single-Page Applications (SPA). • two-way data binding • dependency injection • separation of concerns • testability • abstraction

Slide 10

Slide 10 text

Partials The partials are HTML strings. They may contain AngularJS expressions inside the elements or their attributes. One of the distinctions between AngularJS and the others frameworks is the fact that AngularJS' templates are not in an intermediate format which needs to be turned into HTML.

Slide 11

Slide 11 text

{{buttonText}}

Slide 12

Slide 12 text

Controllers The AngularJS controllers are JavaScript functions which help handling the user interactions with the web application (for example mouse events, keyboard events, etc.) by attaching methods to the scope. All required external components, for the controllers are provided through the Dependency Injection mechanism of AngularJS.

Slide 13

Slide 13 text

function MyController($scope) { $scope.buttonText = 'Click me to change foo!'; $scope.foo = 42; $scope.changeFoo = function () { $scope.foo += 1; alert('Foo changed'); }; }

Slide 14

Slide 14 text

Scope In AngularJS the scope is a JavaScript object which is exposed to the partials. The scope could contains different properties - primitives, objects or methods. All methods attached to the scope could be invoked by evaluation of AngularJS expression inside the partials associated with the given scope, or direct call of the method by any component which keeps reference to the scope.

Slide 15

Slide 15 text

Parent method Child method
function BaseCtrl($scope) { $scope.foo = function () { alert('Base foo'); }; } function ChildCtrl($scope) { $scope.bar = function () { alert('Child bar'); }; }

Slide 16

Slide 16 text

Directives In AngularJS the directives are the place where all DOM manipulations should be placed.

Slide 17

Slide 17 text

myModule.directive('alertButton', function () { return { template: '<button ng-transclude></button>', scope: { content: '@' }, replace: true, restrict: 'E', transclude: true, link: function (scope, el) { el.click(function () { alert(scope.content); }); } }; }); Click me

Slide 18

Slide 18 text

Filters The filters in AngularJS are responsible for encapsulating logic required for formatting data. Usually filters are used inside the partials but they are also accessible in the controllers, directives, services and other filters through Dependency Injection.

Slide 19

Slide 19 text

myModule.filter('uppercase', function () { return function (str) { return (str || '').toUpperCase(); }; });
{{ name | uppercase }}
function MyCtrl($scope, uppercaseFilter) { $scope.name = uppercaseFilter('foo'); //FOO }

Slide 20

Slide 20 text

Services Every piece of logic which doesn't belong to the components described above should be placed inside a service. Usually services encapsulate the domain specific logic, persistence logic, XHR, WebSockets, etc.

Slide 21

Slide 21 text

myModule.factory('Developer', function () { return function Developer() { this.name = 'Foo'; this.motherLanguage = 'JavaScript'; this.code = function () {}; this.live = function () { while (true) { this.code(); } }; }; }); function MyCtrl(Developer) { var developer = new Developer(); developer.live(); }

Slide 22

Slide 22 text

Light AngularJS implementation

Slide 23

Slide 23 text

Restrictions • Our light AngularJS provides basic support of only: • Controllers • Directives • Services • Scope

Slide 24

Slide 24 text

And is on only ~200 SLOC

Slide 25

Slide 25 text

DOMCompiler Provider Scope

Slide 26

Slide 26 text

Provider Responsibilities: • Register components (directives, services, controllers) • Resolves dependencies of the components • Initialize the components

Slide 27

Slide 27 text

Provider interface • get(name,  locals) - returns given service   • invoke(fn,  locals) - initialize given service   • directive(name,  fn) - registers a directive   • controller(name,  fn) - registers a controller   • service(name,  fn) - registers a service   • annotate(fn) - returns an array of the dependencies of given service

Slide 28

Slide 28 text

DOMCompiler Responsibilities: • Compiles the DOM • Traverse the whole DOM tree • Finds registered directives • Invokes the logic associated with them • Manages the current scope

Slide 29

Slide 29 text

DOMCompiler interface • bootstrap() - bootstraps the app   • callDirectives(el,  scope) - invokes directive associated to given element   • compile(el,  scope) - compiles the subtree with root given element

Slide 30

Slide 30 text

Scope • Watches expressions • Evaluates all watched expressions on each $digest loop • Invokes the associated logic on change of the expression result, compared to the previous call of $digest

Slide 31

Slide 31 text

Scope interface • $watch(exp,  fn) - associates given function to an expression   • $eval(exp) - evaluates given expression   • $new() - creates new scope, child of the current   • $destroy() - destroys given scope   • $digest() - performs dirty checking

Slide 32

Slide 32 text

No content

Slide 33

Slide 33 text

Lets start one by one

Slide 34

Slide 34 text

Provider

Slide 35

Slide 35 text

Register Components //... directive: function (name, fn) { this._register(name + Provider.DIRECTIVES_SUFFIX, fn); }, controller: function (name, fn) { this._register(name + Provider.CONTROLLERS_SUFFIX, function () { return fn; }); }, service: function (name, fn) { this._register(name, fn); }, _register: function (name, factory) { this._providers[name] = factory; } //...

Slide 36

Slide 36 text

Sample calls Provider.directive('ng-bind', function () { return { link: function (scope) { //... } }; }); Provider.directive('ng-repeat', function () { return { scope: true, link: function (scope) { //... } }; });

Slide 37

Slide 37 text

Creating components…

Slide 38

Slide 38 text

But before that…a little theory…

Slide 39

Slide 39 text

Graph theory

Slide 40

Slide 40 text

Why?!

Slide 41

Slide 41 text

Says: “Formalize all the things!!” FTW!

Slide 42

Slide 42 text

Graph is… Ordered pair of two sets: G = { V, E }, E ⊆ VxV

Slide 43

Slide 43 text

Or simply… A B E D C

Slide 44

Slide 44 text

Why the f#%& we need math here?

Slide 45

Slide 45 text

HTML

Slide 46

Slide 46 text

DOM Tree html head body p div

Slide 47

Slide 47 text

Dependency graph $resource $q $http $httpBackend $browser

Slide 48

Slide 48 text

A typical problems in graph theory • Check whether given vertex has specific property • Visit a subset of the the vertices of given graph • …

Slide 49

Slide 49 text

How to find all elements with associated directive in given DOM tree?

Slide 50

Slide 50 text

DOM Tree === Acyclic undirected graph

Slide 51

Slide 51 text

Traverse the DOM tree Two basic algorithms: • Breath-First Search (BFS) • Finds the shortest path between nods • Depth-First Search (DFS) • Finds any path

Slide 52

Slide 52 text

DFS!

Slide 53

Slide 53 text

No content

Slide 54

Slide 54 text

We can represent graphs by… • A matrix A, where A[i][j] will be equals to 1 if and only if there is a path between i and j, otherwise it’ll be equals to 0.

Slide 55

Slide 55 text

Example… 0 3 4 1 2 0 1 1 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0

Slide 56

Slide 56 text

DFS JavaScript implementation function dfs(graph, current, visited) { 'use strict'; visited = visited || []; if (visited[current]) { return; } visited[current] = true; for (var k = 0; k < graph.length; k += 1) { if (graph[current][k] && !visited[k]) { dfs(graph, k, visited); } } }

Slide 57

Slide 57 text

Lets go back to our implementation

Slide 58

Slide 58 text

Provider…get a service

Slide 59

Slide 59 text

directive: function (name, fn) { … }, controller: function (name, fn) { … }, service: function (name, fn) { … }, _register: function (name, factory) { … }, get: function (name, locals) { if (this._cache[name]) { return this._cache[name]; } var provider = this._providers[name]; if (!provider || typeof provider !== 'function') { return null; } return (this._cache[name] = this.invoke(provider, locals)); }, _cache: {}

Slide 60

Slide 60 text

annotate: function (fn) { var res = fn.toString() .replace(/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg, '') .match(/\((.*?)\)/); if (res && res[1]) { return res[1].split(',').map(function (d) { return d.trim(); }); } return []; }, invoke: function (fn, locals) { locals = locals || {}; var deps = this.annotate(fn).map(function (s) { return locals[s] || this.get(s, locals); // Indirect recursion }, this); return fn.apply(null, deps); }

Slide 61

Slide 61 text

No content

Slide 62

Slide 62 text

Before minification this.annotate(function ($http, $q, $browser) { //... }); // [“$http”, “$q”, “$browser”]

Slide 63

Slide 63 text

After minification… this.annotate(function (a, b, c) { //... }); // [“a”, “b”, “c”]

Slide 64

Slide 64 text

one more problem…

Slide 65

Slide 65 text

A E C B D

Slide 66

Slide 66 text

A E C B D

Slide 67

Slide 67 text

a cycle…

Slide 68

Slide 68 text

No content

Slide 69

Slide 69 text

upsss….

Slide 70

Slide 70 text

No content

Slide 71

Slide 71 text

DOMCompiler

Slide 72

Slide 72 text

//... compile: function (el, scope) { scope = this.callDirectives(el, scope); [].forEach.call(el.children || [], function (d) { this.compile(d, scope); // Recursive call }.bind(this)); } //...

Slide 73

Slide 73 text

//... callDirectives: function (el, $scope) { var isCreated = false; [].map.call(el.attributes || [], function (attr) { var directive = Provider.get(attr.name + Provider.DIRECTIVES_SUFFIX); return directive && { expr: attr.value, provision: directive }; }) // takes all expressions with value evaluated to true // i.e. if such directive exists the literal will // be returned .filter(Boolean) .forEach(function (d) { if (d.provision.scope && !isCreated) { isCreated = true; $scope = $scope.$new(); } d.provision.link(el, $scope, d.expr); }); return $scope; } //...

Slide 74

Slide 74 text

Scope

Slide 75

Slide 75 text

$rootScope $scope0 $scope1 $scope3 $scope4 $scope2

Slide 76

Slide 76 text

function Scope(parent, id) { 'use strict'; this.$$watchers = []; this.$$children = []; this.$parent = parent; this.$id = id || 0; }

Slide 77

Slide 77 text

Scope.prototype.$new = function () { 'use strict'; Scope.counter += 1; var obj = new Scope(this, Scope.counter); Object.setPrototypeOf(obj, this); this.$$children.push(obj); return obj; }; Scope.prototype.$destroy = function () { 'use strict'; var pc = this.$parent.$$children; pc.splice(pc.indexOf(this), 1); };

Slide 78

Slide 78 text

No content

Slide 79

Slide 79 text

Dirty Checking

Slide 80

Slide 80 text

Scope.prototype.$watch = function (exp, fn) { 'use strict'; this.$$watchers.push({ exp: exp, fn: fn, last: Utils.clone(this.$eval(exp)) }); };

Slide 81

Slide 81 text

Scope.prototype.$digest = function () { 'use strict'; var dirty, watcher, current, i; do { dirty = false; for (i = 0; i < this.$$watchers.length; i += 1) { watcher = this.$$watchers[i]; current = this.$eval(watcher.exp); if (!Utils.equals(watcher.last, current)) { watcher.last = Utils.clone(current); dirty = true; watcher.fn(current); } } } while (dirty); for (i = 0; i < this.$$children.length; i += 1) { this.$$children[i].$digest(); // Recursive call } };

Slide 82

Slide 82 text

and a few directives…

Slide 83

Slide 83 text

Provider.directive('ngl-click', function () { 'use strict'; return { scope: false, link: function (el, scope, exp) { el.onclick = function () { scope.$eval(exp); scope.$digest(); }; } }; });

Slide 84

Slide 84 text

one-way binding…

Slide 85

Slide 85 text

Provider.directive('ngl-bind', function () { 'use strict'; return { scope: false, link: function (el, scope, exp) { el.innerHTML = scope.$eval(exp); scope.$watch(exp, function (val) { el.innerHTML = val; }); } }; });

Slide 86

Slide 86 text

two-way binding

Slide 87

Slide 87 text

Provider.directive('ngl-model', function () { 'use strict'; return { link: function (el, scope, exp) { el.onkeyup = function () { scope[exp] = el.value; scope.$digest(); }; scope.$watch(exp, function (val) { el.value = val; }); } }; });

Slide 88

Slide 88 text

lets take a look at the following snippet…

Slide 89

Slide 89 text

setTimeout & setInterval function Ctrl($scope) { $scope.counter = 0; setTimeout(function () { $scope.counter += 1; }, 1000); }

Slide 90

Slide 90 text

how AngularJS knows count has been changed?

Slide 91

Slide 91 text

…it doesn’t

Slide 92

Slide 92 text

setTimeout & setInterval function Ctrl($scope) { $scope.counter = 0; setTimeout(function () { $scope.counter += 1; $scope.$digest(); //Note that in AngularJS you should use $apply }, 1000); }

Slide 93

Slide 93 text

$timeout Provider.service('timeout', function ($rootScope) { 'use strict’; return function (fn, timeout) { setTimeout(function () { fn(); $rootScope.$digest(); }, timeout); }; });

Slide 94

Slide 94 text

how about 2k-3k bindings?

Slide 95

Slide 95 text

then dirty checking is not reliable…

Slide 96

Slide 96 text

No content

Slide 97

Slide 97 text

Object.observe Already in Chrome

Slide 98

Slide 98 text

No content

Slide 99

Slide 99 text

Thank you! github.com/mgechev twitter.com/mgechev

Slide 100

Slide 100 text

Resources • Source code of AngularJS • AngularJS tutorial • AngularJS Dev Guide • Source code from the slides • AngularJS in Patterns • AngularJS style guide