JavaScript MVC Frameworks - Teil 3

EmberJS

Im letzten Teil des Artikels habe ich ein wenig über die Konzepte und die Umsetzung unserer Beispielapplikation mithilfe von AngularJS erzählt. In diesem Teil möchte ich nun mein Augenmerk auf EmberJS legen.

EmberJS wird häufig als die bessere Alternative (z.B. hier: http://eviltrout.com/2013/06/15/ember-vs-angular.html) zu AngularJS dargestellt. Das hat mich natürlich neugierig gemacht und so musste ich EmberJS im Rahmen dieser Reihe ausprobieren.
EmberJS kommt aus dem Umfeld von Ruby on Rails und verfolgt auch einen ähnlichen Ansatz der Convention over Configuration, wie wir später noch genauer sehen werden.

Die Konzepte

Zunächst mal: Bei der Erstellung dieses Artikels und der Anwendung habe ich EmberJS in der Version 1.6.1 verwendet, einige Dinge mögen sich in Zukunft noch ändern.

Die Catchphrases von EmberJS sind: “A framework for creating ambitious web applications” und “More productive out of the box”. Das fasst die Kernideen ganz gut zusammen.
Ähnlich wie bei Ruby on Rails gibt mir EmberJS einen Satz von Konventionen vor, mit denen ich – so ich diese denn kenne – nicht nur triviale, sondern auch anspruchsvolle Anwendungen erstellen kann. Die Konsequenz ist dann auch, dass ich mich als fremder Entwickler in Anwendungen, die diesen Konventionen folgen auch recht schnell zurechtfinden kann.

Vergleichbar mit AngularJS, welches eher das Augenmerk auf das Markup legt, geht EmberJS hier einen eher “Code-lastigen”-Weg. Man arbeitet selten mit Plain Old JavaScript Objects, sondern man leitet recht häufig von Framework Artefakten ab.

EmberJS setzt das Pattern Model View ViewModel um.

Schauen wir uns doch auch hier einmal die einzelnen Komponenten an: http://emberjs.com/guides/concepts/core-concepts/

Zunächst mal eine grobe Übersicht in Anlehnung an http://guides.emberjs.com/v1.10.0/controllers/ :

Zunächst einmal kennt EmberJS das Konzept des persistenten Models :
Jeder clientseitigen Route (grob: jeder URL, aber siehe unten) kann in EmberJS ein eigenes Model zugeordnet werden. Dies sieht dann  wie folgt aus:

App.ConcertsRoute = Ember.Route.extend({
 model:function(){
  return {…}
   }
});

Es existiert also “irgendwo” ein Sourcecode-Schnipsel wie oben, der dieses Mapping durchführt, wichtig ist hier die Konvention, dass es eine Funktion mit dem Namen “model” geben muss. Üblicherweise verwendet in EmberJS zusätzlich das Unterframework Ember-Data für diese Aufgabe. Dieses kümmert sich auch gleich darum, dass Daten von einer REST Schnittstelle geladen werden können, ohne dass man das von Hand machen müsste. Dies kann dann so aussehen:

App.Concert = DS.Model.extend({
 name: DS.attr('string'),
 datum:  DS.attr('date')
});

Ein Controller (ViewModel) hingegen bildet den Applikations-Zustand ab. Auch wird er in EmberJS dazu verwendet ein Modell mit DisplayLogik zu dekorieren, welche man vielleicht nicht persistieren möchte. Ein Controller ist implizit über die Route einem Modell zugeordnet .

Ein ganz simpler Controller sieht dann so aus:

App.ConcertsController = Ember.ObjectController.extend({
 actions:{
   addConcert: function () {}
 }
);

Ein Template schlussendlich kennt einen Controller (der wiederum sein Modell kennt) und kann dieses darstellen.
Der Template-Mechanismus den EmberJS verwendet ist Handlebars . Die Idee hinter Handlebars ist, dass Logik tendenziell aus den Templates verschwinden soll, die Logik soll im Controller stattfinden. Über sogenannte Outlets können bestimmte Bereiche des Templates anhand der Route ausgetauscht werden.

<scripttype="text/x-handlebars" data-template-name=”concerts”>
  Konzert,<strong>{{ name }} {{ datum }}</strong>!
  </script>

{{ name }}und {{ datum }} sind ähnlich wie in AngularJS nun auch per Data-Binding mit dem Modell bzw. dem Controller verbunden.

Der Router und die Route binden schlussendlich eine URL an einen Controller bzw. an ein Modell.

App.Concert.map(function() {
 this.resource('concerts');
});

Hier wird es dann spätestens ein wenig tricky:
Wenn wir jetzt zu /concerts navigieren, sucht Ember nach einer App.ConcertsRoute und sucht einen App.ConcertsController. Auch sucht EmberJS nun ein Template namens ‚concerts‘. Haben wir zufällig alles oben definiert. Siehe hierzu auch: http://guides.emberjs.com/v1.10.0/routing/defining-your-routes/

Wenn ich nun etwas komplexere Nutzereingaben umsetzen möchte so kommt das Konzept der Ember View ins Spiel. Mit diesen Views können nun primitive Events in fachliche Events umgewandelt werden (z.B. Drag-and-drop; s.u.). Views werden aber laut Dokumentation eher selten benötigt.
Einen Schritt weiter gehen dann Ember-Components: Mit diesen kann ich ähnlich wie mit AngularJS Direktiven mein HTML Vokabular erweitern, Views sind auf das bestehende Vokabular beschränkt. Details hierfür hier: Views bzw. Components .

Auch EmberJS Anwendungen sind testbar, allerdings scheint Testbarkeit nicht so prominent wie z.B. bei AngularJS vorangetrieben zu werden. EmberJS schlägt QUnit als Testframework vor, es können aber auch andere Testframeworks wie z.B. Jasmine verwendet werden.

Wie man bereits erahnen kann, sind in EmberJS Konventionen allgegenwärtig und ein wichtiges Kernkonzept .Für viele Use-Cases gibt es den “Ember-Way”. Allerdings muss der Entwickler diesen auch kennen oder er muss wissen wonach er suchen muss. Zumindest für einen Neuling scheint mir das eine kleine Hürde zu sein.

Das Ergebnis

Aber genug der Vorrede. Wie unterstützt mich EmberJS beim Bau der Konzert Applikation? Die Sourcen sind hier zu finden: https://github.com/holgergp/concertEmber

Ich bin ähnlich wie bei der Umsetzung mit AngularJS vorgegangen und habe einen Yeoman Generator  für das Aufsetzen der Anwendung verwendet.
Der Generator läuft leider nicht Out-of-the-box, es werden noch zusätzliche Dependencies wie Ruby und den CSS-Präprozessor Compass benötigt, die wahrscheinlich nicht bei jedem Entwickler vorinstalliert sind.
Das generierte Projekt ist für die Entwicklung meiner Meinung nach weniger gut geeignet, da bei jeder Codeänderung das Projekt neu kompiliert werden muss.
Der Generator liefert aber nichtsdestotrotz eine ordentliche Basis. Die Kritikpunkte sind natürlich auch kein Problem von EmberJS.
Mittlerweile ist dieser Generator deprecated worden, und die Autoren schlagen vor ember-cli zu verwenden. Diesen habe ich noch nicht ausprobiert.

Aber zur eigentlichen Struktur der Anwendung.

Ich verwende einen Controller für das Verwalten der Konzerte: https://github.com/holgergp/concertEmber/blob/master/app/scripts/controllers/concertController.js

Concertember.ConcertController = Ember.ObjectController.extend({
 
 newConcert: {},
 
 actions: {},
 
}),

Ein wenig Gehirnschmalz musste ich in die Entscheidung stecken, ob ich einen ObjectController (erwartet ein Objekt als Modell) oder einen ArrayController (erwartet ein flaches Array als Datenstruktur) nehme. Da ich allerdings die Konzerte in einer Liste von Listen verwalte, habe ich mich schlussendlich für einen ObjectController entschieden.

Im Modell habe ich mich dafür entschieden Ember-Data zu verwenden. Es schien mir der “Ember-Way” zu sein: https://github.com/holgergp/concertEmber/blob/master/app/scripts/models/concertModel.js
Ember-Data fühlt sich viel restriktiver als ein simples JSON Modell an und ist wahrscheinlich für meinen Fall zu überdimensioniert. Allerdings ließen sich so auch schön Testdaten über FIXTURES anlegen und der Schritt zum Anbinden eines REST Service wäre nicht mehr groß.

In der Route habe ich dann das Modell und den Controller verbunden: https://github.com/holgergp/concertEmber/blob/master/app/scripts/routes/application_route.js

Ein größeres Problem hatte ich überraschenderweise beim Einfärben der Konzerte in Abhängigkeit der Fälligkeit.
Zur Erinnerung: Ich möchte gerne, dass das System mir auf einen Blick anzeigt, wie weit das Konzert in der Zukunft liegt, oder ob ich Dussel das Konzert schon wieder verpasst habe. Mit AngularJS habe ich einfach im Template über alle Konzerte iteriert (ng-repeat) und eine CSS-Klasse in Abhängigkeit des Datums gesetzt.
Ganz so einfach ist es mit EmberJS leider nicht.
Handlebars erlaubt es nicht (soweit ich es verstanden habe) Binding und parametrisiertem Methodenaufruf zu kombinieren mit z.B. einem weiteren Helper (wie z.B. in AngularJS https://github.com/holgergp/concertAngular/blob/master/app/views/main.html#L16).
Eine eigene Komponente hierfür zu schreiben fand ich zu umständlich. Ich habe dann zunächst eine eigene CollectionView geschrieben. Das klappte zwar, ich fand das aber nicht sehr elegant.
Nach 3 oder 4 Mal Hinschauen bin ich auf die Lösung einer Computed Property im Modell gestoßen. Das gefällt mir schon besser:
https://github.com/holgergp/concertEmber/blob/master/app/scripts/models/concertModel.js#L11-L45

Die Umsetzung des Drag-and-drop fand ich dann vergleichsweise einfach, indem ich auf die HTML5 Funktionalität zurückgegriffen habe. In Anlehnung an http://jsfiddle.net/ud3323/5uX9H/  habe ich dies als EmberView umgesetzt: https://github.com/holgergp/concertEmber/blob/master/app/scripts/views/dragAndDrop.js

Handlebars als Templating Lösung bietet an sich alles was man sich wünscht.  Da Handlebars-Templates aber als Script Tags eingebunden werden, fand ich es an einigen Stellen vergleichsweise schwierig in den Browser-Dev-Tools nachzuvollziehen, ob sich Änderungen wie gewünscht ausgewirkt haben.

Die Tests liefen nach ein wenig Probieren auch, es war aber längerer Atem nötig verglichen mit AngularJS oder auch bei KnockoutJS.
https://github.com/holgergp/concertEmber/blob/master/test/spec/test.js

Wie man sieht, habe ich die Anwendung auch mit EmberJS umsetzen können, allerdings hat mich das vergleichsweise viel Aufwand gekostet und ich habe auch zu diesem Zeitpunkt noch nicht das Gefühl wirklich produktiv zu sein. Die Vielzahl an Konzepten hat mich zunächst eher überfordert (z.B. “Wann verwende ich eine View, wann eine Komponente?”)
Ich finde den Code und die Anwendungsstruktur auch weniger ansprechend als der entsprechende AngularJS Code. EmberJS setzt eher auf Vererbung als auf Komposition (siehe ObjectController). Vielleicht bin ich aber auch nur durch AngularJS versaut  ?

Aber nichtsdestotrotz bietet EmberJS durch seine Strukturen natürlich eine ordentlich Basis für Single Page Applications.

Fazit

Mein Fazit für EmberJS und die Konzertanwendung. Ich verwende hier wie gehabt die Skala + (gut), 0 (ok), – (nicht so gut):

  • Einarbeitung: –
    • Die Tutorials auf der EmberJS Seite sind gut, allerdings hatte ich das Gefühl, dass man im Gegensatz zu AngularJS das Zusammenspiel der Konzepte schon viel früher genau kennen muss.
  • Wartbarkeit: 0
    • Der Entwickler muss alle Konzepte kennen. Verwendet man aber den Ember-Way, so wird ein EmberJS Entwickler wahrscheinlich nicht von einer bestehenden Anwendung überrascht.
  • Lassen sich auch anspruchsvolle Anforderungen umsetzen: +
    • Nimmt man Drag-and-drop als Indikator, so ist man mit EmberJS auch für anspruchsvolle Anforderungen gut gerüstet.
  • Wie gut lässt sich die Anwendung testen? 0
    • Wie oben schon erwähnt, lassen sich EmberJS Anwendung gut testen, es ist aber mehr Arbeit nötig, um die Testinfrastruktur aufzubauen.
  • Wie groß ist die Community? 0
    • Kleinere Community, aber ich habe zu allen Problemstellungen Antworten gefunden.
    • Zum Vergleich, es gibt ca. 15.000 Einträge für EmberJS bei Stackoverflow, vs. 85.000 für AngularJS
  • Wie gut ist die Dokumentation? 0
    • Die Dokumentation ist recht umfangreich, aber recht dünn, wenn es etwas anspruchsvoller wird.

Jetzt teilen: