Material Design und Angular.JS

Seit Ende 2014 ist nun auch Google auf den Flat Design Zug aufgesprungen. Die Design Sprache verfolgt für Android die gleichen Ziele wie damals METRO für Windows 8. METRO war viel progressiver und verfolgte einen adaptiven Layout Ansatz. Material Design (wie auch nun Microsoft) folgen dem Responsive Design Paradigma, das ein universelles UI nutzt,

Sozusagen aus einem Irrtum heraus, habe ich mir zuerst das optisch recht gut gelungene LumX angesehen. Nachdem ich mich ohne nennenswerte Doku durch den Prototypen geplagt habe, war die Begeisterung auch schon wieder verflogen.

Im zweiten Anlauf teste ich das Google Framework Angular Material. Alles noch recht frisch und wenige Blog Beispiele im Web.

Visual Studios Pakte Manager Nuget hat auch ein passendes Angebot in der Version 0.8.3.

image

Auf der Hersteller Website wird allerdings schon 0.9.3 angeboten. Da diese Art von Frameworks (immerhin erfinden die das UI Rad alle neu) vor Bugs in der Regel nur so strotzen, besser das Neueste. Dazu nehme ich (über meinen Schatten springend) Bower als Paketmanager. Den kann man sich per Nuget installieren und dann in der Kommandozeile nutzen.

image

Konkret muss das Paket per bower install angular-material aus der Visual Studio Kommandozeile installiert werden.

image

Im Web Projekt werden dann eine Reihe von Verzeichnissen in Bower_Components erzeugt.

image

Um es vorweg zu nehmen. Im Laufe meiner etwas verzweifelten Recherchen zum Thema Icons bin ich erst nach Stunden darauf gekommen, das die Icons extra installiert werden müssen.

bower install material-design-icons

Das sind dann auch 86.000 Dateien mit rund 90 MB Download für Bower. Visual Studio stürzt bei mir ab wenn ich den Folder dem Projekt hinzufügen möchte. Eigentlich bietet Material.js eine Reihe von Standard Icons. Allerdings werden die nicht angezeigt. Es stimmen wohl die Pfade nicht.  Über einen IconProvider lässt sich jedes verwendete Icon einzeln konfigurieren ala

   1:   app.config(function ($mdIconProvider) {
   2:              $mdIconProvider
   3:              .icon('search', 'bower_components/material-design-icons/action/svg/production/ic_search_24px.svg');
   4:          });

Das es bei mir trotzdem nicht geklappt hat habe ich diesen Weg nicht weiter verfolgt.

Material hat ein relativ flexibles Gridlayout. CSS Klassen kommen relativ sparsam zum Einsatz. Im Wesentlichen stehen für die üblichen Anwendungsfälle eigene UI Controls, ausgeführt als Angular Direktiven zur Verfügung. Diese beginnen mit dem Präfix md.

Mein Beispiel benötigt einen Popup Dialog für das Erfassen eines neuen Tabellen Eintrages. Ein modaler Dialog erfolgt mit md-dialog. Der Button ist ein md-Button. Label und Textbox wird per md-input Container zusammen gefasst.

   1:  <md-dialog aria-label="Neuer Name">
   2:      <md-dialog-content >
   3:          <form name="Form1">
   4:                  <md-input-container flex>
   5:                      <label>Vorname</label>
   6:                      <input ng-model="kind.name" >
   7:                  </md-input-container>
   8:             </form>
   9:      </md-dialog-content>
  10:      <div class="md-actions" layout="row">
  11:          <md-button ng-click="close()"> Abbruch </md-button>
  12:          <md-button ng-click="close(kind)" class="md-primary"> Save </md-button>
  13:      </div>
  14:  </md-dialog>

Der modale Popup Dialog enthält einen Schatten und graut den Hintergrund aus. Das besondere sind die Eingabefelder, die mit dem Text aus dem Label vorbelegt werden.

image

Setzt der Benutzer den Fokus in das HTML Input Feld wird die Beschriftung animiert nach oben geschoben.

image

Sieht wirklich sehr schick aus.

Das Konzept findet sich konsistent auch in der Toolbar wieder. In diese wird eine Beschriftung (vornamen für Kind) und ein Search Textbox eingefügt. Das Icon sollte eigentlich per “search”, wie im auskommentierter Zeile 9 definiert werden. Die funktionierende Alternative erfordert den vollen Pfad, den Bower so erzeugt hat.

   1:  <body ng-app="App" layout="column" ng-controller="myController" >
   2:      <md-toolbar>
   3:          <div class="md-toolbar-tools">
   4:              <h1 class="md-headline">Vornamen fürs Kind</h1>
   5:              <span flex></span>
   6:              <md-input-container>
   7:                  <md-icon md-svg-src="bower_components/material-design-icons/action/svg/
