Angular 2 – Ein erster Überblick

Vom 21. - 23.03.2016 fanden in München wieder die AngularJS Days statt. Nachdem bereits als Beta verfügbar, war Angular 2 natürlich eines der Hauptthemen. Bereits am ersten Tag wurde das spürbar, da der ganztägige Workshop „Angular 2 Einführung“ der Refrenten Robin Böhm und Sacha Brink so überlaufen war, dass die Organisatoren diesen ab Mittag in einen größeren Saal verlegen mussten.

TypeScript

Der Workshop wollte uns Zuhörern die Grundlagen von Angular 2 näher bringen und uns nebenbei einen Einstieg in TypeScript geben. Bei TypeScript handelt es sich um einen, man höre und staune - von Microsoft entwickelten - Superset von JavaScript. Jeder JavaScript Code ist somit auch valider TypeScript Code, wobei TypeScript bereits heute den kommenden ES6 Standard abdeckt und z. T. Features wie Decorators, die exzessiv in Angular 2 verwendet werden, aus ES7 implementiert. TypeScript greift also kommenden ECMAScript Versionen vor und erlaubt somit die Verwendung kommender Funktionalität schon heute.

Angular 2 wird komplett in TypeScript entwickelt und nutzt dieses exzessiv. Aus Sicht eines PHP- oder Java Entwicklers hebt TypeScript Angular 2 auf ein völlig neues, bisher aus dem JavaScript Bereich nicht bekanntes, Qualitätsniveau. Möglicherweise sehen einige JavaScript Entwickler die Einführung von TypeScript kritisch, da Module, Komponenten, Klassen, Interfaces und Typisierung sicherlich auf den ersten Blick ein Schritt weg von der Unbeschwerheit bisheriger Entwicklungspraktiken sind. Hinsichtlich Wart- und Wiederverwendbarkeit werden so jedoch völlig neue Maßstäbe gesetzt, was insbesondere bei der Realisierung von großen Projekten eine maßgebliche Rolle spielen wird.

Folgende beispielhafte Implementierung einer Komponente, die aus dem Einführungsworkshop übernommen wurde, gibt uns einen kleinen Vorgeschmack auf die Verwendung von TypeScript in Angular 2. Hierbei liegt das Augenmerk nicht auf einer kompletten Implementierung, sondern soll nur einen Überblick über den aktuellen Stand von Angular 2 und TypeScript geben.

import {Component, OnInit} from 'angular2/core';

import {BookData, IBook} from '../../services/book-data/book-data';

 @Component({

  selector: 'book-index',

  templateUrl: 'app/components/book-index/book-index.html',

  styleUrls: ['app/components/book-index/book-index.css'],

  providers: [BookData],

  directives: [],

  pipes: []

})

 

 export class BookIndex implements OnInit {

  public books: IBook[];

  constructor(private bookData: BookData) {

  }

  ngOnInit(): void {

    this.books = this.bookData.getBooks();

  }

}

Die einzelnen Bereiche des Quellcodes wollen wir uns etwas detaillierter ansehen.

Klassen Importieren

Mit dem import Statement können Komponenten aus anderen Modulen in unserer aktuellen Komponente verfügbar gemacht werden.

import {Observable} from 'rxjs/Observable';

import {Component, OnInit} from 'angular2/core';

import {BookData, IBook} from '../../services/book-data/book-data';

In PHP erfolgt das in ähnlicher Form über das use Statement, in Java hingegen ebenfalls mit import. In der geschweiften Klammer wird angegeben, welche Komponentendefinition importiert werden soll, from referenziert das Modul in dem die Komponente exportiert wurde. Dabei wird, ausser es handelt sich um ein Core Module, der relative Pfad, ausgehend vom aktuellen Modul angegeben. Bei Core Modulen reicht der Namespace und das der Modulnamen, z. B. angular2/core. Beim Importieren können Klassenname, Konstanten - die ein Array mit Klassennamen enthalten müssen - aber auch einfache Funktionen angegeben werden.

Decorators

Bei Decorators handelt es sich um einen vorgeschlagenen Standard für ES6 um Klassen und deren Eigenschaften während der Entwicklung zu annotieren und zu modifizieren. TypeScript selbst bietet zwar keine Annotationen, jedoch in Form von Decorators die Möglichkeit solche zu implementieren. Diese Funktionalität hat das AngularJS Team bei der Entwicklung von Angular 2 genutzt.

So können mit TypeScript und AngularJS relativ einfach eigene Decorators implementiert werden, z. B. um eine Komponente vor unbefugtem Zugriff zu schützen. Die Implementierung eines solchen Decorators könnte wie folgt umgesetzt werden:

import {CanActivate, ComponentInstruction, Router} from 'angular2/router';

import {Injector} from 'angular2/core';

import {appInjector} from '../services/app.injector';

import {TokenService} from '../services/token.service';


export const NeedsAuthentication = () => {

  return CanActivate((to: ComponentInstruction, from: ComponentInstruction, target = ['/']) => {

    let injector: Injector = appInjector(); // Get the stored reference to the application injector

    let tokenService: TokenService = injector.get(TokenService);

    let router: Router = injector.get(Router);

    if (tokenService.token) {

      return true;

    }

     router.navigate(['/Login', { target }]);

    return false;

  });

}

Die Einbindung erfolgt dann als Annotation der jeweiligen Komponenten, im Beispiel ein Dashboard, auf das erst nach erfolgreichem Login zugegriffen werden kann

import {Component, OnInit} from 'angular2/core';

import {NeedsAuthentication} from '../../directives/needsAuthentication';

 @Component({

  selector: 'dashboard',

  templateUrl: 'app/components/dashboard/dashboard.component.html'

})

@NeedsAuthentication()

export class DashboardComponent implements OnInit {

}

Typisierung

Neben Decorators ist die Möglichkeit der strengen Typisierung ein weiteres Feature, das TypeScript mitbringt. So können die Eigenschaften einer Klasse, die Parameter von Methoden und deren Rückgabewerte mit entsprechenden Typen versehen werden.

export class BookIndex implements OnInit {

  public books: IBook[];

  constructor(private bookData: BookData) {

  }

 ngOnInit(): void {

    this.books = this.bookData.getBooks();

  }

}

Die Eigenschaft books wird hier als Array aus Instanzen, die das IBook interface implementieren, deklariert. Weiterhin wird im Konstruktor festgelegt, dass die übergebene Instanz bookData vom Typ BookData sein muss.

Hier kommt jedoch eine Eigenheit von TypeScript ins Spiel. Die dem Konstruktor übergebenen Parameter werden, ohne dass dies explizt implementiert werden muss, als Eigenschaften der Klasse gesetzt. Durch das Schlüsselwort private wird die übergebene Instanz von `BookData` somit automatisch als private Eigenschaft bookData der Klasse BookIndex deklariert.

Über ngOnInit(): void { ... } wird deklariert, dass diese Methode keinen Rückgabewert hat. Als Rückgabewert können natürlich auch skalare Datentypen, Callbacks, Interfaces oder Klassen verwendet werden.

Interfaces

Neben diversen anderen Sprachkonstrukten erlaubt TypeScript auch die Verwendung von Interfaces. Interfaces sind aus anderen Sprachen seit langem bekannt und finden damit, wenn auch nur indirekt - da TypeScript ja in JavaScript transpiliert wird, auch Einzug in JavaScript. Die Verwendung erfolgt analog zu anderen Sprachen wie PHP oder Java, eine weitere Erläuterung sollte somit nicht notwendig sein.

export class BookIndex implements OnInit {

  public books: IBook[];

  constructor(private bookData: BookData) {

  }

  ngOnInit(): void {

    this.books = this.bookData.getBooks();

  }

}

Im Beispiel implementiert die Klasse `BookIndex` das Interface OnInit, das die Methode ngOnInit spezifiziert und somit auch implementiert werden muss. ngOnInit ist einer der verfügbaren Lifecycle Hooks und wird aufgerufen, nachdem Angular 2 die Komponente initialisiert und die Input-Properties initialisiert hat.

Neben ngOnInit gibt es natürlich noch weitere Lifecycle Hooks, eine gute Übersicht bekommt man über die AngularJS Dokumentation.

RxJS

Neben TypeScript hat Microsoft mit RxJS fast unbemerkt eine weitere Library in Form einer API für asynchrone Programmierung beigesteuert. Bei RxJS handelt es sich um eine Kombination aus Observer, Operator Pattern und funktionaler Programmierung in Form einer JavaScript Library. So setzt Angular 2, anstatt der in Angular 1 verwendeten Promises, auf die über RxJS zur Verfügug gestellten Observables.

In RxJS können beliebig viele Observer an einem Observable angemeldet werden. Die Observer können dann auf ein oder eine Serie von Rückgabewerten, die beliebige Werte enthalten können, des Observables reagieren, ohne dass sich diese gegenseitig blockieren.

TypeScript

Der Workshop wollte uns Zuhörern die Grundlagen von Angular 2 näher bringen und uns nebenbei einen Einstieg in TypeScript geben. Bei TypeScript handelt es sich um einen, man höre und staune - von Microsoft entwickelten - Superset von JavaScript. Jeder JavaScript Code ist somit auch valider TypeScript Code, wobei TypeScript bereits heute den kommenden ES6 Standard abdeckt und z. T. Features wie Decorators, die exzessiv in Angular 2 verwendet werden, aus ES7 implementiert. TypeScript greift also kommenden ECMAScript Versionen vor und erlaubt somit die Verwendung kommender Funktionalität schon heute.

Angular 2 wird komplett in TypeScript entwickelt und nutzt dieses exzessiv. Aus Sicht eines PHP- oder Java Entwicklers hebt TypeScript Angular 2 auf ein völlig neues, bisher aus dem JavaScript Bereich nicht bekanntes, Qualitätsniveau. Möglicherweise sehen einige JavaScript Entwickler die Einführung von TypeScript kritisch, da Module, Komponenten, Klassen, Interfaces und Typisierung sicherlich auf den ersten Blick ein Schritt weg von der Unbeschwerheit bisheriger Entwicklungspraktiken sind. Hinsichtlich Wart- und Wiederverwendbarkeit werden so jedoch völlig neue Maßstäbe gesetzt, was insbesondere bei der Realisierung von großen Projekten eine maßgebliche Rolle spielen wird.

