Kolumne

Die Angular-Abenteuer: Codegeneratoren mit Bordmitteln des Angular CLI

Manfred Steyer

©SuS Media

Im Inneren des Angular CLI schlummert seit einiger Zeit der Codegenerator Schematics, der zum Beispiel das Grundgerüst von Komponenten oder Services erzeugt. Diese Bibliothek lässt sich jedoch auch für eigene Zwecke nutzen, um wiederkehrende Programmieraufgaben zu automatisieren.

Programmieren ist ein äußerst kreativer Job. Zumindest hat man uns das gesagt, als wir am Anfang unserer Ausbildung standen. Bis zu einem bestimmten Punkt stimmt das auch. Leider gibt es aber auch monotone Arbeiten, die einem die Freude an der Arbeit verderben: x-mal ein Grundgerüst für eine Komponente schreiben, x-mal eine Bibliothek einbinden und konfigurieren, xmal ein Formular zum Verwalten von Stammdaten bereitstellen. Das sind nur einige Beispiele.

Um solche depressiven Episoden zu vermeiden, bietet es sich an, so viel wie möglich in wiederverwendbare Bibliotheken auszulagern. Was dann noch übrig bleibt, lässt sich mit Codegeneratoren herbeizaubern. Spätestens seit Ruby ist hier häufig auch von Scaffolding, frei übersetzt Gerüstbau, die Rede, weil der Generator häufig nur ein Grundgerüst erzeugt. Soweit ist das nichts Neues. Neu ist aber, dass das Angular CLI genau für diese Aufgabe eine Lösung bietet: Es nennt sich Schematics und kommt beim Generieren neuer Projekte, aber auch beim Generieren von Komponenten, Services oder Modulen zum Einsatz. Außerdem lassen sich damit auch benutzerdefinierte Codestrecken herbeizaubern. Alles, was es dazu braucht, ist ein wenig Code und – wenn es die Sache einfacher macht – ein Template. Die so entstehenden Codegeneratoren werden übrigens selbst auch Schematics genannt.

Abb. 1: Generiertes CRUD-Formular

Abb. 1: Generiertes CRUD-Formular

Ich starte mit einem Beispiel, das Angular-basierte CRUD-Formulare samt Services (Abb. 1) generiert. Der Quellcode, der hier aus Platzgründen nur auszugsweise abgedruckt wird, findet sich auf GitHub. Wer das fertige Beispiel ausprobieren möchte, findet es daneben auch als npm-Paket.

Schematics ist derzeit noch ein Angular-Labs-Projekt. Das bedeutet, dass es als experimentell eingestuft ist und sich das öffentliche API jederzeit ändern kann. Die Tatsache, dass sowohl das CLI als auch der Codegenerator Nx aus der Feder von Core-Contributern darauf basieren, schafft hier jedoch ein wenig Mut, sich die Sache bereits heute schon anzusehen.

Grundgerüst für Codegenerator generieren

Auch das Grundgerüst für einen Codegenerator bereitzustellen, ist eine monotone Aufgabe. Es bietet sich an, dafür einen weiteren Codegenerator einzusetzen. Hierfür sind zunächst ein paar npm-Pakete global zu installieren:

npm i -g @angular-devkit/schematics
npm i -g @angular-devkit/schematics-cli
npm i -g @schematics/schematics

Danach können wir das Grundgerüst mit dem Befehl schematics generieren:

schematics @schematics/schematics:schematic --name crud

Bei @schematics/schematics handelt es sich um den Namen eines npm-Packages und beim darauffolgenden schematic um den Namen des gewünschten Codegenerators in diesem Package. Das auf diesem Weg erhaltene Grundgerüst ist ein eigenes npm-Paket (Abb. 2). Außerdem findet man hier schon drei Ordner mit beispielhaften Codegeneratoren, die sich in diesem Kontext, wie oben erwähnt, auch Schematics nennen.

Abb. 2: Grundgerüst

Abb. 2: Grundgerüst

Das Hauptverzeichnis enthält eine tsconfig.json, um TypeScript zu konfigurieren, und eine package.json, die die nötigen Bibliotheken referenziert. Diese lassen sich mit npm install herunterladen. Außerdem enthält die package.json einen Eintrag schematics, der auf die Datei collection.json im Ordner src verweist. Der Name Collection bezeichnet hier eine Sammlung von Schematics, die jeweils mit einem Namen und einem Verweis auf eine JavaScript-Datei beschrieben werden. Dabei handelt es sich hier um die index.js im Ordner des jeweiligen Schematics. Darin wird als Standardexport eine sogenannte Factory erwartet. Ihre Aufgabe ist es, eine Regel in Form eines Rule-Objekts zu erzeugen, die den gewünschten Code generiert.

