Anzeige
Anzeige
Artikel

JavaScript-Framework AngularJS: HTML aufgepimpt

Schon wieder ein JavaScript-Framework. Noch einfacher und noch flexibler als alle anderen soll sie angeblich sein. Das zumindest verspricht der Hersteller, kein Geringerer als Google. Das Besondere an AngularJS: Ein klares Bekenntnis zu klassischem HTML. Und ein paar Erweiterungen.

7 Min.
Artikel merken
Anzeige
Anzeige

Auf den ersten Blick kommt AngularJS wie ein Baukasten für ungeduldige Feierabend-Entwickler daher. Die optisch schlicht gehaltene Startseite der Homepage [1] vermittelt grob die vermeintliche Grundidee des Frameworks: die Erweiterung von HTML, um Benutzereingaben und Komponenten interagieren zu lassen.

Anzeige
Anzeige

Doch keiner dieser Eindrücke wird dem Framework tatsächlich gerecht. Denn das Projekt [2] entpuppt sich als ein komplexes Zusammenspiel moderner Technologien und birgt höchste Flexibilität für professionelle Frontend-Entwicklung. Dieser Artikel beleuchtet einige grundlegende Konzepte anhand des auf GitHub bereitgestellten Beispiels „OpenWeather App““ [3]. Eine Anwendung zur ortsbezogenen Suche nach Wetterdaten, die diese nach Tagen ordnet und in Panels darstellt.

HTML als Basis für den Bau

Die zunehmend browserübergreifende Unterstützung von DOM-Standards macht HTML, JavaScript und CSS heute zu Schlüsseltechnologien für den Einsatz im Frontend-Bereich dynamisch wachsender Applikationen. Viele JavaScript-Frameworks wählen dafür diverse Ausprägungen des Model-View-Controller-Konzepts (MVC) und verzichten auf die Möglichkeit, HTML um eigene Namensräume zu erweitern. Letztere erlauben individuelle Elemente und Attribute innerhalb einer spezifischen „HTML-Domäne“.

Anzeige
Anzeige

An diesem Punkt setzt AngularJS an und erklärt die Verwendung von HTML zu seinem zentralen Konzept: Tags, Direktiven und Attribute mit dem Präfix „ng-“ erweitern das HTML-Vokabular und bilden eine flexible Möglichkeit, die Interface-Beschreibung zu präzisieren. Der direkte Eingriff in das DOM, wie ihn etwa jQuery pflegt, soll nach Möglichkeit komplett vermieden werden.

Anzeige
Anzeige

Auf diese Weise lassen sich etwa Kontrollstrukturen definieren (ng-repeat), Ereignisbehandlungen sowie für den Kontext benötigte Controller-Objekte referenzieren oder die in einem zentralen Routing-Objekt definierte Haupt-View (ng-view) einbinden. In unserem App-Beispiel wird das Hauptmodul „openWeatherApp“ im Wurzel-Knoten (<html>) der Index-Datei mit Hilfe von ng-app referenziert und somit implizit beim ersten Aufruf der Seite geladen.

Modularisiertes JavaScript

JavaScript-Applikationslogik wird bei AngularJS in Module gekapselt. Grund dafür ist eine weitere zentrale Eigenschaft des Frameworks: Dependency Injection (DI). Vereinfacht ausgedrückt erlaubt DI den Objekten, Abhängigkeiten nur zu benennen. Um die eigentliche Lokalisierung und Erzeugung der benötigten Objekte beziehungsweise Ressourcen kümmert sich das Framework. Ein Objekt braucht damit nur so viel von seiner Umgebung zu wissen, wie für die Erfüllung seiner eigentlichen Aufgabe nötig ist.

Anzeige
Anzeige

Dies macht den Code wesentlich übersichtlicher und vereinfacht den Austausch einzelner Komponenten. Außerdem lassen sich Programm-Module dank DI effektiver testen. Denn durch das Abtreten der Verantwortung für abhängige Objekte kann die eigentlich zu testende Funktionalität besser isoliert werden. Während des Tests werden Abhängigkeiten entsprechend auch nur in Form so genannter „Mock-Objekte“ bereitgestellt.

Modul-Definitionen in app.js
angular.module('openWeatherApp', [
        'ngRoute',
        'openWeatherApp.filters',
        'openWeatherApp.services',
        'openWeatherApp.directives',
        'openWeatherApp.controllers',
        'iso-3166-country-codes'
    ])
    .config(['$routeProvider', function($routeProvider) {
        $routeProvider.when('/forecast', {
            templateUrl: 'partials/forecast.html', 
            controller: 'OpenWeatherCtrl'
        });
        $routeProvider.otherwise({
          redirectTo: '/forecast'
        });
    }]);

Listing 1

Im konkreten OpenWeatherApp-Beispiel definiert das Hauptmodul der app.js weitere Module wie Controller, Services und Filter; AngularJS lädt diese wie erwähnt allein durch diese Definition automatisch nach. Der injizierte Router wird so konfiguriert, dass über den Aufruf der Route „/forecast“ das gleichnamige View-Template eingebunden, also ins DOM gerendert, und mit dem Controller openWeatherCtrl assoziiert wird.

AngularJS und der MVC-Ansatz: So sieht er aus

Der Controller agiert nun als Schaltzentrale der UI-Logik gemeinsam mit der Laufzeitumgebung, die Veränderungen in bestimmten DOM-Objekten überwacht. Dabei sorgt das Framework für eine klare Trennung der Darstellung vom Verhalten der UI. Auch AngularJS folgt also in Grundzügen dem MVC-Ansatz:

Anzeige
Anzeige
  • Das Modell ist eine Sammlung einfacher JavaScript-Objekte und kann somit aus beliebig primitiven oder komplexen Objekten aufgebaut werden. Anders als etwa in Ember.js gibt es zur Erzeugung von Modellen kein Objekt, von dem abgeleitet wird.
  • Die View wird deklarativ in HTML geschrieben und bindet Modelle, Controller und Zugriffsmethoden an bestimmte Elemente, definiert aber kein Verhalten.
  • Der Controller ist für das Verhalten verantwortlich und stellt für die Applikations- bzw. Geschäftslogik notwendige Daten, Eigenschaften und Methoden bereit. Er besitzt keine Kenntnisse über die View.

Der entscheidende Kniff ist, dass der Controller seine öffentlichen Eigenschaften und Zugriffsmethoden über die reservierte Variable $scope zur Verfügung stellt. Somit kann das Framework Datenbindungen zu Elementen der View herstellen, welche den spezifischen Controller verwenden.

Zurück zum App-Beispiel: In der View kann entweder über vordefinierte Ortsnamen-Buttons oder frei über ein Textfeld nach Wetterdaten für einen bestimmten Ort gesucht werden. In der View forecast.html wird dieses Formular beschrieben, mitsamt seiner Bindungen an das Modell bzw. den $scope des Controllers.

Such-Formular für Städte und Freitext
<form class="form-inline" role="form">
    <span class="btn-group" >
        <button 
            ng-repeat="item in exampleLocations" 
            ng-click="setLocation(item)" 
            type="submit" 
            class="btn btn-default">{{item}}
        </button>
    </span>
    <div class="form-group">
        <label class="sr-only" for="location">City</label>
        <input 
            ng-model="location" 
            type="text" 
            class="form-control" 
            id="location" 
            placeholder="City">
        </input>
    </div>
    <button ng-click="getForecastByLocation(location)" type="submit" class="btn btn-primary">Search!</button>
</form>

Listing 2

Über die Direktiven ng-repeat bzw. ng-model werden Eigenschaften des Controller-$scope an Buttons des Suchformulars gebunden.
Über die Direktiven ng-repeat bzw. ng-model werden Eigenschaften des Controller-$scope an Buttons des Suchformulars gebunden.

Zwei Wege, eine Wahrheit

Vergleichbar mit Ember.js oder Meteor wird auch von AngularJS automatisch ein Two-Way-Data-Binding für Variablen erzeugt, die in der View referenziert werden. Das bedeutet, dass sich Änderungen im Datenmodell unmittelbar in der View widerspiegeln – und umgekehrt. Basis dafür ist wieder der $scope als gemeinsamer Sichtbarkeitsbereich im Kontext des jeweiligen DOM-Elements.

Anzeige
Anzeige

In diesem Zusammenhang wird auch gerne von der „single source of truth“ gesprochen, welche Modell und View synchron und den Programmierer bei Laune hält. Denn das Konzept der bidirektionalen Datenbindung entlastet die oft mühselige und fehleranfällige Programmierung von View-Aktualisierungen durch direkte DOM-Manipulation. Letzteres ist bei AngularJS ohnehin nicht gern gesehen.