Folgende beispielhafte Implementierung einer Komponente, die aus dem Einführungsworkshop übernommen wurde, gibt uns einen kleinen Vorgeschmack auf die Verwendung von TypeScript in Angular 2. Hierbei liegt das Augenmerk nicht auf einer kompletten Implementierung, sondern soll nur einen Überblick über den aktuellen Stand von Angular 2 und TypeScript geben.

import {Component, OnInit} from 'angular2/core';

import {BookData, IBook} from '../../services/book-data/book-data';

 

@Component({

  selector: 'book-index',

  templateUrl: 'app/components/book-index/book-index.html',

  styleUrls: ['app/components/book-index/book-index.css'],

  providers: [BookData],

  directives: [],

  pipes: []

})


export class BookIndex implements OnInit {

  public books: IBook[];

  constructor(private bookData: BookData) {

  }

  ngOnInit(): void {

    this.books = this.bookData.getBooks();

  }

}

Die einzelnen Bereiche des Quellcodes wollen wir uns etwas detaillierter ansehen.

Klassen Importieren

Mit dem import Statement können Komponenten aus anderen Modulen in unserer aktuellen Komponente verfügbar gemacht werden.

import {Observable} from 'rxjs/Observable';

import {Component, OnInit} from 'angular2/core';

import {BookData, IBook} from '../../services/book-data/book-data';

In PHP erfolgt das in ähnlicher Form über das use Statement, in Java hingegen ebenfalls mit import. In der geschweiften Klammer wird angegeben, welche Komponentendefinition importiert werden soll, from referenziert das Modul in dem die Komponente exportiert wurde. Dabei wird, ausser es handelt sich um ein Core Module, der relative Pfad, ausgehend vom aktuellen Modul angegeben. Bei Core Modulen reicht der Namespace und das der Modulnamen, z. B. angular2/core. Beim Importieren können Klassenname, Konstanten - die ein Array mit Klassennamen enthalten müssen - aber auch einfache Funktionen angegeben werden.

Decorators

Bei Decorators handelt es sich um einen vorgeschlagenen Standard für ES6 um Klassen und deren Eigenschaften während der Entwicklung zu annotieren und zu modifizieren. TypeScript selbst bietet zwar keine Annotationen, jedoch in Form von Decorators die Möglichkeit solche zu implementieren. Diese Funktionalität hat das AngularJS Team bei der Entwicklung von Angular 2 genutzt.

So können mit TypeScript und AngularJS relativ einfach eigene Decorators implementiert werden, z. B. um eine Komponente vor unbefugtem Zugriff zu schützen. Die Implementierung eines solchen Decorators könnte wie folgt umgesetzt werden:

import {CanActivate, ComponentInstruction, Router} from 'angular2/router';

import {Injector} from 'angular2/core';

import {appInjector} from '../services/app.injector';

import {TokenService} from '../services/token.service';


export const NeedsAuthentication = () => {

  return CanActivate((to: ComponentInstruction, from: ComponentInstruction, target = ['/']) => {

    let injector: Injector = appInjector(); // Get the stored reference to the application injector

    let tokenService: TokenService = injector.get(TokenService);

    let router: Router = injector.get(Router);


    if (tokenService.token) {

      return true;

    }


    router.navigate(['/Login', { target }]);


    return false;

  });

}

Die Einbindung erfolgt dann als Annotation der jeweiligen Komponenten, im Beispiel ein Dashboard, auf das erst nach erfolgreichem Login zugegriffen werden kann

import {Component, OnInit} from 'angular2/core';

import {NeedsAuthentication} from '../../directives/needsAuthentication';


@Component({

  selector: 'dashboard',

  templateUrl: 'app/components/dashboard/dashboard.component.html'

})

@NeedsAuthentication()

export class DashboardComponent implements OnInit {

}

Typisierung

Neben Decorators ist die Möglichkeit der strengen Typisierung ein weiteres Feature, das TypeScript mitbringt. So können die Eigenschaften einer Klasse, die Parameter von Methoden und deren Rückgabewerte mit entsprechenden Typen versehen werden.

export class BookIndex implements OnInit {

  public books: IBook[];

  constructor(private bookData: BookData) {

  }


  ngOnInit(): void {

    this.books = this.bookData.getBooks();

  }

}

Die Eigenschaft books wird hier als Array aus Instanzen, die das IBook interface implementieren, deklariert. Weiterhin wird im Konstruktor festgelegt, dass die übergebene Instanz bookData vom Typ BookData sein muss.

Hier kommt jedoch eine Eigenheit von TypeScript ins Spiel. Die dem Konstruktor übergebenen Parameter werden, ohne dass dies explizt implementiert werden muss, als Eigenschaften der Klasse gesetzt. Durch das Schlüsselwort private wird die übergebene Instanz von `BookData` somit automatisch als private Eigenschaft bookData der Klasse BookIndex deklariert.

