Über unsMediaKontaktImpressum
Philipp Burgmer & Frederik von Berg 07. Februar 2017

Neuerungen von Angular 2

Mit großen Erwartungen wurde im September 2016 Angular 2 veröffentlicht. Fast genau 4 Jahre nach AngularJS 1.0, wurde der Nachfolger von vielen Webentwicklern fieberhaft erwartet. 4 Jahre sind im Web-Umfeld eine lange Zeit und so schickt sich Angular an, neue und überarbeitete Konzepte anzubieten und damit den Entwickler-Alltag leichter und einfacher zu gestalten. In diesem Artikel möchten wir Ihnen einen Überblick über neue, weggefallene und überarbeitete Konzepte geben.

Ökosystem

Seit 2010 hat sich im Web-Umfeld viel gewandelt und auch wenn AngularJS mittlerweile in Version 1.6 zur Verfügung steht, wurde es Zeit, Angular von Grund auf neu zu entwerfen.

AngularJS wurde in JavaScript entwickelt. Durch die dynamische Typisierung und das fehlende Konzept zur Code-Strukturierung ist es jedoch schwer, eine große Codebasis wartbar zu halten. Das betrifft sowohl das Framework an sich, als auch damit entwickelte Anwendungen. Änderungen und Refactorings zum Bespiel sind oft komplex und fehleranfällig [1].

Aus diesem Grund wurde Angular 2 in TypeScript [2] entwickelt. TypeScript ist eine Spracherweiterung von JavaScript. Die Sprache stellt auf der einen Seite viele Neuerungen von neueren JavaScript-Versionen (EcmaScript 2016) wie Klassen und Module auch für ältere Browser zur Verfügung. Auf der anderen Seite bietet sie mit einem statischen Typ-System, Decorators und Interfaces auch zusätzliche Features an, die die Entwicklung deutlich effizienter machen [3].

Der Clou: TypeScript-Code wird in gültigen JavaScript-Code übersetzt. Die Angular-Bibliotheken liegen somit in JavaScript vor. Dies bietet die Möglichkeit, Angular-Anwendungen nicht nur in TypeScript, sondern weiterhin auch in ES5, ES6 oder sogar Dart entwickeln zu können. Empfohlen wird jedoch die Verwendung von TypeScript. Hier profitiert man von der kompletten Tool-Palette und kann auch direkt das Angular CLI einsetzen (mehr dazu später im Artikel).

Eine weitere Neuheit ist der Umstieg auf eine semantische Versionierung [4]. Hierbei besteht eine Versionsangabe aus X.Y.Z:

  • X: Hauptversion, wird bei einer nicht rückwärtskompatiblen Änderung der API inkrementiert
  • Y: Unterversion, wird beim Hinzufügen einer neuen, abwärtskompatiblen Funktion inkrementiert
  • Z: Patchlevel, wird bei einem Bugfix inkrementiert

Zukünftig soll es ca. alle 6 Monate eine neue Angular-Hauptversion geben. Die Änderungen an der API sollen dabei klein gehalten werden, um einen einfachen Umstieg zu gewährleisten. Da es dann zukünftig häufiger neue Angular-Versionen gibt, ist man angehalten, von Angular zu sprechen und Versionsnummern nur anzugeben, wenn es sich um eine spezielle Version handelt. Es wird davon abgeraten, Projekte ng2-* oder angular2-* zu nennen [5].
Die Angular-Bibliotheken folgen grundsätzlich der semantischen Versionierung, verwenden derzeit aber teilweise unterschiedliche Versionsnummern. Der Angular-Router ist schon bei Version 3 angelangt, während alle anderen Kern-Bibliotheken die Versionsnummer 2 haben. Dies wird mit der nächsten Version im März 2017 angepasst und es wird direkt Angular 4 statt Angular 3 erscheinen.

Jeder der schon AngularJS in einem größeren Projekt eingesetzt hat weiß, wie komplex das Projekt-Setup ist. Es können verschiedene Build-Tools, Linter, etc. eingesetzt werden und um vieles muss man sich selbst kümmern. Ab Angular-Version 2 entwickelt das Angular-Team ein eigenes Angular-CLI [6], um genau diese Aufgaben zu erleichtern und Abläufe zu vereinfachen. Diese kann wie üblich als npm-Paket installiert werden (global, aber auch rein lokale Installation möglich). Danach kann man mit einem einfachen Aufruf von ng new demo-app ein neues Angular-Projekt anlegen. Im Hintergrund erzeugt die CLI ein neues Verzeichnis mit einem auf Webpack basierenden Build-System. Ein Entwicklungsserver mit LiveReload, der über ng serve gestartet werden kann, steht zur Verfügung. Des Weiteren können über das CLI Komponente, Routen, Services und Pipes generiert werden. Die Testausführung kann gestartet und eine optimierte Version für den Produktiveinsatz gebaut werden.

