Angular 2

Von Dominik Mathmann, GEDOPLAN IT Training.

Angular JS aus dem Hause Google ist seit Jahren eines der großen Frameworks wenn es um die Entwicklung  clientseitiger Anwendungen mittels JavaScript geht. Die Version 2, die erstmals Mitte 2014 vorgestellt wurde, ist dabei weit mehr als ein einfaches Update des bestehenden Frameworks. Angular 2 wurde von Grund auf neu entwickelt und konzeptioniert und unterscheide sich in vielen Bereichen deutlich von der Vorgänger Version. Zwar ist Angular 2 noch in der Beta Phase (seit Dezember 2015), dennoch wollen wir in diesem Artikel einen ersten Blick auf die Konzepte hinter der neuen Version werfen.

Die Qual der Wahl

Eine der grundlegenden Entscheiden die getroffen werden muss ist in welcher Sprache die Angular-Anwendung überhaupt geschrieben werden soll. Hier stehen gleich mehrere Varianten zur Verfügungen:

  • JavaScript
  • JavaScipt 6
  • TypeScript
  •  Dart

Das JavaScript 6 in der Liste aufgeführt ist wird kaum jemanden überraschen und auch die Möglichkeit mit Googles eigener Programmiersprache „Dart“ ans Werk zu gehen ist nachvollziehbar. Angular 2 selbst ist allerdings in TypeScript geschrieben.  TypeScript stammt aus dem Hause Microsoft und ist schon einige Jahre auf dem Markt. Ins Rampenlicht gerückt ist diese Programmiersprache aber erst als Google ankündigte bei der Entwicklung von Angular 2 auf eben diese Sprache zu setzen. Derzeit sieht es so aus das viele Entwickler auf diesen Zug aufspringen und ihre Angular 2 Anwendungen verstärkt mir TypeScript entwickeln.

Die Beispiele in diesem Artikel folgen ebenfalls diesem Trend. Aber schon wieder eine neue Programmiersprache? Ganz so schlimm wie es sich anhört ist es dann allerdings gar nicht, ganz im Gegenteil. Zum einen handelt es sich bei TypeScript um eine Obermenge von JavaScript, demnach ist jeder JavaScript Code auch gültiger TypeScript Code.  Darüber hinaus wird jeder Java Entwickler sich heimisch fühlen: statische Typisierung, „lesbare“ Klassen- und Vererbungs-Konstrukte,  Interfaces und Generics sind hier nur einige wenige Stichwörter. Beispiel gefällig?

import {Customer}     from './Models';
import
{ServiceInterface, BasicRepository} from './ServiceUtils';

class CustomerService
  
extends BasicRepository<Customer>
     implements ServiceInterface{

   constructor() {
   
super();
       }

  currentCustomer: Customer;
   
getCustomer(): Customer{
      
return this.currentCustomer;
   
}
}


Viele Worte der Erklärung benötigt dieses Beispiel für einen Java Entwickler vermutlich nicht. Die hier gezeigte Klasse besteht aus Import-Anweisungen, einer Klassendeklaration inklusive Vererbung, einem Konstruktor und der Definition einer Variablen und einer Methode auf Klasseneben. TypeScript Dateien verwenden in aller Regel die Extension „.ts“ und werden dank eines entsprechenden Compilers in reines JavaScript kompiliert (wahlweise ES3, ES5 oder ES6). Syntaktische Fehler werden somit direkt bei der Entwicklung deutlich und nicht erst im Browser, darüber hinaus bieten uns IDEs die TypeScript unterstützen auch eine Codevervollständigung an die wohl jeder Java Entwickler schmerzlich bei JavaScript vermisst haben wird.

 

Module

Bei der Java Entwicklung sind „imports“ ein alter Hut. Die Nutzung einer fremden Klasse setzt voraus das eine Import-Anweisung deklariert ist die dem Compiler anzeigt aus welchem Package die definierte Klasse stammen soll. Eine solche Deklaration von Abhängigkeiten gab es bis ES6 im Standard von JavaScript nicht. Jede JavaScript Datei die für die Entwicklung benötigt wurde musste als Script-Tag in der HTML Seite verankert werden. Jegliche Definitionen von Variablen in diesen Dateien die außerhalb von Funktionen lagen wurden als globale Attribute dem Browser hinzugefügt und standen somit allen anderen Skripten zur Verfügung. Was bei einer Handvoll Dateien noch gut funktioniert stößt allerdings sehr schnell an die Grenze der Wartbarkeit. Um dieses Problem an zu gehen sind über die Zeit neben Entwurfsmustern („IIFE“, Immediately-Invoked Function Expression) auch konkrete Konzepte zur Definition von Modulen entstanden:

·         AMD (Asynchronous Module Definition)

o   implementiert z.B. von requirejs

o   asynchrones Laden

·         CommonJS

o   implementiert z.B. von node.js

o   synchrones Laden

·         SystemJS

o   Wrapper-System um sowohl AMD, CommonJS , als auch ES6 Module zu laden

Der Grundgedanken dieser Konzepte ist dem der Java-Importe sehr ähnlich. Anstatt alles was die Anwendung an irgendeiner Stelle jemals benötigt in einer endlosen Litanei von Script-Tags fest zu halten definiert jedes unserer Module seine eigenen Abhängigkeiten die dann beim Aufruf aufgelöst und geladen werden. Erst in der Version ES6 hat dieser Gedanken auch seinen Weg in den Standard gerfunden und lehnt sich syntaktisch an die beiden aufgeführten Systeme an. Bis dies allerdings nativ in aktuellen Browsern unterstützt wird kann leider noch einige Zeit vergehen. Zum Glück bieten AMD und CommonJS schon heute die Möglichkeit modular zu Entwickeln. Angular 2 greift genau dieses Thema auf und verwendet SystemJS. Auch wenn Angular gänzlich ohne Module entwickelt werden kann, ist die Empfehlung von offizieller Seite diese zu nutzen.

Hello Angular2

Bevor wir uns eine einige Aspekte von Angular2 Anwendungen in Detail ansehen werden, starten wir mit einer sehr minimalistischen HelloWorld-Anwendung. Das Beispiel ist bewusst auf ein Minimum reduziert um sich mit den grundlegendsten Konstrukten von Angular2 vertraut zu machen. Das Beispiel besteht aus 4 Dateien.

index.html

<html>

    <head>

        <title>gedoplan aktuell - angular2 - HelloWorld</title>

        <base href="/">

    </head>

    <body>

    <hello-world-app>waiting for hello-world-app...
    </hello-world-app>

    <script src="helloworld/scripts/angular2-polyfills.js">
    </script>

    <script src="helloworld/scripts/system.js"></script>

    <script src="helloworld/scripts/Rx.js"></script>

    <script src="helloworld/scripts/angular2.dev.js"></script>

    <script src="helloworld/boot.js"></script>

</body>

</html>

Die Start-Seite unserer Anwendung ist überraschend überschaubar, was daran liegt das es sich bei der Anwendung um eine so genannte Single Page Applikation handelt (SPA). Das Konzept dahinter besteht darin, dass der User sich technisch gesehen immer auf einer HTML-Seite aufhält und Inhalte durch das Nachladen von html-Teilen aktualisiert werden. Eine Navigation würde hierbei nun nicht zu einem normalen http-Request führen und eine vollständige HTML-Seite zurückliefern, sondern über einen Ajax-Aufruf nur dafür sorgen das ein kleiner Teil unserer Seite ausgewechselt wird. Zurück zu unserer index.xhtml: neben den üblichen head- und body-Elementen befindet sich in unserer Seite ein besonderes HTML-Tag <hello-world-app>“. Dieses Tag wird genau die Stelle sein die Angular nach der Initialisierung mit seinen Inhalten füllen wird. Der hier verwendete Name des Tags definiert sich durch den „selector“ der innerhalb unserer Hauptkomponente gesetzt wird (s. unten). Es folgen die benötigten externen Bibliotheken für Angular und dessen Abhängigkeiten bevor als letzte Script-Referenz unsere eigene „boot.js“ referenziert wird die unser Modul-System initialisiert.

helloworld/boot.js

System.config({
      
packages: {
        
app: {
            
defaultExtension: 'js'
       
}
      
}
     });