Über ngOnInit(): void { ... } wird deklariert, dass diese Methode keinen Rückgabewert hat. Als Rückgabewert können natürlich auch skalare Datentypen, Callbacks, Interfaces oder Klassen verwendet werden.

Interfaces

Neben diversen anderen Sprachkonstrukten erlaubt TypeScript auch die Verwendung von Interfaces. Interfaces sind aus anderen Sprachen seit langem bekannt und finden damit, wenn auch nur indirekt - da TypeScript ja in JavaScript transpiliert wird, auch Einzug in JavaScript. Die Verwendung erfolgt analog zu anderen Sprachen wie PHP oder Java, eine weitere Erläuterung sollte somit nicht notwendig sein.

export class BookIndex implements OnInit {

  public books: IBook[];

  constructor(private bookData: BookData) {

  }


  ngOnInit(): void {

    this.books = this.bookData.getBooks();

  }

}

Im Beispiel implementiert die Klasse `BookIndex` das Interface OnInit, das die Methode ngOnInit spezifiziert und somit auch implementiert werden muss. ngOnInit ist einer der verfügbaren Lifecycle Hooks und wird aufgerufen, nachdem Angular 2 die Komponente initialisiert und die Input-Properties initialisiert hat.

Neben ngOnInit gibt es natürlich noch weitere Lifecycle Hooks, eine gute Übersicht bekommt man über die AngularJS Dokumentation.

RxJS

Neben TypeScript hat Microsoft mit RxJS fast unbemerkt eine weitere Library in Form einer API für asynchrone Programmierung beigesteuert. Bei RxJS handelt es sich um eine Kombination aus Observer, Operator Pattern und funktionaler Programmierung in Form einer JavaScript Library. So setzt Angular 2, anstatt der in Angular 1 verwendeten Promises, auf die über RxJS zur Verfügug gestellten Observables.

In RxJS können beliebig viele Observer an einem Observable angemeldet werden. Die Observer können dann auf ein oder eine Serie von Rückgabewerten, die beliebige Werte enthalten können, des Observables reagieren, ohne dass sich diese gegenseitig blockieren.



Das folgende Beispiel zeigt eine stark vereinfachte Version eines LoginService, der anstatt, des nach einem erfolgreichen Login erhaltenen Token, ein Observable zurückgibt.

import {Injectable} from 'angular2/core';';

import {Observable} from 'rxjs/Observable';

import {Http} from 'angular2/http';

import {LoginDto} from '../dtos/login.dto';

import {TokenDto} from '../dtos/token.dto';

import 'rxjs/Rx';


@Injectable()

export class LoginService {

  public constructor(private http: Http) {}

  public login(dto: LoginDto): Observable {

    return Observable.create((observer)=> {

    // try to login here, e. g. against a webservice

    this.http.post("http://my-service", "user=test&pass=test")

        .map(response => response.json())

        .subscribe(

          (tokenDto) => {

            // save the token here, e. g. to browser local storage

          },

          (error) => observer.error(error),

          () => observer.complete()

        );

      });

}

Im Beispiel wird mit der create() Methode ein Observer, der einen HTTP POST Request auf einen Webservice durchführt, angemeldet. Dazu wird mit der map() Methode versucht, die Rückgabewerte des Observable, nach erfolgreichem Login auf das erwartete Ergebnis, im Beispiel ein TokenDto, zu casten. Anschließend werden drei Ereignisse implementiert.

Erstens wenn der Login erfolgreich war und eine TokenDto Instanz zurückgegben wurde, wird diese im Local Storage des Browsers gespeichert (nicht ausimplementiert). Zweitens wenn während des Login's ein Fehler aufgetreten ist und Drittens wenn der Observer erfolgreich verarbeitet werden konnte.

Im Kapitel Templates/Forms wird gezeigt, wie der LoginService und das zurückgegebene Observable verwendet werden können.

Big Picture

Einen guten Überblick über Angular 2 und seine Architektur bekommt man über folgende Grafik:


In den folgenden Kapiteln wird auf Teile der Architektur eingegangen und der grundlegende Aufbau einer Angular JS Anwendung erklärt.

Components

Jede Angular 2 Anwendung besteht aus mindestens einer oder beliebig vielen Komponenten und ist wie ein Baum aufgebaut. Die Root-Komponente ist der Einstiegspunkt in die Anwendung und lädt die jeweiligen Sub-Komopnenten bei Bedarf nach. Eine Komponente besteht hierbei aus mindestens einer Klasse, einem Template und beliebig vielen Stylesheets.

Der Aufbau einer Angular 2 Anwendung hat in etwa die folgende Struktur

app

|- components

|- book-index

|- book-index/book-index.ts

|- book-index/book-index.html

|- book-index/book-index-01.css

|- book-index/book-index-02.css

|- services

|- services/book-data

|- services/book-data/book-data.ts

|- app.ts

|- app.html

|- main.ts

|- index.html

|- favicon.ico

Die Hauptkomponente wird hierbei in der Datei app.ts mit dem zugehörigen Template app.html implementiert, das Bootstrapping der Anwendung erfolgt in der Datei main.ts. Beispiele für die Implementierung einer Hauptkomponente und des Bootstrappings werden im Kapitel Routing gezeigt.

Routing

Angular 2 bringt Out-of-the-Box ein Standardrouting mit, das sich einfach anpassen und erweitern lässt. Im Gegensatz zu Angular 1 wird hier standardmäßig auf das # nach der URI verzichtet. Das Verhalten lässt sich jedoch über eine Anpassung der Konfiguration wieder auf das alte Verhalten anpassen.

Das Routing wird grundsätzlich während des Bootstrappings initialisiert. Durch das Überschreiben des Standardroutings LocationStrategy mit der Klasse HashLocationStrategy mittels Dependency Injection

import {provide} from 'angular2/core';

import {AppComponent} from './app/workshop';

import {bootstrap} from 'angular2/platform/browser';

import {ROUTER_PROVIDERS, LocationStrategy, HashLocationStrategy, } from 'angular2/router';


bootstrap(AppComponent, [

  ROUTER_PROVIDERS,

  provide(LocationStrategy, {

    useClass: HashLocationStrategy

  })

]);

wird das aus Angular 1 bekannte Verhalten wiederhergestellt. Das eigentliche Routing kann dann in der Hauptkomponente der Application konfiguriert werden. Auch das erfolgt wieder über eine Annotation, nämlich @RouteConfig. Im Beispiel werden zwei Routen konfiguriert. Zum Einen, die zum Dashboard, wobei es sich um die Standardroute handelt, die aufgerufen wird, wenn keine direkt angegeben wird. Zum Anderen die für den Login, auf die der User automatisch redirected werden soll, wenn er nicht schon authentifiziert ist.

Um die Routen konfigurieren und das Tag <router-outlet> im Template der Komponente, das den View-Port, in dem der Router die Komponenten rendert, verwenden zu können, müssen in der @Component Annotation die Directiven ROUTE_DIRECTIVES registriert werden.

import {Component} from 'angular2/core';

import {RouteConfig, ROUTER_DIRECTIVES, ROUTER_PROVIDERS} from 'angular2/router';

import {APP_SERVICES} from './services/all';

import {DashboardComponent} from './components/dashboard/dashboard.component';

import {LoginFormComponent} from './components/login-form/login-form.component';


@RouteConfig([

  {

    path: '/dashboard',

    name: 'Dashboard',

    component: DashboardComponent,

    useAsDefault: true

  },

  {

    path: '/login',

    name: 'Login',

    component: LoginFormComponent

  }

])


@Component({

  selector: 'admin',

  templateUrl: 'app/app.component.html',

  providers: [ROUTER_PROVIDERS, APP_SERVICES],

  directives: [ROUTER_DIRECTIVES, LoginFormComponent, DashboardComponent]

  })

export class AppComponent { }

Neben dem Attribut path der @RouteConfig Annotation, auf das die Path Info nach dem index.html gemappt wird, kann auch ein Attribute name vergeben werden. Über den Wert dieses Attributs kann beim Setzen der Links, wenn anstatt der Route der Name verwendet wird <a [routerLink]=" ['Book', { isbn: book.isbn }] ">{{book.isbn}}</a>, sichergestellt werden, dass bei späteren Änderungen der Route keine weiteren Anpassungen am Code notwendig sind.

Annotationen

Jede Komponente, jeder View und jede Directive in Angular 2 muss über eine Annotation konfiguriert werden. Hierbei werden u. a. Metadaten wie die URL zum Template oder zu den Dateien mit den CSS Styles angegeben. Das folgende Beispiel zeigt die Annotation für eine Komponente.

@Component({

  selector: 'book-index',

  templateUrl: 'app/components/book-index/book-index.html',

  styleUrls: ['app/components/book-index/book-index.css'],

  providers: [BookData],

  directives: [],

  pipes: []

})

Das Attribut templateUrl gibt den relativen Pfad zum HTML Template unserer Komponente an. Zusätzlich können wir über das Attribut styleUrls ein Array mit beliebig vielen Pfad zu den Dateien mit den CSS Styles der Komponente referenzieren. Damit die Komponente weiß, an welchen DOM Knoten sie gebunden werden soll, wird über das selector Attribut ein entsprechender CSS Selektor angegeben. In diesem Fall wird die Komponente also an den <book-index></book-index> Knoten im Template app/componenents/book-index/book-index.html gebunden.

Das Attribute providers erwartet ein Array mit Klassennamen, deren Instanz in die aktuelle Komponente, in diesem Beispiel über den Klassen-Konstruktor, injected werden sollen. Auf das Thema Dependency Injection werden wir anschließend noch etwas detaillierter eingehen.

Angular 2 bringt natürlich Out-of-the-Box die notwendigen Annotationen mit, die für die Implementierung einer Anwendung notwendig sind, erlaubt über Decorators aber auch die Implementierung eigener Annotationen.

Dependency Injection

Gerade bei der Entwicklung mittlerer und großer Anwendungen ist der Einsatz des Dependency Injection Patterns aus Gründen der Wiederverwendbar- und Testbarkeit unerlässlich. Mit Angular 2 wird Dependency Injection sowohl als Pattern als auch als Framework eingesetzt. Ein detaillierter Einblick wie Angular 2 DI als Pattern und als Framework einsetzt würde den Rahmen dieses Blogbeitrags sprengen. Mit folgendem Beispiel erhält man jedoch einen guten Eindruck, wie DI in Angular 2 eingesetzt wird.

import {Observable} from 'rxjs/Observable';

import {Component, OnInit} from 'angular2/core';

import {BookData, IBook} from '../../services/book-data/book-data';


@Component({

  selector: 'book-index',

  templateUrl: 'app/components/book-index/book-index.html',

  styleUrls: ['app/components/book-index/book-index.css'],

  providers: [BookData],

  directives: [],

  pipes: []

})

export class BookIndex implements OnInit {