Module

Status: überarbeitet
Ein Modul-Konzept gab es schon in AngularJS, jedoch diente dieses nicht zur Strukturierung des Codes, sondern nur zum Bekanntmachen beim Framework. Das Modulsystem ermöglichte keine saubere Kapselung, es war auch keine Struktur im Dateisystem nötig und es lies sich darüber nicht steuern, welcher Code in den Browser übertragen werden muss.

Angular 2 setzt für zwei verschiedene Aufgaben (Strukturierung vs Registrierung) auf zwei getrennte, aber gut harmonierende Module-Systeme: die Module von ES6 und NgModule. Das ES6-Modulsystem sorgt dafür, dass die Struktur im Dateisystem eingehalten wird und steuert, welcher Code überhaupt geladen wird. Weiterhin verhindert es globale Variablen. Angular-Module sind hingegen ähnlich wie Module in AngularJS ein Konzept, um dem Framework mitzuteilen, welche Software-Komponenten es gibt und wie und wo diese in das Framework eingebunden werden sollen. Es können daran Service, Pipes und UI-Komponenten registriert sowie andere Module eingebunden werden.

Der Ausgangspunkt für eine Angular-App ist immer ein Root-Modul. Weitere Module werden dann direkt oder ggf. auch über Lazy-Loading geladen. Ein Module ist dabei eine meist leere Klasse mit dem Decorator NgModule (ähnlich wie Annotationen in Java). Über imports werden andere Module (ebenfalls eine Klasse mit Decorator) eingebunden. Diese können aus dem eigenen Code stammen, von einer 3rd Party-Bibliothek, die per npm installiert wurde, oder von Angular selbst bereitgestellt werden. In allen Fällen muss die referenzierte Klasse über das ES6-Module-System geladen werden (import in Zeile 1).

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Services

Status: kaum Änderungen
Bei den Services gibt es kaum Veränderungen. Weiterhin kapselt ein Service eine gewisse Funktionalität. Andere Software-Komponenten können einen Service später per Dependency Injection konsumieren.

Services sollten in Angular am besten als Klasse bzw. Konstruktor-Funktion implementiert werden. Es gibt jedoch auch weiterhin die Möglichkeit, eine Factory-Funktion zu verwenden. Das Provider-Konzept von AngularJS 1 hingegen gibt es nicht mehr.

Der Decorator @Injectable ist etwas irreführend. Anders als der Name vermuten lässt, sorgt er nicht dafür, dass der Service selbst an anderer Stelle injezierbar ist. Dies muss über die Registrierung an einem Module geschehen. Stattdessen werden beim Erzeugen der Service-Instanz die Konstruktor-Parameter injeziert (in Java @Inject am Konstruktor).

import {Reader} from “./reader.model”
@Injectable()
export class ReaderService {
    constructor(private logger: Logger)
    getAllReaders(): Reader[] {
        this.logger.info(“getAllReaders()”);
        return [];
    }
}

Komponenten

Status: Neues Konzept
Wo ist mein Controller hin? Ab Angular 2 gibt es keine Controller mehr. Oder zumindest werden sie dort nicht mehr als solche bezeichnet. Controller in AngularJS hatten gleich mehrere Schwächen.

Zum einen kam man (zumindest bis AngularJS 1.5 zwangsläufig) mit Scopes in Berührung. Einem komplexen Konzept, das eigentlich Framework-spezifische Aufgaben erfüllt. Über diese Scopes war es möglich, auf die Daten von anderen Controllern zuzugreifen, was zu einer engen, aber nicht ersichtlichen Kopplung der Controller führte. Das war ein Hauptgrund für die schlechte Wartbarkeit von AngularJS 1-Anwendungen: der Zugriff auf fremde Daten. Eine weitere Schwäche war der oft nicht ersichtliche Zusammenhang zwischen Controller und Template.

Ab Angular 2 gibt es deshalb Komponenten. Eine Komponente verwaltet ihre Daten und ist von den anderen Komponenten streng isoliert. Die Komponente besteht aus einer Klasse, einem Decorator an dieser Klasse sowie genau einem Template, das am Decorator und somit quasi auch direkt an der Klasse angegeben wird. Die Komponenten-Klasse ersetzt unseren Controller aus AngularJS 1, erfüllt aber ähnliche Aufgaben. Sie stellt Daten und Funktionalität für das Template bereit. Diese werden einfach als Feld in der Klasse bzw. Instanz abgelegt. Sie müssen nicht über den Scope zum Template gebracht werden.

import { Compontent, Input } from “@angular/core”;

