Angular JS

Directive Crash Course

Moritz Grauel / @mo_gr

Driving School Car Crash
How to draw an owl

Moritz Grauel

Berlin native. Bit herder. Code connoisseur. Coffee lover.

Freelance Software Entwickler & Trainer

Web – JVM – iOS

AngularJS Logo

Superheroic JavaScript MVW Framework

  • Client Side MVC Framework
  • ehemals 20% Projekt bei Google
  • > 5 Jahre
  • aktuell 1.2.15
  • aktive Community, > 400 Contributor
  • Github, MIT

AngularJS Killer Features

  • Modulare Anwendungsstruktur & Testbarkeit
  • Dependency Injection
  • 2-way Data Binding
  • Template System

Demo: hello-angular

AngularJS Direktiven

ng-app, ng-controller, ng-click, ...

Nicht nur Attribute, auch Elemente

Direktiven sind Marker im DOM

für JavaScript execution

jQuery vs Direktiven

$("#content form.form ul ~ input + div").on("click", fmlHandler);

vs.

You can't touch this!
You can't touch this!
You can't touch this!

Eigene Direktiven

Demo: hello-directive

Was gehört in eine Direktive?

  • DOM Interaktion
  • 3rd Party JS Bibliotheken
  • UI Widgets
  • semantisches Templating

semantisches Templating



    

Was gehört in eine Direktive?

  • DOM Interaktion
  • 3rd Party JS Bibliotheken
  • UI Widgets
  • semantisches Templating

Angular-UI

  • Twitter Bootstrap (und weitere)
  • als direktiven verpackt
  • gutes Beispiel zum lernen

Keep 'em stupid

  • wenig/keine Logik in direktiven
  • kein AJAX etc
  • DOM ist Komplexität genug
  • Business Logik gehört in services

Direktiven Deklarieren

  • immer in Modul Kontext
  • directive() Funktion

                    var module = angular.module('Example', []);
                    module.directive('directiveName', function() {
                        return {
                          // describe directive
                        };
                    });
                

Normalisierter Name

  1. Strip x- und data-
  2. Konvertiere : zu camelCase
  3. Konvertiere - zu camelCase
  4. Konvertiere _ zu camelCase
app.directive('fooBar', ...)

                <foo-bar/>
                <foo:bar/>
                <foo_bar/>
                

Arten von Direktiven

  1. Tag
    <my-user/>
  2. Attribut
    <div my-user='user'>
  3. CSS Klasse
    <div class='my-user: user;'>
  4. Kommentar
    <!-- directive: my-user user -->

Direktiven Beschreiben


            var module = angular.module('Example', []);
            module.directive('directiveName', function() {
              return {
                // describe directive
                link: linkFunction,
                restrict: 'EA'
                //...etc
              };
            });
                

link

Link-Funktion

nimmt nicht am DI teil


            link: function (scope, element, attribute) {
                // element is jqLite/jQuery wrapped
                // code to run on/with that element
            }
                

restrict

Wo kann die directive verwendet werden

String aus den Zeichen:

  • 'E' Element
  • 'A' Attribut (default)
  • 'C' Klasse
  • 'M' Kommentar

            {
              // snip
              restrict: 'EA',
              // ...snip
            }
                

template / templateUrl

neue DOM Elemente in die Direktiven einfügen