  public books: IBook[];

  constructor(private bookData: BookData) {

  }


  ngOnInit(): void {

    this.books = this.bookData.getBooks();

  }

}

Das Beispiel geht davon aus, dass ein Service BookData implementiert wurde, der ein Array mit Büchern zurückgibt, wobei jedes Buch das IBook Interface implementiert. Dieses Interface, ebenfalls im Service definiert, wird importiert und kann somit in der Komponente verwendet werden. Eine Beispielimplementierung des Interfaces und der Serviceklasse ist im nächsten Kapitel Services zu finden.

Um den Service in der Komponente verwenden zu können sind drei Schritte notwendig:

  • 1. In Schritt eins werden die Serviceklasse und das Interface in unsere Komponente importiert
  • 2. Im zweiten Schritt wird der Service über die Annotation @Component im Attribut providers angemeldet
  • 3. Drittens muss die BookData Klasse mit @Injectable annotiert werden
  • 4. Abschließend kann dieser dann per Dependency Injection und durch die Typisierung als BookData über den Konstruktor injected werden

Services

Ein zentraler Bestandteil von Angular 2 sind Services. Services sind Klassen, die Funktionalitäten kapseln und diese beliebig vielen Komponenten zur Verfügung stellen. So bieten sich Services besonders zur Separierung der Businesslogik an. Ein einfacher Service, der Bücher für eine Bibliothek verwaltet, kann wie folgt aufgebaut sein.

import {Injectable} from 'angular2/core';


export interface IBook {

  isbn: string;

  title: string;

  author: string;

}


@Injectable()

export class BookData {

  private books: IBook[];

  constructor() {

    this.books = [

      { "isbn": "978-1784398767", "title": "Symfony2 Essentials", "author": "Wojciech Bancer" },

      { "isbn": "978-1782164104", "title": "Persistence in PHP with Doctrine ORM", "author": "Kévin Dunglas" }

    ];

  }

  public getBooks(): IBook[] {

    return this.books;

  }

}

Nach dem Import der Injectable Annotation wird das Interface IBook definiert. Dieses Interface beschreibt ein Buch der Beispielanwendung. Als Eigenheit von TypeScript reicht es, im Gegensatz zu anderen Sprachen, aus, ein Array vom Typ IBook als Rückgabewert der Methode getBooks() zu definieren, damit die im JSON Format gespeicherten Datensätze das Interface auch tatsächlich implementieren.

Die @Injectable Annoation registriert den Provider für die Dependency Injection, was die einfache Verwendung des Services in einer Komponente ermöglicht. In der Realität würde der Service natürlich noch weitere Methoden zur Verfügung stellen und die Bücher aus einer Datenbank oder über einen Webservice holen. Zur Veranschaulichung sollte das Beispiel jedoch vollkommen ausreichen.

Directives

Bei Directiven handelt es sich grundsätzlich um Komponenten, die kein eigenes Template mitbringen. Ein gutes Beispiel wäre z. B. ein Zurück-Button, der über einen Selector in das Header Template einer Seite integriert werden soll.

import {Directive, HostListener} from 'angular2/core';

import {Location} from 'angular2/router';

import {PlatformInformationService} from '../services/platform.information.service';


@Directive({

  selector: '[back-button]',

  host: { '[attr.hidden]': 'isHidden' }

})

export class BackButtonDirective {

  public isHidden: boolean = true;

  constructor(private _platformInformationService: PlatformInformationService, private _location: Location) {

    this.setHidden();

  }


  private setHidden(): void {

    this.isHidden = this._platformInformationService.isWeb;

  }


  @HostListener('click')

  public onClick() {

    this._location.back();

  }

}

Damit die Klasse BackButtonDirective korrekt geladen werden kann, muss Diese noch zusätzlich in der Komponente mit

import {Component, OnInit} from 'angular2/core';

import {BackButtonDirective} from '../../directives/back.button.directive';


@Component({

  selector: 'boardz-header',

  templateUrl: 'app/components/header/header.html',

  directives: [BackButtonDirective]

})

export class HeaderComponent implements OnInit {

}

angmeldet werden. Anschließend kann die Integration im Template, einfach über das Setzten das Attributs back-button erfolgen, was z. B. wie folgt aussehen könnte

<li back-button>

  <a>

    <i class="fa fa-arrow-circle-left"></i>

  </a>

</li>

Templates/Forms

Templates und Forms werden zusammen mit der jeweiligen Komponente abgespeichert. Dabei handelt es sich um simple HTML Dateien, die mit Angular 2 spezifischen Tags erweitert und somit an das Model gebunden werden können.

Das Beispiel zeigt ein stark vereinfachtes HTML ohne Validierung für einen Login Dialog. Über die NgForm Direktive wird das Formular um zusätzliche Funktionen erweitert, und hält zur Laufzeit die mit NgControl deklarierten Eingabefelder, die wiederum zur Verwaltung des Formularstatus und der Validierung notwendig sind.

<div class="container">

  <h1>Login Form</h1>

  <form (ngSubmit)="login()" #loginForm="ngForm">

    <div class="form-group">

      <label for="username">Username</label>

      <input type="text" class="form-control" required [(ngModel)]="dto.username" ngControl="username">

    </div>

    <div class="form-group">

      <label for="password">Password</label>

      <input type="password" class="form-control" required [(ngModel)]="dto.password" ngControl="password">

    </div>

    <button type="submit" class="btn btn-default">Login</button>

  </form>

</div>

Über die die NgModel Direktiven werden die Eingabefelder per Two-Way Data Binding an die dto Instanz unserer zugehörigen Komponenten gebunden, die als public Eigenschaft deklariert sein muss.

import {Component, provide} from 'angular2/core';

import {Router} from 'angular2/router';

import {NgForm} from 'angular2/common';

import {LoginDto} from '../../dtos/login.dto';

import {LoginService} from '../../services/login.service';

import {LogService} from '../../services/log.service';

import {ConsoleLogService} from '../../services/consoleLog.service';

import {Configuration} from '../../app-config';


@Component({

  selector: 'login-form',

  templateUrl: 'app/components/login-form/login-form.component.html',

  providers: [provide(LogService, { useClass: ConsoleLogService }), LoginDto, LoginService, Configuration]

})

export class LoginFormComponent {

  public hasError: boolean = false;

  public constructor(

    private router: Router,

    private loginService: LoginService,

    private logService: LogService,

    public dto: LoginDto) {

  }


  public login() {

    this.loginService.login(this.dto)

        .subscribe(

          () => {

            this.setError(false);

            this.router.navigate(['Dashboard']);

          },

          () => {

            this.setError(true);

            this.logService.logDebug('Login was unsuccessful.');

          }

        );

  }


  setError(value: boolean) {

    this.logService.logDebug('LoginForm.setError: Setting error state to: ' + value);

    this.hasError = value;

  }

}

Ändern sich die Daten im Model, ändern sich automatisch auch die Daten im Template.

Die Möglichkeiten, die Angular 2 beim Templating bietet sind extrem umfangreich und würden den Umfang dieses Blogbeitrags bei weitem sprengen. Allein das nur in kleinen Auszügen erklärte Beispiel gibt jedoch einen Eindruck von der Mächtigkeit mit der Angular 2 den Entwickler ausstattet und ihn bei der Entwicklung einer extrem interaktiven Oberfläche unterstützt.

Angular-CLI

Bei Angular-CLI handelt es sich um ein Tool, das derzeit Funktionalitäten für

  • das Scaffolding von Angular 2 Projekten, Komponenten, Direktiven, Services und Routen
  • das Ausführen von Unit und E2E Tests mit Protractor
  • dem Starten/Stoppen der Laufzeitumgebung
  • dem Formatieren des Source-Codes
  • dem Deployment der Anwendung als Github Page

bereitstellt. Angular-CLI befindet sich noch in einem sehr frühen Entwicklungsstadium, macht den Einstieg in Angular 2 jedoch sehr einfach, ein Blick lohnt sich also auf jeden Fall. Angular-CLI setzt mindestens eine node.js Installation > 4 voraus.

Hybrid und Desktop Anwendung

Nach Fertigstellung einer Anwendung soll diese z. T. nicht nur im Browser ausgeführt, sondern eben auch als Hybrid und/oder Desktop Anwendung ausgeliefert werden. Abgesehen davon, dass man sich natürlich schon im Vorfeld dazu Gedanken machen muss, bieten die Projekte Apache Cordova und Electron die dazu notwendige Funktionalität.

Apache Cordova, das als Open-Source Version aus Phone Gap hervorgegangen ist, deckt hierbei den Mobile, Electron den Desktop Bereich ab. Beide Tools lassen sich einfach installieren und integrieren sich nahtlos in die Entwicklung einer Angular 2 Anwendung. Bereits während der Entwicklung lässt sich so laufend die Kompatibilität mit den jeweiligen Zielplattformen prüfen und die Anwendung bei Problemen auf der Zielplattform debuggen.

Als etwas komplizierter hat sich die Einbindung des Android SDK's im Rahmen von Apache Cordova gezeigt, da der SDK zum einen relativ viel Speicherplatz und entsprechend lang für den Download benötigt, zum Anderen da die Gerätesimulation quälend langsam ist. Hier bietet sich die Möglichkeit an auf andere Lösungen wie Genymotion auszuweichen, die hier mit einer wesentlich besseren Performance aufwarten können.

Fazit

Dass sich das AngularJS Team für TypeScript entschieden hat, zeigt bereits heute klar die Richtung in die sich JavaScript künftig entwickeln wird. Und zwar weg von der einfachen ScriptSprache, hin zu einer ausgewachsenen Programmiersprache, die die Entwicklung großer Anwendungen erlaubt und vollständig unterstützt.

Dass der TypeScript Code nicht tatsächlich ausgeführt, sondern lediglich in ES5 kompatibles JavaScript transpiliert wird, kann man als Stärke oder Schwäche sehen. Als Stärke, da man als Entwickler nun die Möglichkeit hat, die neuen Features einschließlich einer bessern IDE Unterstützung zu nutzen, als Schwäche, da Interfaces, Typisierung und Klassen natürlich nach der Transpilierung nicht mehr vorhanden sind und somit einfach umgangen werden können. Mit ES6 wird jedoch der größte Teil von TypeScript Bestandteil von JavaScript und die Sprache um ein gutes Stück erwachsener.

TypeScript öffnet bereits jetzt für viele PHP und Java Entwickler die Tür in die JavaScript Welt, was zweifellos dazu führen wird, dass sich die Sprache noch schneller verbreitet und als „echte“ Programmiersprache akzeptiert wird.

Mit Angular 2 veröffentlich Google in den kommenden Monaten ein ausgewachsenes Framework, das sicherlich über die nächsten Jahre das JavaScript Ökosystem dominieren und auch stark verändern wird. Man muss davon ausgehen, dass Angular 2 und andere JavaScript Frameworks, die aktuell noch im Windschatten von Angular 2 fahren, die Aufmerksamkeit vieler Entwickler, die bisher auf andere Sprachen gesetzt haben, auf sich ziehen wird. Wenn nicht, insbesondere im PHP Ökosystem, ein Umdenken stattfindet und Innovationen zugelassen werden, wird mittelfristig ein nicht unerheblicher Teil der Anwendungen, die bisher in PHP oder Java geschrieben wurden, vom Markt verdrängt oder eben mit JavaScript umgesetzt.

Dass sich mittlerweile alle Big Player in irgendeiner Form im JavaScript Umfeld engagieren zeigt, dass der Sprache eine extrem hohe Relevanz beigemessen wird. Diese Relevanz zusammen mit einer stark wachsenden Community wird, im Gegensatz zu anderen Sprache, dazu führen, dass die Weiterentwicklung auch künftig mit extrem hoher Geschwindigkeit vorangetrieben und die dadurch entstehenden Innovationen maßgeblich für eine Veränderung der bisher bekannten IT Landschaft verantwortlich sind. Wer sich dieser Erkenntnis verweigert, wird mittelfristig am Markt, in welcher Form auch immer er dort auftritt, keinen Platz mehr finden.

Links


    Neueste Posts

    Die größten Akquisitionen von Google, Amazon und Apple und einige Learnings daraus!
    Neue Ausgabe des eStrategy-Magazin verfügbar 360 Stories – ein Spiel für Teams Die B2B E-Commerce Pyramide
    Digital Storytelling - ein Kurztrip in die Kreativität und wieder zurück

    Archiv

    Dezember November Oktober September August Juli Juni Mai April März Februar Januar
    Dezember November Oktober September August Juni Mai April Februar Januar
    Dezember November Oktober September August Juli Juni März Februar
    Oktober September August Juli Juni Mai April März Januar
    Dezember November Oktober September August Juli Mai April Februar
    November Oktober September April Februar
    Dezember September Juni Mai Februar Januar
    Juli Mai April März Februar Januar
    September August Juli März
    Oktober September Juli Juni Mai März Februar
    Februar

    Kategorien

    E-Commerce Unternehmensmeldung Online-Marketing Magento Commerce Neos TYPO3 SEO SEA Usability Digitale Transformation Agile Projektentwicklung Corporate Web Analytics Künstliche Intelligenz Mobile Marketing Social Media Veranstaltungen Research & Development

    Unser Herz schlägt online -
    Deins Auch?


    Wir stellen uns jeden Tag neuen Heraus-forderungen des Online-Business – immer auf der Suche nach spannenden Lösungs-ansätzen und sinnvollen Technologien. Eine Vielzahl namhafter Kunden vertrauen auf das Online Know-how „Made in Kolbermoor / Rosenheim und München“. 

    Lust auf TechDivision? Hier geht zu unseren Stellenanzeigen

    eStrategy Magazin


    Erfahren Sie mehr zu den Themen E-Commerce, Online-Marketing, Mobile, Projektmanagement, Webentwicklung und E-Recht in unserem kostenlosen Online-Magazin.

    Jetzt herunterladen!

    Whitepaper:
    Agiles Projektmanagement


    In unserem kostenlosen Whitepaper versuchen wir Basiswissen und Erfahrungen aus vielen Jahren täglicher Projekt- und Unternehmenspraxis zu vermitteln, mit denen Sie die Anforderungen des Arbeitslebens von Heute besser bewältigen können.

    Jetzt herunterladen!

    Autor

    Header

    Subheader

    Autor

    Tim Wagner Geschäftsführer / CTO