Angular JS – Directive Crash Course – AngularJS Direktiven



Angular JS – Directive Crash Course – AngularJS Direktiven

0 0


bedcon-angular-directives

Slides of my bedcon 2014 presentation: AngularJS directive crash course

On Github mo-gr / bedcon-angular-directives

Angular JS

Directive Crash Course

Moritz Grauel / @mo_gr

Moritz Grauel

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

Freelance Software Entwickler & Trainer

Web – JVM – iOS

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

Strip x- und data- Konvertiere : zu camelCase Konvertiere - zu camelCase Konvertiere _ zu camelCase
app.directive('fooBar', ...)
                <foo-bar/>
                <foo:bar/>
                <foo_bar/>
                

Arten von Direktiven

Tag
<my-user/>
Attribut
<div my-user='user'>
CSS Klasse
<div class='my-user: user;'>
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:

Test Boilerplate – Jasmine Test Runner – Karma 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

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

Demo: drinks

isolate Scope

ermöglicht definiertes Interface einer Direktive

'@' – unidirektionales Mapping in den Direktiven Scope '=' – bidirektionales Mapping zwischen umliegendendem Scope und Direktive '&' – ermöglicht function calls im umliegendem Scope

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

Moritz – @mo_gr - #bedcon