@Component({
    selector: “music-app”,
    template: `<h1>{{title}}</h1>`
})
export class MusicAppCompontent {
    @Input() title: string;
}

Über den Selektor (Standard CSS-Selektor) kann die Komponente in anderen Templates eingebunden werden; in unserem Beispiel über den Tag <music-app title=‘Musik-App’></music-app>. An per @Input() markierte Porperties können der Komponente Daten übergeben werden. Per @Output() können Events an das aufrufende Template gesendet werden. Innerhalb des Templates haben wir direkt Zugriff auf die Variablen und Funktionen der im Hintergrund für uns erzeugten Instanz der Komponenten-Klasse. Dafür wird wie schon in AngularJS 1 die Notation mit doppeltgeschweifter Klammer verwendet.

Scopes

Status: entfernt
Wie bereits erwähnt, kam man bei AngularJS sehr häufig mit Scopes in Berührung. Scopes hatten dabei Aufgaben aus zwei Bereichen: Framework-interne und anwendungsspezifische. Auf Seiten der Anwendung wurden Scopes hauptsächlich für zwei Aufgaben verwendet: Zum einen für das Bereitstellen von Daten und Funktionen für das Template und zum anderen zum Überwachen von Daten auf Veränderungen. Für beide Aufgaben stellt uns Angular neue Möglichkeiten bereit, ohne dabei die Nachteile von Scopes zu haben. Die Daten von Komponenten bleiben streng gekapselt und wir kommen nicht mit Framework-Internas in Berührung.

Um trotzdem Informationen über Änderungen analog zu $scope.watch() zu erhalten, gibt es jetzt den Lifecycle-Hook OnChanges[7]. Dieser wird aufgerufen, falls Angular eine Änderung in den Inputs einer Komponente feststellt. Das Übergeben von Daten und Bereitstellen von Funktionalität für das Template haben wir bereits bei den Komponenten besprochen. Hier ist kein Umweg mehr über den Scope notwendig.

Pipes

Status: überarbeitet
Filter heißen ab Angular 2 Pipes. Das Konzept dahinter ist gleich geblieben, im Template können Pipes weiterhin mit dem Pipe-Operator | verwendet werden. Ebenfalls gibt es weiterhin Pipes, die bei jedem Aktualisieren der Data-Bindings ausgeführt werden (impure bzw. statefull in AngularJS 1) und solche, die nur aufgerufen werden, wenn sich die Eingangsdaten geändert haben (pure bzw. stateless).

Aus Performancegründen wurden jedoch die statefull-Filter orderBy und filter aus dem Framework entfernt. Neu dazugekommen ist die async-Pipe. Diese nimmt ein Promise oder Observable entgegen, registriert sich als Handler und gibt den aktuellen Wert aus. Falls die Komponente zerstört wird, meldet die async-Pipe den Handler automatisch ab. So kommt es zu keinem Speicherleck.

{{ release.date | date:’dd.MM.yyyy’}}

Lazy-Loading

Status: Neues Konzept
Seit dem Erschienen von AngularJS im Jahr 2012 ist die Zahl der mobilen Endgeräte im Web stark gewachsen. Um diese Themen zu adressieren wird ab Angular 2 ein Lazy-Loading von Modulen ermöglicht. Module können mit diesem Konzept bei Bedarf vom Server nachgeladen werden. Dies reduziert die Größe der JavaScript-Datei, welche initial an die Endgeräte ausgeliefert wird. Das ist vor allem für mobile Geräte bzw. bei schlechten und langsamen Netzwerkverbindungen wichtig. Des Weiteren kann am Endgerät nur der Code eingesehen werden, der auch wirklich ausgeliefert wurde. Beispielsweise wird ein Modul mit dem Administrationsbereich nur an Administratoren ausgeliefert. Mitarbeiter ohne diese Berechtigung bekommen das Modul nicht. Die potenzielle Angriffsfläche für einen Hacker sinkt dadurch. Das Lazy-Loading wird durch den neuen hierarchischen Injektor und den neuen Router ermöglicht.

Dependency Injector mit hierarchischen Injektoren

Status: Neues Konzept
Dependency Injection ist eines der wichtigsten Konzepte in AngularJS und bleibt es auch in Angular 2. In AngularJS gab es in größeren Anwendungen jedoch häufig Probleme mit der Beschränkung, dass Services immer als anwendungsweite Singletons behandelt werden.
In Angular gibt es daher nicht einen einzigen Injektor, sondern es wird eine Hierarchie aus Injektoren aufgebaut. So wird z. B. an jeder Komponente aber auch beim Lazy-Loading einen neuer Injektor erzeugt. Wird nun von einer Komponente ein Service benötigt, wird zuerst der eigene Injektor gefragt. Kann dieser die Anfrage nicht bedienen, gibt er die Anfrage an den nächst höheren Injektor in der Hierarchie weiter. Dies geschieht solange, bis die Anfrage bedient wird oder es keinen höheren Injektor mehr gibt. In diesem Fall wirft Angular einen Fehler.

