Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Lightweight AngularJS

Minko Gechev
November 29, 2014

Lightweight AngularJS

Light implementation of AngularJS.

Implement your own AngularJS in ~200 lines of code.

Minko Gechev

November 29, 2014
Tweet

More Decks by Minko Gechev

Other Decks in Programming

Transcript

  1. 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"] }
  2. Table of Contents • Introduction to AngularJS • Light AngularJS

    • DOM compiler • Provider • Scope • Services • Directives
  3. Purpose of the presentation • Learn how AngularJS works by

    building a simplified version of it • Understand design decisions behind the implementation of AngularJS
  4. 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
  5. 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.
  6. <html ng-app> <!-- Body tag augmented with ngController directive -->

    <body ng-controller="MyController"> <input ng-model="foo" value="bar"> <!-- Button tag with ng-click directive, and string expression 'buttonText' wrapped in "{{ }}" markup --> <button ng-click="changeFoo()">{{buttonText}}</button> <script src="angular.js"></script> </body> </html>
  7. 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.
  8. function MyController($scope) { $scope.buttonText = 'Click me to change foo!';

    $scope.foo = 42; $scope.changeFoo = function () { $scope.foo += 1; alert('Foo changed'); }; }
  9. 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.
  10. <div ng-controller="BaseCtrl"> <div id="child" ng-controller="ChildCtrl"> <button id=“parent-method" ng-click="foo()">Parent method</button> <button

    ng-click="bar()">Child method</button> </div> </div> <script> function BaseCtrl($scope) { $scope.foo = function () { alert('Base foo'); }; } function ChildCtrl($scope) { $scope.bar = function () { alert('Child bar'); }; } </script>
  11. <script> 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); }); } }; }); </script> <alert-button content="42">Click me</alert-button>
  12. 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.
  13. <script> myModule.filter('uppercase', function () { return function (str) { return

    (str || '').toUpperCase(); }; }); </script> <div>{{ name | uppercase }}</div> <script> function MyCtrl($scope, uppercaseFilter) { $scope.name = uppercaseFilter('foo'); //FOO } </script>
  14. 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.
  15. 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(); }
  16. Restrictions • Our light AngularJS provides basic support of only:

    • Controllers • Directives • Services • Scope
  17. 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
  18. DOMCompiler Responsibilities: • Compiles the DOM • Traverse the whole

    DOM tree • Finds registered directives • Invokes the logic associated with them • Manages the current scope
  19. 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
  20. 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
  21. 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
  22. 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; } //...
  23. Sample calls Provider.directive('ng-bind', function () { return { link: function

    (scope) { //... } }; }); Provider.directive('ng-repeat', function () { return { scope: true, link: function (scope) { //... } }; });
  24. A typical problems in graph theory • Check whether given

    vertex has specific property • Visit a subset of the the vertices of given graph • …
  25. Traverse the DOM tree Two basic algorithms: • Breath-First Search

    (BFS) • Finds the shortest path between nods • Depth-First Search (DFS) • Finds any path
  26. 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.
  27. 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
  28. 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); } } }
  29. 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: {}
  30. 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); }
  31. //... compile: function (el, scope) { scope = this.callDirectives(el, scope);

    [].forEach.call(el.children || [], function (d) { this.compile(d, scope); // Recursive call }.bind(this)); } //...
  32. //... 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; } //...
  33. 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); };
  34. 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 } };
  35. Provider.directive('ngl-click', function () { 'use strict'; return { scope: false,

    link: function (el, scope, exp) { el.onclick = function () { scope.$eval(exp); scope.$digest(); }; } }; });
  36. 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; }); } }; });
  37. 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; }); } }; });
  38. setTimeout & setInterval <script> function Ctrl($scope) { $scope.counter = 0;

    setTimeout(function () { $scope.counter += 1; }, 1000); } </script> <div ng-controller="Ctrl"> <span ng-bind="counter"></span> </div>
  39. setTimeout & setInterval <script> function Ctrl($scope) { $scope.counter = 0;

    setTimeout(function () { $scope.counter += 1; $scope.$digest(); //Note that in AngularJS you should use $apply }, 1000); } </script> <div ng-controller="Ctrl"> <span ng-bind="counter"></span> </div>
  40. $timeout Provider.service('timeout', function ($rootScope) { 'use strict’; return function (fn,

    timeout) { setTimeout(function () { fn(); $rootScope.$digest(); }, timeout); }; });
  41. Resources • Source code of AngularJS • AngularJS tutorial •

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