Daneben verweist die collection.json pro Schematic auf ein optionales JSON-Schema, das die unterstützten Kommandozeilenparameter beschreibt. Im betrachteten Fall ist das die Datei schema.json. Der Unterordner files beinhaltet in diesem Beispiel Templates zum Generieren von Dateien für den jeweiligen Schematic. Es bietet sich an, die generierten Dateien ein wenig zu betrachten, zumal sie einige zentrale Aspekte veranschaulichen und daneben auch sehr viele Kommentare mit nützlichen Hinweisen und Erklärungen beinhalten.

Factory für CRUD-Formulare

Nachdem wir uns mit der Grundstruktur vertraut gemacht haben, können wir eine erste eigene Factory schreiben. Einen Auszug der Factory aus dem hier genutzten Beispiel zum Generieren von CRUD-Formularen findet sich in Listing 1.

[...]
import { CrudOptions } from './schema';
import { CrudModel } from './model'; 
import * as crudModelUtils from '../utils/crud-model-utils';

export default function (options: CrudOptions): Rule {

  return (host: Tree, context: SchematicContext) => {
    [...]
  }
}

Zur Vereinfachung werden hier nur ausgewählte Importe abgedruckt: CrudOptions ist eine Klasse, deren Eigenschaften die unterstützen Kommandozeilenparameter beschreibt. Sie korreliert mit der vorhin erwähnten JSON-Schema-Datei und könnte sogar daraus generiert werden. Damit die Anzahl der unterstützten Parameter nicht ausufert, habe ich mich entschieden, über den Parameter model auf eine JSON5-Datei zu verweisen, die die nötigen Eckdaten wie Namen der Felder, Filtermöglichkeiten oder den URL des dahinterstehenden Web-API beschreibt. Die ebenfalls importierte Klasse CrudModel beschreibt dessen Aufbau. Der dritte hier gezeigte Import, crudModelUtils, beinhaltet Hilfsfunktionen zum Formatieren von Ausgaben. Diese kommen im Template zum Einsatz.

Die Factory selbst ist lediglich eine Funktion, die ein Rule-Objekt erzeugt. Wie die return-Anweisung zeigt, handelt es sich bei diesem Objekt ebenfalls um eine Funktion, die einen Tree mit dem Namen host und ein Kontextobjekt übergeben bekommt. Der Tree beschreibt einen Auszug aus dem Dateisystem. Änderungen daran werden jedoch ähnlich wie bei Datenbanktransaktionen nicht sofort zurückgeschrieben, sondern zunächst nur protokolliert. Erst wenn alle gewünschten Dateien in diesem virtuellen Dateibaum erfolgreich erzeugt oder modifiziert werden konnten, schreibt Schematics sie auf die Platte. Listing 2 zeigt einen Auszug aus dieser Funktion. Die ersten Zeilen lesen die erwähnte Modelldatei, die das CRUD-Formular beschreibt.

[...]
const modelFile = `/${options.sourceDir}/${options.path}/${options.model}`;
const modelBuffer = host.read(modelFile);
if (modelBuffer === null) throw new SchematicsException(`[...]`);
const modelJson = modelBuffer.toString('utf-8');
const model = JSON5.parse(modelJson) as CrudModel;

const templateSource = apply(url('./files'), [
  template({
    ...stringUtils,
    ...options,
    ...crudModelUtils as any,
    model
  }),
  move(options.sourceDir)
]);

const rule = chain([
  branchAndMerge(chain([
    mergeWith(templateSource),
    addImportToParentModule(options)
  ])),
]);

return rule(host, context);

Der Pfad der Modelldatei leitet sich aus den übergebenen Parametern ab: sourceDir und path übergibt das CLI standardmäßig, und model ist ein zusätzlicher Parameter, der durch das Schema beschrieben wird.

Die Methode apply wendet alle Templates im Ordner files auf die nachfolgend angeführten Regeln an. Die von template erzeugte Regel führt alle Templates aus. Dabei stellt sie den Templates die übergebenen Werte zur Verfügung. Diese beinhalten die Kommandozeilenparameter, das geladene Modell, aber auch Hilfsfunktionen, um Ausgaben zu formatieren. Die Nutzung des Spread-Operators (drei Punkte) bewirkt hier, dass TypeScript nicht die Objekte selbst, sondern deren Eigenschaften übergibt.

Die Regel move verschiebt alle Dateien, die aus den Templates hervorgehen, in den Source-Ordner der Anwendung. Das Ergebnis von apply ist ein Source-Objekt, das ebenfalls einen virtuellen Dateibaum repräsentiert.

