Eine Blog-Serie zum Vergleich von Elm, ReactJS und AngularJS anhand eines praktischen Beispiels. Hier geht es direkt zu [Teil 1, 3, 4, 5, 6]
“Ich packe in meinen Rucksack…. ein Quickstart-Tutorial”
Wer sich über die Entwicklung mit Angular2 informieren will, der landet innert Kürze auf dieser Url: https://angular.io/docs/js/latest/quickstart.html.
Persönlich hatte ich schon selbst Erfahrungen mit Angular 1.4 gesammelt, darum wusste ich, dass die Überschrift “5 MIN QUICKSTART” entweder als Hohn oder Lüge zu verstehen war. Und es war natürlich auch klar, dass es deutlich mehr zu tun gäbe, um mit AngularJS das selbe Resultat zu erreichen. Um es vorwegzunehmen: meine effektive Erfahrung übertraf meine Prophezeiung sogar noch!
Da müssen wir durch…
Typsicherheit
Javascript selbst kennt nur dynamisches Typing und wird zur Laufzeit interpretiert. Das lässt eine recht freie Art der Programmierung zu, führt aber z.B. zu einer Reihe von lästigen und unproduktiven Laufzeitfehlern aufgrund von Tipp- und Typ-Fehlern. Aus diesem Grund macht es durchaus Sinn, dass AngularJS nun (u.a.) auf dem typisierten Javascript-Derivat TypeScript von Microsoft aufbaut.
Angular2-Applikationen können zur Zeit mit ES5, ES2015 oder TypeScript geschrieben werden. Da Elm statisches Typing anbietet, kommt nur (die vorgeschlagene) Variante mit TypeScript für den Vergleich mit Elm in Frage
Projekt und Abhängigkeiten
Für die Entwicklung mit Angular2 MUSS node.js und npm installiert sein. Danach:
mkdir ang2-anagram && cd ang2-anagram
TypeScript Compiler Konfiguration
Da wir TypeScript verwenden, muss der Compiler mittels der Datei tsconfig.json konfiguriert werden.
{ "compilerOptions": { "target": "es5", "module": "system", "moduleResolution": "node", "sourceMap": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "removeComments": false, "noImplicitAny": false }, "exclude": [ "node_modules", "typings/main", "typings/main.d.ts" ] }
Wer den Inhalt dieser Datei genau verstehen will, benötigt bereits ein breites Wissen über die aktuellen Javascript-Standards, Build-Tools, Module-Loader und sogar node.js . Hier sei jedoch zusammengefasst erklärt, dass diese Datei den TypeScript-Compiler anweist, die TS-Dateien nach Javascript zu “transpilieren”, und zwar im ES5-Standard. Es sollen aber auch ES2016-Style Typ-Dekoration zugelassen werden (experimental). Die Ergebnisse werden mit SystemJS geladen und gebundelt.
Mit exclude
werden Verzeichnisse und Dateien von der Transpilation und dem Bundling ausgeschlossen.
Typ-Informationen (typings)
Weiter geht’s: als nächstes wird im referenzierten Tutorial der TypeScript-Compiler mit zusätzlichen Typinformationen versehen, indem Referenzen auf externe d.ts Dateien im File typings.json erfasst werden. Dies ist für unsere Zwecke momentan nicht nötig, wir haben also (noch) Glück und können diesen Schritt auslassen. Untenstehende Datei würde per Tutorial also ebenfalls eingebunden, und früher oder später werden wir die Datei auch nachliefern müssen, wenn oder falls wir externe Bibliotheken einbinden und uns dafür TypeScript-Type-Checking wünschen.
{ "ambientDependencies": { "es6-shim": "github:DefinitelyTyped/DefinitelyTyped/es6-shim/es6-shim.d.ts#7de6c3dd94feaeb21f20054b9f30d5dabc5efabd", "jasmine": "github:DefinitelyTyped/DefinitelyTyped/jasmine/jasmine.d.ts#5c182b9af717f73146399c2485f70f1e2ac0ff2b" } }
Wir verzichten demnach momentan auf Typinformationen für den ES6-Shim (Unterstützung von ES6-Features in Legacy-Browsern) und für das Testing-Framework Jasmine.
package.json
Es folgt die npm -Projekt-Definition im File package.json (auch wieder leicht gekürzt gegenüber dem Tutorial):
{ "name": "angular2-quickstart", "version": "1.0.0", "scripts": { "start": "tsc && concurrently \"npm run tsc:w\" \"npm run lite\" ", "tsc": "tsc", "tsc:w": "tsc -w", "lite": "lite-server" }, "license": "ISC", "dependencies": { "angular2": "2.0.0-beta.15", "systemjs": "0.19.26", "es6-shim": "^0.35.0", "reflect-metadata": "0.1.2", "rxjs": "5.0.0-beta.2", "zone.js": "0.6.10" }, "devDependencies": { "concurrently": "^2.0.0", "lite-server": "^2.2.0", "typescript": "^1.8.10" } }
Auch diese Datei ist wieder gepackt mit Entwicklungs- und Laufzeit- Abhängigkeiten. Bei den Entwicklungs-Abhängigkeiten (devDependencies ) sehen wir den typescript -Kompiler und den lite-server , der als Entwicklungs-Server dient. concurrently erlaubt die Ausführung mehrere Node-Prozesse gleichzeitig.
Bei den Laufzeit-Abhängigkeiten (dependencies ) wurde systemjs und es6-shim schon oben erwähnt, reflect-metadata bringt den Decorator-Support. rxjs und zone.js schlussendlich behandeln die reaktiven und asynchronen Aspekte der Applikation. Die werden wir sicher benötigen. Nicht für “Hello World”, aber für den Anagrammerator, und Elm unterstützt diese Funktionalität von Haus aus (diese Aussage, bzw. zu welchem Umfang muss noch genauer untersucht werden)
Die Sektion scripts beinhaltet übrigens nützliche npm-Ziele, um den TypeScript-Compiler oder den Entwicklungs-Web-Server anzustossen.
Das haut den stärksten Wanderer aus den Latschen
Die erzwungene Wander-Referenz soll an dieser Stelle die Aufmerksamkeit des Lesers wieder herstellen. Zwar hätte ich damit wohl besser gewartet bis nach dem nächsten Befehl:
npm install
Damit werden alle diese vorgängig konfigurierten Abhängigkeiten heruntergeladen und im node_modules/
-Ordner abgelegt. Unter Verwendung obiger Konfiguration (welche dem Quick-Start-Tutorial gegenüber noch geschrumpft wurde), sind das sage und schreibe 11‘646 Dateien, die 113 MB Disk-Platz beanspruchen.
Eine erste Vergleichsgrösse zu Elm (mal abgesehen von den offensichtlichen Unterschieden im “Ease-of-Getting-Started”):
Verwendete Abhängigkeiten für “Hello World” auf Disk:
Elm | Angular | Faktor | |
---|---|---|---|
Anzahl Files | 149 | 11‘646 | 78 |
Anzahl MB | 1.1 | 113 | 102 |
(Hierzu kommen übrigens auch noch die Node.js-Programmdateien, welche bei Elm nur für die Ausführung der REPL-Konsole gebraucht werden. Dafür muss für Elm ein Compiler installiert werden.)
Dieses Verhältnis wird sich auch beim Ausbau des Anagramm-Generators noch verändern und sollte ganz zum Schluss nochmals betrachtet werden.
Entwicklung mit Angular
Nun sind wir endlich soweit, dass wir eine Komponente erstellen können. Dazu erstellen wir erst das empfohlene app
-Verzeichnis:
mkdir app && cd app
Eine Angular2-Komponente besteht aus 3 Teilen:
- Imports: deklariert die verwendeten Bibliotheken
- Component Decorator: Metadaten zur Erstellung und Verwendung der Komponente
- Component Class: Die Komponente selbst enthält Eigenschaften und Applikations-Logik. In unserem Beispiel ist sie noch leer.
Das Beispiel aus dem Quick-Start sieht in etwas so aus (leicht angepasst, damit es der Elm-Vorlage mehr entspricht) – app.component.ts :
import {Component} from 'angular2/core'; @Component({ selector: 'my-app', template: '<span>Hello Angular</span>' }) export class AppComponent { }
Das nächste File heisst main.ts und wird im app/ -Ordner angelegt:
/// <reference path="../node_modules/angular2/typings/browser.d.ts" /> import {bootstrap} from 'angular2/platform/browser'; import {AppComponent} from './app.component'; bootstrap(AppComponent);
Das Skript benutzt die Browser-Bootstrapping-Variante des Frameworks, um unsere Komponente in diesem Kontext zu betreiben. Die erste Zeile ist kein Kommentar und stammt auch nicht aus dem Tutorial, sondern wurde nach Fehlern bei der Ausführung eingefügt. Gefunden wurde sie mit den hervorragenden und viel zu wenig gelobten Werkzeugen “Google Search” und StackOverflow.
Schliesslich erstellen wir im Projekt-Root-Verzeichnis (cd.. ) noch die Html-Datei index.html mit folgendem Inhalt:
<html> <head> <title>Angular 2 Anagrammerator</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- 1. Load libraries --> <!-- IE required polyfills, in this exact order --> <script src="node_modules/es6-shim/es6-shim.min.js"></script> <script src="node_modules/systemjs/dist/system-polyfills.js"></script> <script src="node_modules/angular2/es6/dev/src/testing/shims_for_IE.js"></script> <script src="node_modules/angular2/bundles/angular2-polyfills.js"></script> <script src="node_modules/systemjs/dist/system.src.js"></script> <script src="node_modules/rxjs/bundles/Rx.js"></script> <script src="node_modules/angular2/bundles/angular2.dev.js"></script> <!-- 2. Configure SystemJS --> <script> System.config({ packages: { app: { format: 'register', defaultExtension: 'js' } } }); System.import('app/main') .then(null, console.error.bind(console)); </script> </head> <!-- 3. Display the application --> <body> <my-app>Loading...</my-app> </body> </html>
Der Code ist zwar kommentiert, aber um genau zu verstehen, was hier vor sich geht, ist auch wieder ein solides Basiswissen und eine eingehende Erklärung vonnöten:
- Zuerst werden die vielen Sourcen aus den zuvor deklarierten Abhängigkeiten eingebunden, wie üblich das meiste davon nur, um mit IE kompatibel zu sein.
- Dann wird der Module-Loader konfiguriert, indem
js
als Standard-Suffix für zu verarbeitende Dateien und der Einstiegspunkt auf app/main festgelegt wird. (An dieser Stelle sei auf die überzeugende(re) Alternative webpack hingewiesen) - Zum Schluss folgt der Html-Body, der mit einem merkwürdigen Tag namens <my-app> startet, was mich erneut stutzen lässt, weil ich aus dem darüberstehenden Code nicht ableiten kann, woher das nun genau stammt und warum das gültiges HTML sein soll. Aber doch, im Dekorator der Klasse AppComponent findet sich ein Eintrag namens selector , der den Wert my-app hat…!
Auf die Datei styles.css verzichten wir, weil es a) schon mehr als genug zu tun gab und b) das Elm-Beispiel bisher auch kein Styling hat.
Geschafft! Wortwörtlich
Die Ausführung der ganzen Geschichte gestaltet sich mit npm start dann doch überraschend einfach. Durch die Ausführung des Befehls wird der TypeScript-Compiler im Watch-Modus gestartet (überwacht die Quell-Dateien und kompiliert sie bei Änderung neu) und gleichzeitig wird der Entwicklungs-Webserver lite-server gestartet und index.html angezeigt.
Wenn sich nun eine Datei verändert, wird die Seite im Browser neu geladen, was natürlich äusserst nützlich ist.
Bei der Entwicklung des Blog-Posts wurde an dieser Stelle der oben beschriebene Bug im Tutorial von angular.io festgestellt und behoben
Können wir endlich los….?
Zugegeben, das Angular Quick-Start-Tutorial beinhaltet viele Teile, die für die Ausgabe von “Hello World” nicht nötig gewesen wären. Das wird auch im offiziellen Quick-Start Tutorial so erwähnt, mit der Begründung, man wolle für grössere Absichten gerüstet sein und z.B. rxjs jetzt schon einbinden, damit man es später nicht vergesse.
Persönlich empfinde ich dieses Quick-Start-Tutorial alles andere als Quick. Es ist gespickt mit verwirrenden Begriffen aus diversen spezifischen Technologien, die ein Anfänger zu Überlesen gezwungen ist und bei welchen sich auch ein Profi fragen sollte, ob er sich damit auseinandersetzen will.
Aber da diese Details in der Natur der Sache liegen, welche ohne sie nicht oder nur schwer auskommt, kann es pädagogisch schon sinnvoll sein, den Benutzer früh und wiederholt an die sich über die Jahre entwickelte Komplexität im Javascript-Entwicklungs-Bereich heranzuführen.
Elm sitzt mit leichtem Gepäck und voller Tatendrang auf der Vorderkante des Sofas im Wohnzimmer von Angular, während dieser seinen Rucksack mit der Hälfte davon vollstopft und dabei unverständliches Zeug vor sich hin brummelt. Dabei dämmert es Elm langsam, worauf sie sich mit dieser gemeinsamen Wanderung eingelassen hatte. Sie zückt ihr vibrierendes Smartphone aus der Hüfttasche und sieht darauf eine Nachricht von ReactJS:
“Na, seid ihr bald alle soweit? Habe zur allgemeinen Unterhaltung noch meine Fiddle mitgenommen und warte beim Bahnhof auf Euch. Frag Angular bitte, ob ich von seinem TypeScript auch etwas abhaben kann, mein Arzt hat es mir für grössere Wanderungen empfohlen. Wir können die Details der Reise ja unterwegs besprechen. Ich muss Euch unbedingt von meinem neuen Bibel-Chor erzählen!”
Und kurz darauf:
“Babel-Core!! Blödes Autokorrekt…”
Und im Anhang fand sich ein Bild der Reisetasche von ReactJS:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>Hello, React!</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.1/react-with-addons.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.1/react-dom.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script> </head> <body> <div id="main"></div> <script type="text/babel"> ReactDOM.render( <span>Hello, React!</span>, document.getElementById('main') ); </script> </body> </html>
Elm muss schmunzeln, als sie Angular das Bild zeigt und sich dieser offensichtlich darüber aufregt, wie minimalistisch sich alle, ausser ihm selbst natürlich, auf diese Reise vorbereitet haben. Dabei weiss ja eigentlich niemand so genau, was sie auf diesem Ausflug alles erwarten würde.
Leicht aufgebracht schwingt er sich seinen Rucksack auf den Rücken und dabei fallen ihm einige .js und js.map – Dateien aus einer offengelassenen Seitentasche.
“Igitt, das ist ja ekelhaft!”, stösst es Elm auf, als diese die verstreuten Dateien genauer betrachtet. “Kannst Du die nicht besser verstauen? Das müssen ja nun wirklich nicht alle zu sehen bekommen!”.
Auf dem Weg zum Bahnhof schweigen beide. Die bedrückte Stimmung lockert sich erst, als sie dort auf ReactJS stossen, der die beiden schon von Weitem und heftig winkend dorthin beordert, wo ihr Weg sie ohnehin führen musste. Dem Funkeln in seinen Augen nach zu schliessen, hat er viel zu erzählen…
In der nächsten Etappe wird auch noch der letzte Reise-Teilnehmer React.JS vorgestellt.
Dann aber wenden wir uns wieder dem Anagramm-Generator und Elm zu, denn wie sich herausstellen wird, haben die anderen zwei während der Fahrt sowieso eine Menge Insider-Zeugs miteinander zu besprechen.