entweder inline oder über Angulars templateCache


            app.directive('placeholder', function () {
              return {
                restrict: 'E',
                template: '
real deal!
' }; });

replace

neues template in DOM Element einfügen oder ersetzen

default ist false


            app.directive('placeholder', function () {
              return {
                restrict: 'E',
                replace: true,
                template: '
real deal!
' }; });

Noise Direktive

  • halbwegs sinnvolles Beispiel
  • 3rd Party Bibliothek Howler.js einbinden
  • Direktive, die auf click einen Sound abspielt

Howler.js Minimal Beispiel


                    var sound = new Howl({
                      urls: ['path/to/sound.mp3']
                    });
                    sound.play();
                

TDD

Direktiven & Tests

Wir brauchen:

  1. Test Boilerplate – Jasmine
  2. Test Runner – Karma
  3. Angular Boilerplate – Angular Mocks

Demo: sound

Noise Direktive


            app.directive('noise', function($window) {
              return {
                link: function (scope, element, attrs) {
                  var sound = new $window.Howl({
                    urls: [attrs.noise]
                  });
                  element.on('click', function() {sound.play();});
                }
              };
            })
                

Direktiventest Boilerplate


describe('MyDirective Test', function () {
    var scope, element;
    // Load module
    beforeEach(module('MyModule'));
    // prepare directive
    beforeEach(inject(function($compile, $rootScope) {
        scope = $rootScope.$new();
        element = angular.element('
Test
'); $compile(element)(scope); })); // actual tests it('should do something', function() { expect(something).toHaveHappened(); }); });

3rd Party Integration – Rückrichtung

  • Callbacks und Eventlistener
  • Angulars Digest Cycle muss getriggert werden
  • scope.$apply() ist der Schlüssel

Noise Direktive II.


    app.directive('noise', function($window) {
        return {
            link: function (scope, element, attrs) {
                var sound = new $window.Howl({
                    urls: [attrs.noise],
                    onplay: function () {
                        scope.$apply(function () {
                            scope.playing = attrs.noise;
                        });
                    }
                });
                element.on('click', function() {sound.play();});
            }
        };
    })
                

Typische Gefahr

  • allmächtige Link-Funktion
  • schwer zu Testen
  • code smell

controller

  • Direktiven Controlloer
  • inline oder via Name
  • ermöglicht Services, bessere Testbarkeit etc

    app.directive('complexDirective', function () {
        return {
            controller: 'complexDirectiveController',
            link: function(scope, elem, attr, controller) {
                controller.init(elem);
            };
        };
    });
    app.controller('complexDirectiveController',
                   function($scope, ComplexService) {
        // magic happens here
    });
                

Direktiven Kommunikation via Controller

  • Direktiven können Controller von anderen Direktiven verlangen
  • Klassisches Beispiel: cross field validation

Demo: cross-field

require

  • Name oder Array von Namen anderer Direktiven
  • wird als 4. Parameter an die Link Funktion gegeben
  • Prefix '?' wirft keinen Fehler, wenn andere Direktive nicht vorhanden
  • Prefix '^' sucht im DOM weiter oben

app.directive('fooBar', function() {
    return {
        controller: 'fooBarController',
        require: ['reqDir', '?^optDir', 'fooBar'],
        link: function(s, e, a, controllers) {
            var reqDir = controllers[0];
            var ownController = controllers[2];
            if (controllers[1]) controllers[1].interact(reqDir);

        };
    }
});
                

transclude

  • ermöglicht das Einbinden von Direktiven Inhalt in Templates
  • klassisches Beispiel: MessageBox
  • assoziiert scope

app.directive('messageBox', function() {
    return {
        transclude: true,
        template: '
' + '

Alert!

' + '
' + '
' }; });
Something went wrong: {{message}}

scope?

  • Kleber zwischen View & Controller
  • hierarchich (prototypisch vererbt)
  • beliebter Stolperstein

scope

  1. false – Direktive hat keinen eigenen Scope (default)
  2. true – Direktive hat eigenen Child Scope
  3. {} – Direktive hat eigenen, isolierten Scope

Demo: drinks

isolate Scope

ermöglicht definiertes Interface einer Direktive

  1. '@' – unidirektionales Mapping in den Direktiven Scope
  2. '=' – bidirektionales Mapping zwischen umliegendendem Scope und Direktive
  3. '&' – ermöglicht function calls im umliegendem Scope
Isolate Scopes

War's das?

weitere erlaubte Properties

  • priority
  • terminal
  • compile

Direktiven Deklarieren - Komplett


    app.directive('example', function(MagicService) {
        // lazy one-time init
        return {
            restrict: 'EA',
            link: function(scope, elem, attrs, controllers) {
                // called per usage
            },
            // or compile: compileFn,
            templateUrl: 'neatTemplate.html',
            ,// or template: '...'
            replace: true,
            transclude: true,
            scope: {foo:'='},
            controller: 'aController',
            require: ['?^other', 'example']
            priority: 100,
            terminal: false
        };
    });
        

Take Home Message

  • Direktiven sind ein guter Weg zu wart- und testbaren Anwendungen.
  • Direktiven ermöglichen deklaratives Markup.
  • Der Einstieg in eigene Direktiven ist simpel.
  • Die mögliche Flexibilität impliziert gewisse Komplexität.

Vielen Dank!

Fragen?

Slides auf GitHub

Get in touch!

@mo_gr

mo@notadomain.com

http://moritz.grauel.is/awesome