Controller OpenWeatherCtrl mit $scope
angular.module('openWeatherApp.controllers', [])
    // Controller for "open weather map" api data search
    .controller('OpenWeatherCtrl',
        ['$scope','openWeatherMap','exampleLocations','ISO3166',
            function($scope,openWeatherMap,exampleLocations,ISO3166) {
                // Expose example locations to $scope
                $scope.exampleLocations = exampleLocations;
                // On initialization load data for first example entry
                $scope.forecast = openWeatherMap.queryForecastDaily({
                    location: exampleLocations[0]
                });
                // Get forecast data for location as given in $scope.location
                $scope.getForecastByLocation = function() {
                    $scope.forecast = openWeatherMap.queryForecastDaily({
                        location: $scope.location
                    });
                };
                // Set $scope.location and execute search on API
                $scope.setLocation = function(loc) {
                    $scope.location = loc;
                    $scope.getForecastByLocation();
                };
            }
        ]
    )

Listing 3

Der Controller der Wetter-App (openWeatherCtrl) fällt letztlich knapper aus, als man zunächst denken mag. So wird etwa die Nutzung der Wetterdaten-API in ein Service-Modul verlagert und als Abhängigkeit injiziert. Die Aufgabe des Controllers besteht im Wesentlichen in der Bereitstellung der Suchfunktion (getForecastByLocation) sowie der dabei zurückgelieferten Wetterdaten. Diese werden wiederum direkt in eine $scope-Variable geschrieben, sodass die View automatisch an den Stellen aktualisiert wird, wo Daten aus diesem Modell referenziert werden.

Ausgaben verarbeiten

Hierbei kommen schließlich „Expressions“ zum Einsatz, erkennbar an der Einfassung in doppelten geschweiften Klammern. Diese Marker erlauben die Ein- und Ausgabeverarbeitung, also etwa die Validierung oder Formatierung von Werten, des definierten $scope. Im Beispiel der Wetter-App werden aus dem Objekt mit den Vorhersagedaten für einen bestimmten Tag Temperaturwerte mittels des Filters „number“ auf eine Nachkommastelle formatiert ausgegeben.

Anzeige
Anzeige

AngularJS stellt dafür eine Reihe nützlicher Filter für unterschiedliche Zwecke bereit. Um eigene Filter zu definieren, stellt das Framework darüber hinaus ein Konstrukt („filter“) zur Verfügung. Auch für eigene HTML-Direktiven liefert Google ein passendes Werkzeug: Mit „directives“ lassen sich flexible Controller-Funktionen mit View-Eigenschaften verknüpfen und in eigene HTML-Elemente gießen. Für die Wetter-App etwa Panels mit datumsbezogenen Wetterdaten und zugehörigem Bild. Weitere Beispiele finden sich in der umfangreichen Dokumentation des Frameworks.

Die fertige Wetter-App: Vorhersagen für die kommenden Tage werden in Panels dargestellt, die im HTML-Code über die eigens implementierte Direktive „weather-panel” deklariert wurden.
Die fertige Wetter-App: Vorhersagen für die kommenden Tage werden in Panels dargestellt, die im HTML-Code über die eigens implementierte Direktive „weather-panel” deklariert wurden.

Tests inklusive

Gerade bei der Entwicklung großer oder kontinuierlich wachsender Projekte spart das automatisierte Testen wertvolle Zeit zur formalen Validierung der implementierten Funktionalität. Zudem ermutigt es, von Anfang an klar strukturierten Code in kleinen überschaubaren Funktions-Einheiten zu verfassen.

AngularJS erleichtert das Schreiben und Testen des Codes darüber hinaus noch mittels klarer Trennung des Controller-Codes von der View. Dependency Injection und auch die Vermeidung von direkter DOM-Manipulation spielen für die Testbarkeit ebenfalls eine große Rolle. Explizite Abhängigkeiten werden minimiert und Code-Blöcke können besser isoliert getestet werden. Aber das Testen einer AngularJS-Applikation wird nicht nur ermöglicht, sondern durch die Bereitstellung zweier grundlegender Setups geradezu schmackhaft gemacht:

Anzeige
Anzeige
  • Skeletons und Skripte für das Unit-Testing mit dem Testing-Framework Jasmine und dem Test-Runner karma
  • eine zusätzliche Runner-API für das „End-to-End Testing“, also Testen des UI-Verhaltens bei Benutzerinteraktion

Als Beispiel soll ein End-to-End-Test aus der Wetter-App dienen. Die Interface-Tests werden hierbei in so genannten „scenarios“ definiert und mit den vom JS-Testing-Framework Jasmine bereitgestellten Suite-Funktionen describe/it/expect beschrieben, spezifiziert und auf erwartete Rückgabewerte geprüft. Dabei bedient sich der Test-Runner tatsächlicher Browser-Umgebungen, es werden also Instanzen des aktuellen Browsers gestartet, um das Verhalten unter den normalen Bedingungen einer laufenden Rendering-Engine zu testen. Beim Beschreiben der Tests können Elemente über Selektoren direkt angesprochen und das Benutzerverhalten simuliert werden. Somit lässt sich etwa der Klick auf ein bestimmtes Element und dessen erwartete Veränderung an anderer Stelle hervorrufen und testen.

End-to-End-Testing mittels Jasmine
describe('OpenWeather App', function() {
    beforeEach(function() {
        browser().navigateTo('../../app/index.html');
    });
    describe('Forecast view', function() {
        beforeEach(function() {
          browser().navigateTo('#/forecast');
        });
        it('should render forecast when user navigates to /forecast', function() {
            expect(element('[ng-view] form button[type="submit"]').text()).toMatch(/Search!/);
        });
        it('should map the value of an "instant city forecast" button to the input field', function() {
            element('[ng-view] form .btn-group > button:first-child').click();
            expect(element('[ng-view] form input#location').attr('value')).toBe('Hamburg');
        });
    });
});

Listing 4

Fazit

Unter der „HTML-Haube“ von AngularJS steckt ein anspruchsvolles Framework mit dem Ziel, einer Reihe von modernen Entwicklungsparadigmen gerecht zu werden: MVC bzw. MVVM, modularer Aufbau mit Dependency Injection und ein ausgefeiltes Test-System. Gerade bei komplexen Entwicklungsvorhaben und in Team-Situationen mit einer klaren Rollenaufteilung zwischen UI/UX-Designern und JavaScript-Entwicklern sollte die Evaluation von AngularJS in Betracht gezogen werden.

Mehr zu diesem Thema
Fast fertig!

Bitte klicke auf den Link in der Bestätigungsmail, um deine Anmeldung abzuschließen.

Du willst noch weitere Infos zum Newsletter? Jetzt mehr erfahren

Anzeige
Anzeige
Schreib den ersten Kommentar!
Bitte beachte unsere Community-Richtlinien

Wir freuen uns über kontroverse Diskussionen, die gerne auch mal hitzig geführt werden dürfen. Beleidigende, grob anstößige, rassistische und strafrechtlich relevante Äußerungen und Beiträge tolerieren wir nicht. Bitte achte darauf, dass du keine Texte veröffentlichst, für die du keine ausdrückliche Erlaubnis des Urhebers hast. Ebenfalls nicht erlaubt ist der Missbrauch der Webangebote unter t3n.de als Werbeplattform. Die Nennung von Produktnamen, Herstellern, Dienstleistern und Websites ist nur dann zulässig, wenn damit nicht vorrangig der Zweck der Werbung verfolgt wird. Wir behalten uns vor, Beiträge, die diese Regeln verletzen, zu löschen und Accounts zeitweilig oder auf Dauer zu sperren.

Trotz all dieser notwendigen Regeln: Diskutiere kontrovers, sage anderen deine Meinung, trage mit weiterführenden Informationen zum Wissensaustausch bei, aber bleibe dabei fair und respektiere die Meinung anderer. Wir wünschen Dir viel Spaß mit den Webangeboten von t3n und freuen uns auf spannende Beiträge.

Dein t3n-Team

Melde dich mit deinem t3n Account an oder fülle die unteren Felder aus.

Bitte schalte deinen Adblocker für t3n.de aus!
Hallo und herzlich willkommen bei t3n!

Bitte schalte deinen Adblocker für t3n.de aus, um diesen Artikel zu lesen.

Wir sind ein unabhängiger Publisher mit einem Team von mehr als 75 fantastischen Menschen, aber ohne riesigen Konzern im Rücken. Banner und ähnliche Werbemittel sind für unsere Finanzierung sehr wichtig.

Schon jetzt und im Namen der gesamten t3n-Crew: vielen Dank für deine Unterstützung! 🙌

Deine t3n-Crew

Anleitung zur Deaktivierung
Artikel merken

Bitte melde dich an, um diesen Artikel in deiner persönlichen Merkliste auf t3n zu speichern.

Jetzt registrieren und merken

Du hast schon einen t3n-Account? Hier anmelden

oder
Auf Mastodon teilen

Gib die URL deiner Mastodon-Instanz ein, um den Artikel zu teilen.

Anzeige
Anzeige