Innerhalb eines Injektors sind Services weiterhin wie in AngularJS Singletons. Da jedoch mehrere Injektoren existieren, können Services mehrmals instanziert werden oder auch selektiv durch eine andere Implementierung ersetzt werden. Ein gutes Beispiel für diese Möglichkeiten findet sich in der Angular-Dokumentation unter [8].

Ahead-of-time-Compilation

Status: Neues Konzept
Wie schon AngularJS hat auch Angular einen eigenen Compiler. Dieser analysiert die Templates der Komponenten und wandelt die darin enthaltenen Data-Bindings in ausführbaren JavaScript-Code um.

Normalerweise wird dieser Compiler Just-in-time ausgeführt. D. h. der ausführbare Code wird erst nach dem Laden der Dateien im Browser erzeugt. Dafür müssen sowohl der Compiler als auch die Templates an den Client übertragen werden. Die Umwandlung ist recht aufwändig und kann bei client-seitiger Ausführung z. B. auf mobilen Geräten zu einer schlechten Performance und hoher Batteriebelastung führen.

Um diesen Problemen entgegenzuwirken, unterstützt Angular eine so genannte Ahead-of-time-Compilation. Templates und Komponenten werden dann schon zum Build-Zeitpunkt so vorbereitet, dass diese direkt vom Browser ausgeführt werden können. Dies spart eine Umwandlung beim Start der Anwendung und auch der Compiler muss nicht mit ausgeliefert werden. Angular-Anwendungen werden durch die Ahead-of-time-Optimierung kleiner und schneller.

Renderer

Status: Neues Konzept
Ab Angular 2 ist es möglich, eine Anwendung auf verschiedenen Plattformen mit spezifischen Oberflächen auszuführen. Das Framework wurde dafür vom DOM entkoppelt und in eine Ausführungsschicht und eine Renderschicht unterteilt. Komponenten und Direktiven enthalten keine direkte DOM-Manipulationen mehr, sondern beauftragen einen Renderer mit ebendieser. Der Renderer ist dabei ein vom Framework zur Verfügung gestellter Service, der per DI konsumiert werden kann.

Es gibt z. B. folgende Renderer:

  • Standard Renderer: Das Template wird in HTML geschrieben und im Browser gerendert, dieser Renderer ist standardmäßig in Angular enthalten.
  • Angular Universal [9]: Ist ein Backend Renderer der vom Angular-Team entwickelt wird. Die Seite wird auf dem Server gerendert und dann als HTML an den Browser gesendet. Dies hat den Vorteil, dass Browser und Suchmaschinen eine vollständige Seite geliefert bekommen, auch wenn diese keinen JavaScript-Code ausführen können.
  • NativeScript [10]: Ist ein Renderer für mobile Apps auf iOS und Android. Statt HTML-Elementen werden native UI-Elemente gerendert, so kann mit Angular eine native Smartphone-App entwickelt werden.

Fazit

Mit Angular 2 ist dem Team um Miško Hevery ein Framework gelungen, dass für die Zukunft gerüstet ist. Moderne Konzepte, unterstützt durch TypeScript, erleichtern das Entwickeln in größeren Teams und das Framework liefert viele Konzepte, die für eine Single-Page-Application benötigt werden, aus einer Hand. Wir sind von der überarbeiteten Version überzeugt und freuen uns auf den Alltagseinsatz. Beim Einsatz von Angular geht Google mit gutem Beispiel voran und hat bereits große Anwendungen mit Angular umgesetzt. Hierzu gehören u. a. Adsense und AdWords.

In einem weiteren Artikel werden wir detaillierter auf NativeScript eingehen und im Zusammenspiel mit Angular erläutern, wie native Android- & iOS-Apps entwickelt werden können.

Dies war der erste von zwei Teilen zum Thema "Angular 2".
Der zweite Teil befasst sich mit "Native Apps in JavaScript".

Autoren

Frederik von Berg

Frederik von Berg ist seit 2012 bei der W11K GmbH tätig. Dort beschäftigt er sich mit Projekten rund um Scala und Web-Entwicklung.
>> Weiterlesen

Philipp Burgmer

Philipp Burgmer ist ein Web-Native. Seit 15 Jahren in der Web-Programmierung unterwegs, gibt er sein angesammeltes und ständig aufgefrischtes Wissen gerne in Schulungen, Vorträgen bei Konferenzen und Blog-Einträgen weiter.
>> Weiterlesen
Das könnte Sie auch interessieren
Kommentare (0)

Neuen Kommentar schreiben