production/ic_search_48px.svg"
style="display:inline-block;"></md-icon>
   8:                  <input ng-model="search" class="" placeholder="enter search">
   9:                  <!--<ng-md-icon icon="search"></ng-md-icon>-->
  10:              </md-input-container>
  11:          </div>
  12:      </md-toolbar>

 

image

Als nächstes wird der Button (+) erzeugt, um weitere Vornamen hinzufügen zu können. Die graue Umrandung der Namensliste wird mit dem Layout Control Card erzeugt. Ich bin mir nicht ganz sicher ob das der passende Einsatzzweck ist. 

Die Liste erhält interaktive Klickbare Einträge durch hinzufügen einer md Checkbox. Die ganze Zeile ist clickbar und löst eine Animation aus.

animated

 

   1:      <div layout="row" flex>
   2:          <md-content layout="column" flex class="md-padding">
   3:              <md-button class="md-fab md-primary md-button md-fab-bottom-right"
   4:                         aria-label="Neu" ng-click="add($event)">
   5:                  <md-icon md-svg-src="bower_components/material-design-icons/content/
svg/production/ic_add_48px.svg"
></md-icon>
   6:              </md-button>
   7:              <md-card flex-gt-sm="90" flex-gt-md="80">
   8:                  <md-card-content>
   9:                      <h2>Namen </h2>
  10:                      <md-list>
  11:                          <md-list-item ng-repeat="name in namen | filter:search" layout="row">
  12:                              <md-checkbox></md-checkbox>
  13:                              <p>{{ name }}</p>
  14:                              <md-divider></md-divider>
  15:                          </md-list-item>
  16:                      </md-list>
  17:                  </md-card-content>
  18:              </md-card>
  19:          </md-content>
  20:      </div>

Im JavaScript Logik Teil muss ngMaterial der DI eingebunden werden. Rein zu Demo Zwecken, wird ein ein Toast Benachrichtigung eingebaut. Das Objekt mdToast muss ebenfalls injiziert werden. Nicht ganz uninteressant, der Zugriff aufs UI aus einem Controller ohne MVX Bindung!

   1:  var app = angular.module('App', ['ngMaterial', 'ngAnimate', 'ngAria']);
   2:      
   3:   app.controller('myController', ['$scope', '$mdToast', '$mdDialog', function ($scope, $mdToast, $mdDialog) {
   4:              $scope.namen = ["hannes", "franz", "susi"];
   5:              $scope.save = function () {
   6:                  $mdToast.show({
   7:                      position: "bottom right",
   8:                      template: "<md-toast>gespeichert!</md-toast>"
   9:                  });
  10:              };

Fast spannender ist die kommunikation über zwei Controller zwischen modalen Dialog und Hauptview. In der Add Methode (Zeile 11) wird Show aufgerufen. Dabei wird die Controller Funktion (addController) und das HTML Template als Parameter übergeben. Über ein Promise wird asynchron der .then Zweig (Zeile 17) aufgerufen, wenn der Popup Dialog geschlossen wurde.  Da im Context und lokalen Scopes des Dialogs die Methode hide (Zeile 7) mit dem Scope des Formulares aufgerufen wird, findet so die erfassten Daten den Weg in den übergeordneten Scope wieder. Dann kann der neue Vornamen an die Liste der Namen angefügt werden.

   1:   function addController($scope, $mdDialog) {
   2:                  $scope.close = function (answer) {
   3:                      if (answer === null) {
   4:                          $mdDialog.cancel()
   5:                      }
   6:                      else {
   7:                          $mdDialog.hide(answer);
   8:                      };
   9:                  };
  10:              };
  11:              $scope.add = function (ev) {
  12:                  $mdDialog.show({
  13:                      controller: addController,
  14:                      templateUrl: 'addtemplate.html',
  15:                      targetEvent: ev,
  16:                  })
  17:                  .then(function (kind) {
  18:                      //speichern;
  19:                      $scope.namen.push(kind.name);
  20:   
  21:                  }, function () {
  22:                      // fehler;
  23:                  });

Auch Angular Material steht am Anfang. Rein subjektiv hat es sich an der ein oder anderen Stelle manchmal sehr langsam angefühlt. Der Lösungsansatz per Direktiven Controls abzubilden ist relativ nahe an zb Web Forms Steuerelementen, nur eben rein am Client. Wobei schon der nächste Trend in Sicht ist, diese wieder am Server rendern zu lassen. Auffallend ist, das Microsoft alles tut, um diese Art von Web Entwicklung auch aus Visual Studio zu ermöglichen.

Kommentare sind geschlossen