System.import('./app.js').catch(function(err){
    
console.error(err);

Wir verwenden für dieses Beispiel SystemJS als Modul-Loader. Je nach verwendetem Framework für das Laden von Modulen wird sich diese Datei unterscheiden. Der zugrunde liegende Ablauf ist bei den meisten Varianten derselbe: von unseren eigenen Sourcen referenzieren wir lediglich eine zentrale Datei (boot.js) die unser gewähltes Modul-System initialisiert. In diesem Fall teilen wir SystemJS mit, dass unsere Module im Ordner „helloworld“ die Dateiendung „js“ aufweisen. Schlussendlich konfigurieren wir unser Modul-System noch dahingehend welches unserer Module den „Startpunkt“ unserer Anwendung darstellt und referenzieren hier unser Skript das Angular startet:

helloworld/boot-app.ts

import {bootstrap} from 'angular2/platform/browser';
import
{HelloWorld} from './hello-world';
bootstrap(HelloWorld, [])
 
.catch(err => console.error(err));

Wir initialisieren unserer Anwendung indem wir die von Angular bereitgestellte Funktion „bootstrap“ aufrufen und unsere erste Komponente übergeben.  Eine Komponente („Component“) ist das zentrale Konstrukt in Angular. Wir werden später Komponenten schreiben die wiederverwendbare Funktionen zur Verfügung stellen, aber auch das Bindeglied zwischen HTML-Seite und Controllerlogik  darstellen.helloworld/hello-world.ts

import {Component} from 'angular2/core'
@Component({
  
template: '<p>{{helloText}}</p>',
  
selector: 'hello-world-app'
   })

export class HelloWorld{
    
helloText:string="Hello World from Angular2"

Eine Klasse wird zu einer Angular-Komponente indem wir diese mit entsprechenden Metadaten versorgen. Um dies zu tun verwenden wir den „@Component“ Decorator aus dem package ‚angular2/core‘, der als Parameter ein Objekt mit Konfigurationsattributen erwartet. Eines dieser Attribute ist: „template“. Dabei handelt es sich um die HTML-Repräsentation unserer Komponente, also das was der Browser später anzeigen wird. Bei großen Templates wird man hier in aller Regel zu der Property „templateUrl“ greifen und eine separate HTML-Datei referenzieren. Für unsere Anzeige beschränken wir uns auf ein Minimum und geben nur ein Wert aus unserer Komponentenklasse aus. Dafür verwenden wir die die doppelten geschweiften Klammern die das Attribut der Komponente umschließen (zur Template-Syntax später mehr). Darüber hinaus definieren wir den „selector“ den wir bereits in der index.html gesehen haben. Dies veranlasst Angular die Ausgaben dieser Komponente an der Stelle des entsprechenden Tags an zu zeigen.

Zugegeben,  ein nicht ganz intuitiver Weg einer Angular 2 Anwendung ein „Hello World“ zu entlocken. Allerdings besteht die Welt zum Glück nicht aus „Hello World“-Anwendungen und die Konzepte hinter diesem Vorgehen werden uns bei der Entwicklung komplexer Anwendungen später gute Dienste leisten.

Template Syntax

Im Inline-Template unseres kleinen Hello-World Beispiels kam bereits Template-Syntax zum Einsatz um Werte aus der Komponente im HTML an zu zeigen. Grundsätzlich wird die von Angular bereit gestellte Template Syntax verwendet um unsere HTML-Seiten mit zusätzlicher Funktionalität auszustatten. Grob lässt sich der Einsatz dieser Syntax in folgende Bereiche gliedern:

·         Interpolation

o   Format : {{ expression }}

o   einfache Ausgaben, meist Attribute der Komponente, wird in einen String umgewandelt

·         Property-Binding

o   Format: <… [property] = „expression“ …>

o   Eigenschaften von View-Elementen setzen, z.B. Image-Source, Style-Klassen

·         Event-Binding

o   Format: <… (event) = „expression“ …>

o   Registriert einen Event-Listener

·         Two-Way-Binding

o   Format: <…. [(ngModel)]=“expression“ …>

o   Bindet Formularfelder an Eigenschaften der Komponente und aktualisiert sowohl das Eingabefeld als auch das Attribut bei Änderungen der Gegenseite

·         Template-Expanding

o   Format: <… *property=[expression] …>

o   Kurzschreibweise für Layout manipulierende Direktives, wird für folgende 3 Standard Direktiven verwendet: ngFor, ngSwitch und ngIf

·         Template-Variablen

o   Format: <…. #name  …>

o   lokale Referenz auf Elemente des Templates

Schauen wir uns dazu ein Beispiel an, form.html:  ein Formular um Benutzerkommentare zu erfassen und eine anschließende Ausgabe aller bereits Erfassten Beiträge.  In diesem Beispiel wurden bewusst jegliche Style Angaben und nicht angular-spezifischen Attribute entfernt:

<form ngForm #form="ngForm">
    
<label>Benutzername:</label>

 <input [ngControl]="username"
   
[(ngModel)]="comment.username"
required/>

   

    <labe>Kommentar:</label>

    <textarea [ngControl]="commenttext"
    [(ngModel)]="comment.text"
required></textarea>


Unser Formular beginnt mit der Direktive „ngForm“ die einen Wrapper für unser Formular erzeugt  und damit die Funktionen um z.B. Zugriffe auf den Status des Formulars und die Verwaltung der enthaltenen Formularfelder erweitert. Die Direktive kann auch weggelassen werden, da Angular automatische jegliches Formular um diesen Wrapper erweitert. Um dieses Formular-Objekt in unserem Template zu nutzen verwenden wir eine Template Variable „form“ und weisen dieser Variablen den Wert „ngForm“ zu. Angular erkennt diese spezielle Zuweisung und wir erhalten eine Referenz auf das Wrapper-Objekt des Formulars, die wir später bei der Implementierung des Buttons verwenden werden.  Die beiden Eingabefelder weisen gleich zwei der vorgestellten Konstrukte zur Erweiterung unserer Templates auf. Zum einen verwenden wir ein Attribute-Binding „[ngControl]“ um der Property („ngControl“) einen eindeutigen Schlüssel für dieses Eingabefeld zu geben. Damit registrieren wir diese Eingabekomponenten im übergeordneten Formular, sodass dieses über Falscheingaben in diesem Feld informiert wird. Darüber hinaus verwenden wir ein ngModel-Binding („[(ngModel)]“) um die Eingabefelder an entsprechende Eigenschaften unserer Komponenten-Klasse zu binden. Zusätzlich verwenden wir auf den Feldern die Angular-Direktive „required“.  Hinter dieser Direktive steckt ein Standard-Validator der das Feld in einen Fehler-Zustand versetzt wenn das Eingabefeld nicht gefüllt ist und diesen Fehler meldet.

<button (click)="addComment()"
 
[disabled]="!form.valid">
   
speichern

</button>


Der gezeigte Button verwendet ein Event-Binding. Mittels „(click)“ registrieren wir unsere angegebene Methode der Komponente auf den Click-Event des Buttons. Zusätzlich wollen wir den Button deaktivieren wenn das Formular ungültige Werte enthält. Dazu verwenden wird erneut ein Attribut-Binding („[disabled]“) um den Button bei Bedarf zu deaktivieren. Als Expression kommt nun die Template-Variable zum Einsatz die wir auf Formulareben definiert haben, um so Zugriff auf den Zustand des Formulars zu erhalten. Unter anderem steht hier das Attribut „valid“ zur Verfügung welches anzeigt ob das Formular mit falschen Eingaben versorgt wurde. Da beide Eingabekomponenten sich mittels „ngControl“ am Formular registriert haben wird eine fehlende Eingabe in diesen Feldern auch im Formular zu einem „valid=false“ führen. 

Kommentare:

<div *ngFor="#comment of comments">
   {{comment.username}} : {{comment.text}}

</div>


Als Abschluss des Formulars sollen unterhalb unserer Eingaben alle bereits erfassten Kommentare angezeigt werden. Hierzu bietet sich die Direktive „*ngFor“ an die über eine Menge von Objekten iteriert. Innerhalb der Expression haben wir nun die Möglichkeit eine Template-Variable (#comment) zu definieren und die Elemente an zu geben über die iteriert werden soll („of comments“). Das DIV-Element auf dem diese Direktive zum Einsatz kommt wird beim Rendering nun so oft wiederholt wie Elemente in dem Array vorhanden sind. Um eine Aktualisierung der GUI wenn Elemente aus diesem Array entfernt werden oder neu hinzukommen müssen wir uns übrigens nicht kümmern. Angular registriert einen so genannten „watch“ und wird die Anzeige aktualisieren sobald sich die zugrunde liegende Datenquelle verändert.

Unter der Haube

Das oben gezeigte Template alleine reicht natürlich nicht um ein funktionierendes Formular zu implementieren. Was fehlt sind die Controller-Funktionen die Eingaben entgegennehmen  und die bereits erfassten Kommentare liefern. Die Struktur dieser Komponente die als Controller für das Formular dient unterscheidet sich kaum von unserem ersten Beispiel:

import {Component} from 'angular2/core'
import
{Comment} from '../models/GithubModels'

@Component({
  
templateUrl: 'app/components/form/form.html',
  
styleUrls: ['app/components/form/form.css']
})

export class Form {
   
comment: Comment
   
comments: Comment[]

   
constructor() {
       
this.comment = new Comment();
       
this.comments = this.loadComments();
   
}
   
clearComment() {
       
this.comment = new Comment();
   
}
   
addComment() {...}
   
loadComments(): Comment[] {...}

}

Wie jede unserer Komponenten beginnen wir mit den Import der benötigten Module. Neben der Component aus dem Core-Packet verwenden wir auch ein eigens erstelltes Modul „GithubModels“ bei dem es sich um eine TypeScript Datei  handelt die einige Klassen zur Strukturierung unserer Datensätze anbietet.  Es folgen die Metadaten für Angular. In diesem Fall benötigen wir keinen Selektor, da diese Komponente nicht über eine Direktive im HTML landet sondern später per Navigation aufgerufen wird. Statt wie im ersten Beispiel ein Inline Template zu verwenden referenzieren wir dieses Mal eine separate HTML Datei. Zusätzlich verwenden wir für diese Komponente eine eigene CSS-Datei, die lediglich die Komponentenspezifischen Styles enthält. Es folgt der Export unserer eigentlichen Komponente auf der sich unser Template stützt. Hier finden wir die Attribute wieder die wir bereits in der HTML-Datei verwendet haben, „comment“ beinhaltet das aktuelle Kommentar-Objekt welches hinter den Eingabefeldern unseres Formulars steht. „comments“ enthält alle bereits erfassten Kommentare.

Wegführung

Unser vorrangehendes Beispiel weist in seinen Metadaten im Gegensatz zu der Startkomponente die wir gesehen haben kein Attribut „selector“ aus. Ein solcher Selektor wird deklariert, wenn eine Komponente eine HTML-Tag Repräsentation erhalten soll um zum Beispiel eine wiederverwendbare Funktion / HTML-Struktur zu erstellen. Unsere Form-Komponente hingegen dient als Controller für die Anzeige unserer Formular-Seite. Wir haben bereits erwähnt das es sich bei unserer Anwendung um eine Single Page Applikation handelt, dennoch strukturieren wir unsere Anwendungsteile in separate HTML Dateien die durch Angular bei Bedarf geladen werden. Für solche Navigationen gibt es ein eigenes Package mit entsprechenden Komponenten, Klassen und Decorators: 'angular2/router'. Die Angabe der Navigationsregeln erfolgt wieder per Decorator oberhalb unserer Komponentenklasse. In aller Regel werden wir eine solche Deklaration in der Hauptkomponente finden um die Navigation aus unserem HTML-Template heraus auf die einzelnen Unterseiten zu realisieren. Zusätzlich ist es aber auch möglich mehrstufige Navigationen zu implementieren, sodass auch unsere Controller eigene Navigationen deklarieren. Schauen wir uns aber die Grundlegende Struktur einer solchen Navigation an. Als Basis dienen uns zwei Komponenten (RoutingPage1 und RoutingPage2) zu die unser Controller navigieren soll. Diese Komponenten geben als Template lediglich ihre Namen aus:

import {Component} from 'angular2/core'
@Component({
   
template: 'Page1'
})

export class RoutingPage1 {
}


Hier nun die Komponente welche die entsprechenden Navigationsregeln definiert, routing.ts:

import {Component} from 'angular2/core'
import
{RoutingPage1} from './page1'
import
{RoutingPage2} from './page2'
import
{RoutingPageWithParameter} from './pageWithParameter'
import
{RouteConfig, Route, ROUTER_DIRECTIVES} from 'angular2/router';

@RouteConfig([
   
new Route({ path: "/page1", component: RoutingPage1,
        
name:  "Page1",
        
useAsDefault: true
   
}),
   
new Route({ path: "/page2", component: RoutingPage2,
        
name: "Page2" }),
])

@Component({
   
template: ` My Template
               
<hr/>
                <a [routerLink]="['Page1']">page1</a>
                <a [routerLink]="['Page2']">page2</a>
                <br/>
                <router-outlet></router-outlet>`
,

    directives: [ROUTER_DIRECTIVES]
})

export class Routing { }

      

Neben der inzwischen vertrauten Component und Controllern der beiden Beispiel-Seiten benötigen wir folgende drei Importe aus dem router-Package:

·         RouteConfig

o   Decorator, dient zur Definition unserer Navigationen

·         Route

o   Klasse, stell ein konkretes Navigationsziel dar

·         ROUTER_DIRECTIVES

o   Direktiven-Liste, importiert benötige Direktiven die in der HTML-Seite verwendet werden sollen, z.B. routerLink

Die konkreten Navigationsziele implementieren wir mittels des RouterConfig-Decorators der ein Array von Route-Objekten entgegen nimmt. Über den Konstruktor der Klasse „Route“ definieren wir den URL-Pfad, die Komponente die bei der Navigation aufgerufen werden soll und einen internen Namen den wir später in unseren Links verwenden werden. Anschließend definieren wir wie gewohnt die Metadaten unserer Controllerkomponente von der die Navigationen ausgelöst werden sollen. Das hier verwendete Template weist zwei neue Aspekte auf. Zum einen verwenden wir ein Attribut-Binding auf dem Link-Tag „routerLink“ welches dafür sorgt, dass die eigentliche Navigation durchgeführt wird. Die Zuweisung des Werte scheint auf den ersten Blick ein wenig seltsam zu sein, da es sich hierbei nicht um einen einfachen String handelt sondern um ein Array. Das liegt daran, dass neben den hier gezeigten einfachen Navigationen auch mehrstufige Navigationen und auch die Angabe von Parametern möglich ist. Der zweite wichtige Bereich unseres Templates ist die Verwendung der Direktive „router-outlet“. In diesen Bereich wird Angular das entsprechende Template der Komponente laden die wir durch unsere Routing Regel angesprochen haben. Um diese Direktive zu verwenden benötigen wir innerhalb unserer Metadaten noch die entsprechende Angabe, dass wir die zusätzlichen Router-Direktiven innerhalb unserer Templates verwenden wollen (ROUTER_DIRECTIVES).

Beim Aufruf unserer Routing-Komponente passiert nun folgendes: zum einen wird das HTML-Template gerendert und im Browser dargestellt. Zusätzlich ermittelt Angular basierend auf der URL welche der Routing-Regeln zutreffend ist. Die damit verbundene Komponente stellt nun sein HTML-Template zur Verfügung und füllt den Bereich der „router-outlet“-Direktive.

Wer das Beispiel aufmerksam betrachtet dem wird auffallen das die URL des Browser unser aktuelles Navigationsziel beinhaltet. Mit dieser URL adressieren wir aber keine konkrete Ressource (unter /routing/page1) sondern anwendungsinterne Logik zur Navigation. Die HTML5-Technik die es erlaubt die Adresse des Browsers zu ändern ohne einen http-Request zu erzeugen nennt sich „HTML 5 pushState“. Damit das in dieser Form funktioniert ist es nötig in unserer HTML Seite das Tag „<base href>“ zu verwenden und auf den Kontext unserer Applikation zu verweisen um sicher zu stellen das alle Ressourcen aus dem korrekten Pfad geladen werden. Darüber hinaus ist ein weiterer Kniff auf Seiten des Servers nötig. Der direkte Aufruf der oben gezeigten URL würde den Webserver dazu veranlassen  eine Ressource unter dem Pfad „routing/page1“ zu suchen, was zu einem Fehler führt da es sich dabei nicht um einen konkreten Pfad zu einer Datei oder ein Servlet handelt. Um diesen Fehler zu umgehen müssen wir den Webserver dazu veranlassen alle Requests auf unsere index.xhtml Seite um zu leiten um Angular korrekt zu initialisieren und die eigentliche Navigation aus zu werten. Sollte das nicht gewünscht sein kann auch die aus Angular 1 bekannte HashLocationStrategy angewendet werden die dazu führt das unsere anwendungsinternen Navigationen als Hash an die URL angehängt werden (http://localhost:3000/#/routing/page1)

Her damit

Unsere Anwendungen bestehen natürlich nicht nur aus Controllern. Zentrale Methoden und Klassen werden in aller Regel durch einen Services implementiert der innerhalb der Anwendung injiziert werden kann. Ein Service ist in Angular nichts anders als eine Klasse die als so genannter „Provider“ dem Framework bekannt gemacht wird. Schauen wir uns dazu folgendes Beispiel an, beginnend mit der Service-Klasse:

import {Injectable}     from 'angular2/core';
@Injectable()

export class GithubService {
   
constructor() { }
   
getRepositoryDetails(): any {...}
}

Wie versprochen handelt es sich um eine simple Klasse, es kommt lediglich ein neuer Decorator „Injectable“ zum Einsatz. Dieser Decorator wäre in diesem Beispiel technisch nicht einmal nötig da dieser erst erforderlich ist wenn der Service wiederrum andere Objekte injiziert bekommt. Um einer Komponente nun Zugriff auf einen solchen Service zu ermöglichen werden die Metadaten der Komponente um die Property „provider“ erweitert. Hier erwartet Angular ein Array von zu verwendeten Provider-Klassen in dem wir unseren Service hinzufügen. Abschließen fügen wir einen passend typisierten Parameter zum Konstruktor hinzu umso Zugriff auf die Instanz des Providers zu erlangen:

import {Component, OnInit} from 'angular2/core'
import
{GithubService} from '../../services/GithubServices'

@Component({
   
templateUrl: 'app/components/github/github.html',
   
providers: [GithubService]
})

export class Github{
   
constructor(private github: GithubService) {...}
}

 

Die Deklaration des provider-Attributes auf der Ebene der Komponente ist nur eine Möglichkeit in den Depedency Injection Mechanismus von Angular ein zu greifen. Deklarationen können auch auf übergeordneten Komponenten oder bereits beim Bootstrap der Anwendung erfolgen( bootstrap(App, [GithubService]) und damit den untergeordneten Komponenten ebenfalls über ihren Konstruktor zur Verfügung gestellt werden.

Das Beispielprojekt

Alle oben gezeigt Beispiele verwenden eine Projektstruktur die als Basis den SystemJS-Branch des offiziellen angular2-seed Projektes verwenden. Werfen wir einen kurzen Blick auf die hier angewendete Struktur:

Die Voraussetzung um das Projekt zu kompilieren und zu starten ist eine installierte Version von node.js. Bei node.js handelt es sich um eine Browserunabhängige JavaScript Laufzeitumgebung mit dessen Hilfe wir lokal JavaScript Anwendungen ausführen können. Neben der Laufzeitumgebung haben wir damit auch die Möglichkeit mittels „npm“ (Node.js Packet Manager) Abhängigkeiten zu externen Bibliotheken zu verwalten und diese automatisch bei Bedarf herunterladen zu lassen. Neben node.js verwendet das Projekt „gulp“ als so genannten Task-Runner (vergleichbar mit „ant“). Gulp wird dazu verwendet wiederkehrende Aufgaben innerhalb des Projektes zu automatisieren. In unserem Projekt sorgt das gulp-Script zum Beispiel  dafür, dass alle TypeScript Dateien kompiliert und statische Dateien kopiert werden um  schließlich einen Webserver zur lokalen Entwicklung zu starten. Um das Beispiel Projekt aus zu führen reichen folgende Befehle:

npm install  (= lädt alle deklarierten Abhängigkeiten herunter)

npm start (= führ den Task „start“ aus, der in der Datei „package.json“ deklariert ist. Dieser wiederrum ruft grunt auf um ein „clean“ auf dem Projekt aus zu führen und anschließend die TypeScript-Dateien zu kompilieren. Alternativ dazu könnte grunt auch direkt aufgerufen werden: ‚gulp  [TASK]‘)

URL aufrufen: localhost

Machen lassen

Für unser kleines Projekt wird gulp als Task Runner verwendet, grunt wäre dazu eine direkte Alternative. Grundsätzlich automatisieren diese Tools diverse Aufgaben die bei der Entwicklung und Bereitstellung unserer Software nötig sind. Gulp stellt dabei eigentlich nur das Grundgerüst zur Verfügung mit dem wir eine Abfolge von Aufgaben erledigen können. Die eigentlichen Tasks werden in aller Regel durch Zusatzmodule realisiert von denen auf der offiziellen Seite  mehr als 2000 angeboten werden.  Die Möglichkeiten sind damit Vielfältig, aber was machen wir denn nun damit? Alle Tasks die für unser Projekt zur Verfügung stehen finden sich in der Datei „gulp.js“ im Projektroot. Hier definieren wir zuerst die Module die wir verwenden werden (welche wir zuvor per „npm install [modul-name] --save-dev“ installiert und unseren Development-Abhängigkeiten hinzugefügt haben).  Anschließend definieren wir unsere Tasks welche die verschiedenen Aufgaben erledigen. Werfen wir ein Blick auf unser Beispiel-Projekt und welche Aufgaben von gulp erledigt werden. Grob lassen sich unsere Aufgaben in zwei Bereiche unterteilen:

Entwicklung (‚gulp server‘)

TypeScript kompilieren, statische Ressourcen kopieren, HTML-Referenzen in index.html setzen, lokalen Webserver starten

Produktion (‚gulp package‘)

TypeScript kompilieren, JavaScript minimieren, CSS minimieren, JavaScript und CSS in einer Datei zusammenfassen, HTML-Referenzen in index.html setzen, aus den Templates und Styles der Angular-Komponenten inline Elemente generieren, Maven Artefakt erstellen

Die einzelnen Schritte sind im verlinkten Beispielprojekt umfassend kommentiert, um einen ersten Eindruck von der konkreten Implementierung zu bekommen.

Diese Tasks sind nur ein sehr überschaubares Beispiel was möglich ist. Im Internet findet sich eine Vielzahl von umfassenden weiteren Beispielen. Dennoch ist es wichtig sich mit der Technik und den Abläufen vertraut zu machen. Die Anforderungen an die eigene Projektstruktur sollten den Build Prozess bestimmen und nicht das gewählte gulp-Script.

Finale!

Derzeit befindet sich Angular wie bereits angesprochen noch in einer Beta Phase. Es existieren derzeit keine offiziell bestätigten Daten wann Angular2 in der finalen Version zur Verfügung stehen wird. Bis dahin ist also noch genug Zeit sich mit den neuen Konzepten vertraut zu machen. Darüber hinaus ist auch Angular 1 nicht abgeschrieben. Google hat angekündigt auch diesen Versionsstrang weiter zu pflegen und mit Updates zu versorgen bis „die allermeisten“ Entwickler sich auf Angular2 konzentrieren.

Thematisch ist dieser Artikel natürlich bei weitem nicht alles was es zum Thema Angular 2 zu sagen gibt. http-Services mit Observables, eigene Direktiven und Testing sind nur einige der Stichpunkte die offen geblieben sind. Diese Themen wurden bewusst an dieser Stelle nicht angesprochen weil sie den Rahmen eines solchen Artikels sprengen würden. Vielmehr sollte dieser Beitrag den Einstieg in die Welt von Angular erleichtern und „Lust auf Mehr“ machen, wir hoffen das ist gelungen.

Links

Beispielprojekt

Live-Beispiel

Offizielle Webseite

Developer Guide

TypeScript Handbook 

Node.js

Gulp

Unsere Kundenzeitschrift GEDOPLAN aktuell

In dieser Rubrik stellen wir Ihnen einen Artikel aus der aktuellen Ausgabe unserer Kundenzeitschrift zum Online-Lesen zur Verfügung. Alle weiteren Ausgabe zum Download finden Sie hier.

Aktueller Kurs zum Thema:

AngularJS für Webapplikationen

* Nächster Termin: 21.9. - 23.9.2016 in Berlin.