Danach kreiert das Beispiel eine Regel, die sich aus mehreren anderen Regeln zusammensetzt. Die Methode chain erzeugt eine Regel, die die übergebenen Regeln der Reihe nach anstößt, und branchAndMerge erzeugt einen Branch des Projekts im virtuellen Dateisystem. Änderungen daran schreibt sie am Ende, wenn alles erfolgreich war, zurück. Die Regel mergeWith übernimmt den virtuellen Dateibaum aus templateSource. Interessant ist auch die Regel addImportToParentModule: Sie sucht nach dem aktuellen Angular-Modul und trägt dort die generierten Artefakte ein. Um dies zielgerichtet zu bewerkstelligen, kommt das TypeScript-Compiler-API zum Einsatz. Am Ende führt das Beispiel die Regel aus.

Templates

Abb. 3: Templates

Abb. 3: Templates

Um Code zu generieren, kommen hier Templates mit Platzhalter zum Einsatz. Aber nicht nur die Templates nutzen Platzhalter, sondern auch ihre Dateinamen. Diese sind links und rechts mit jeweils zwei Unterstrichen zu flankieren (Abb. 3).

Den Namen __path__ ersetzt Schematics somit durch die Variable path, die an die Regel templates zu übergeben ist. Im hier betrachteten Beispiel kommt sie aus dem Objekt options (Listing 2). Um diese Variablen zu formatieren, lassen sich auch Funktionen aufrufen: __ name@dasherize__ übergibt beispielsweise die Variable name an die Funktion dasherize, die diese mittels Kebab Case formatiert. Aus HotelBuchung wird also hotel-buchung. Diese Funktion ist an die Regel template zu übergeben. Sie befindet sich hier im Objekt stringUtils (Listing 2).

Die Inhalte der Templates erinnern an PHP oder JSP. Hier finden sich Texte, die 1:1 in die generierten Dateien geschrieben werden. Zusätzlich findet man zwischen den Begrenzungszeichen <% und %> Anweisungen, die zur Codegenerierung ausgeführt werden. Um Werte auszugeben, kommt hingegen <%=variable%> zum Einsatz:

export class <%= classify(name) %>Filter {
  <% for (let field of getFilterFields(model)) { %>
    <%=field.name%>: string = '';
  <%  } %>
}

Die einzelnen hier genutzten Variablen und Funktionen sind auch hier an die Regel template zu übergeben (Listing 2).

Testen

Die einfachste Möglichkeit zum Ausprobieren eines Schematics besteht darin, die Collection in den Ordner node_modules eines Testprojekts zu kopieren. Ich habe es zum Beispiel nach node_modules/angular-crud kopiert. Später, wenn die Collection über eine hausinterne oder öffentliche npm Registry installiert wird, landet die Collection auch dort. Wichtig ist, dass die node_modules der Collection nicht mitkopiert werden dürfen. Deswegen gibt es bei mir keinen Ordner node_modules/ angular-crud/node_modules.

Danach lässt sich der Schematic über das Angular CLI aufrufen (Abb. 4). Tabelle 1 beschreibt die einzelnen Parameter etwas genauer.

Abb. 4: Schematic über Angular CLI ausführen

Abb. 4: Schematic über Angular CLI ausführen

 

Parameter Beschreibung
g Kürzel für generate; stößt einen Schematic an
crud-module Name des Schematics
hotel Name des zu generierenden Elements; wird als Parameter name übergeben
–model model.json Parameter mit Name der Model-Datei, die das gewünschte CRUD-Formular näher beschreibt
–collection angular-crud npm-Paket mit Collection, die den gewünschten Schematic enthält

Tabelle 1: Aufrufen des Schematics näher betrachtet

Fazit

Mit Schematics findet sich eine durchdachte Lösung zum Generieren von Quellcode im Unterbau des Angular CLI. Die Vorgehensweise erinnert an serverseitige MVC-Frameworks: Logik und Ausgabe sind voneinander getrennt und kommunizieren Datenobjekte. Da Schematics seit einiger Zeit erfolgreich für das Scaffolding von ganzen Projekten, aber auch Komponenten oder Services für Angular zum Einsatz kommen, kann man sie durchaus auch als praxiserprobt bezeichnen, auch wenn sich ihr öffentliches API noch ändern kann.

Geschrieben von
Manfred Steyer
Manfred Steyer
Manfred Steyer ist selbstständiger Trainer und Berater mit Fokus auf Angular 2. In seinem aktuellen Buch "AngularJS: Moderne Webanwendungen und Single Page Applications mit JavaScript" behandelt er die vielen Seiten des populären JavaScript-Frameworks aus der Feder von Google. Web: www.softwarearchitekt.at
Kommentare

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu: