Suche
Immer hereinspaziert: Dynamische Importe in Angular

Angular Pro-Tipps: #5 – Dynamic Imports

Aaron Czichon

Im ersten Teil der Angular Pro-Tipps von Aaron Czichon wurden die mit Angular 2 eingeführten Pipes näher beleuchtet. Für den zweiten Teil widmete sich unser Autor ausführlich dem HTTP-Client. Der dritte Teil der Angular Pro-Tipps stand dann ganz im Zeichen der Dependency Injection in Angular, Teil 4 widmete unser Experte den Neuerungen in Angular 5. Im aktuellen fünften Teil der Serie geht es um dynamische Importe im beliebten Framework.

Bisher gelernt

Angular Pro-Tipps von Aaron Czichon

Bisher haben Sie gelernt, wie Sie mit den in Angular 2 eingeführten Pipes umgehen. Diese können genutzt werden, um Daten und Objekte vor der Anzeige in der View und/oder vor dem Rendering zu transformieren und entsprechend zu formatieren. Wenn Sie etwa ein Datum in einem bestimmten Format anzeigen wollen, können Sie hierfür eine Pipe verwenden und müssen nicht das eigentliche Datenobjekt bearbeiten.

Im zweiten Teil wurde der HTTP-Client etwas genauer dargestellt. Sie haben gelernt, wie Sie den neuen Client nutzen können, um Daten von einer Schnittstelle abzurufen und wie das Grundprinzip von Observables funktioniert.

Nachdem es im dritten Teil der Kolumne um die Dependency Injection in Angular ging, war der vierte Teil eine Zusammenfassung der Neuerungen in Angular 5. In Teil fünf wird es nun wieder technischer: Es geht um dynamische Importe in Angular.

Dynamic Imports in Angular

Sie kennen vermutlich das Problem. Sie haben eine größere Angular-Anwendung mit vielen unterschiedlichen Modulen. Ihre Anwendung ist ideal mit ngModules und Webpack auf Lazy Loading ausgelegt und Sie nutzen Tree-Shaking, so weit es Ihnen möglich ist. Dennoch haben Sie einige Module in Ihrer Anwendung, welche unabhängig funktionieren und nur äußerst selten benötigt werden.

Nehmen wir hierzu ein Beispiel. Sie haben eine Administrationsoberfläche mit Angular entwickelt. Diese Administrationsoberfläche dient zur Gestaltung von Inhalten. Nun soll diese Oberfläche um ein Vorschaumodul für die Inhalte ergänzt werden. Da das Vorschaumodul aber aus verschiedenen Möglichkeiten der Vorschau besteht (z.B. Web, Mobile und Print), möchten Sie nicht immer alle Module mit in Ihrer Anwendung referenzieren.

Hier kommen die sogenannten Dynamic Imports zum Tragen. In normalen Fällen importieren Sie Klassen, Komponenten und Module mit import { SampleModule } from 'sample.module'. So weit so gut. Wollen Sie nun jedoch zur Laufzeit evtl. eine JavaScript- bzw. TypeScript-Datei importieren, welche beispielsweise Funktionen enthält, die Sie nicht immer brauchen, sieht das Ganze anders aus.

Hierzu gibt es, je nach aktuell genutzter TypeScript-Version, zwei Möglichkeiten.

Nutzen Sie noch eine Version, die älter als TypeScript 2.4 ist, müssen Sie die sogenannte System.import-Syntax benutzen. Ab Version 2.4 können Sie die Standard-Import-Möglichkeit von ECMAScript nutzen, den eigentlichen dynamischen Imports. Hierbei ähnelt die Syntax auch dem „normalen“ Import stark. Aber zunächst ein Schritt nach dem anderen.

Starten wir mit der aktuellen, der Import().then()-Syntax. Stellen Sie sich vor, Sie haben eine Collection an Funktionen, die Sie erst benötigen, wenn Sie in Ihrer Anwendung einen entsprechenden Button klicken. In unserem Beispiel enthält diese Datei folgende Implementierungen:

// generator.ts
export function generatePdf(data: any) {
	// Implementation
	console.log(„Generate PDF here“);
}
export function generateHtml(data: any) {
	// Implementation
	console.log(„Generate HTML here“);
}
export function generateMarkdown(data: any) {
	// Implementation
	console.log(„Generate HTML here“);
}

Das sind nun Ihre Implementierungen in der Datei generator.ts.

Nun benötigen Sie diese Funktionen nicht immer oder möchten diese, während die Anwendung ausgeliefert wird, verändern. Diese Funktionen enthalten keinen Angular-Spezifischen Code und wird möglicherweise von anderen Frameworks ebenso in Ihren Projekten genutzt. Die Funktionen sollen nun nur dann aufgerufen werden, wenn Sie in Ihrer Anwendung (einer Komponente) auf einen Button klicken, der eine dieser Generator-Funktionen ausführt.

Legen Sie sich dazu eine neue Komponente in Ihrem Angular-Projekt an (bspw. mit ng generate component generator).

Ihre neue Komponente sollte in etwa so aussehen.

// generator.component.ts

Import { Component } from ‚@angular/core‘;

@Component({
	selector: ‚generator‘,
	templateUrl: ‚./generator.component.html‘,
	styleUrl: ‚./generator.component.css‘
})
export class GeneratorComponent {
	constructor() { }
}

Implementieren Sie in diese neue Komponente nun zunächst die drei leeren Methoden für die drei Buttons.

// generator.component.ts

...
export class GeneratorComponent {
	constructor() { }

	generatePdfDocument() {
		// Implementation here
	}

	generateHtmlDocument() {
		// Implementation here
	}

	generateMarkdownDocument() {
		// Implementation here
	}
}

Ihr Template müssen Sie entsprechend anpassen, sodass dieses auch die entsprechenden Click-Events auslöst.

// generator.component.html
<div>
	<button (click)=„generatePdfDocument()“>Generate PDF</button>
	<button (click)=„generateHtmlDocument()“>Generate HTML</button>
	<button (click)=„generateMarkdownDocument()“>Generate Markdown</button>
</div>

Bis hierhin sollte Ihnen alles bekannt sein. Es handelt sich um eine normale Angular-Komponente mit drei Methoden, die von einem Button-Klick getriggert werden.

Jetzt folgt der neue Teil. Nutzen Sie nun die Import().then()-Syntax, um die generator.ts-Datei zu Importen und die darin enthaltenen Methoden aufzurufen.

// generator.component.ts

...
export class GeneratorComponent() {
	...

	generatePdfDocument() {
		const data = { /*  Object of sample data */};
		import(‚./generator‘)
			.then(module => {
				module.generatePdf(data);
			});
	}

	generateHtmlDocument() {
		const data = { /*  Object of sample data */};
		import(‚./generator‘)
			.then(module => {
				module.generateHtml(data);
			});
	}

	generateMarkdownDocument() {
		const data = { /*  Object of sample data */};
		import(‚./generator‘)
			.then(module => {
				module.generateMarkdown(data);
			});
	}
}

Nun wird die generator.ts-Datei nur dann von Ihrem Server geladen, sofern eine der drei Methoden durch den Klick auf einen Button aufgerufen wird. Das spart Bandbreite und vor allem unnötigen Code auf dem Client, der vielleicht gar nicht benötigt wird.

Um die Anwendung nun zu testen, müssen Sie jedoch noch eine kleine Konfiguration vornehmen. Dem TypeScript-Compiler ist zu diesem Zeitpunkt noch nicht bekannt, dass Sie sogenannte ESNext-Module nutzen möchten. Dessen Standardkonfiguration läuft aktuell noch mit ES2016. Passen Sie hierzu entsprechend in Ihrem Angular-Projekt unter src/tsconfig.app.json die Konfiguration so an, dass TypeScript die ESNext-Module nutzt:

// tsconfig.app.json
{
...
	„compilerOptions“: {
		...
		„module“: „esnext“
		...
	}
...
}

Starten Sie nun den lokalen Webserver und testen Sie die neue Funktionalität im Browser.

Hinweis: Wenn Sie die Konfiguration für den TypeScript-Compiler korrekt angegeben haben, brauchen Sie auch keine weiteren Konfigurationsanpassungen für Webpack vornehmen. Webpack erkennt dynamische Imports automatisch und stellt diese als eigene Chunks bereit.

Benutzen Sie nun noch eine TypeScript-Version, die älter als 2.4 ist, müssen Sie auf die alte System.import-Syntax setzen.

Um ein Modul stattdessen mit der import.then-Syntax zu importieren, müssen Sie Ihre Funktionen nun etwas anpassen.

// generator.component.ts

...
export class GeneratorComponent() {
	...

	generatePdfDocument() {
		const data = { /*  Object of sample data */};
		System.import(‚./generator‘)
			.then(module => {
				module.generatePdf(data);
			});
	}

	generateHtmlDocument() {
		const data = { /*  Object of sample data */};
		System.import(‚./generator‘)
			.then(module => {
				module.generateHtml(data);
			});
	}

	generateMarkdownDocument() {
		const data = { /*  Object of sample data */};
		System.import(‚./generator‘)
			.then(module => {
				module.generateMarkdown(data);
			});
	}
}

TypeScript sollte die Syntax entsprechend auflösen. Sollte dies nicht der Fall sein, deklarieren Sie einfach einen entsprechenden Typ für den Compiler:

declare var System: {
	import: Promise<any>
};

Für Webpack müssen Sie auch hier keine weitere Konfiguration vornehmen. Einzig müssen Sie darauf achten, dass Webpack im richtigen Kontext läuft und die Dateipfade, welche sie der System.import-Funktion mitgeben, korrekt auflösen kann.

Geschrieben von
Aaron Czichon
Aaron Czichon
Aaron Czichon hat im Alter von 14 Jahren mit Software-Entwicklung angefangen und sich PHP und HTML beigebracht. Seit seiner Ausbildung zum Fachinformatiker Anwendungsentwicklung arbeitet er hauptberuflich bei der cellent AG mit Hauptsitz in Stuttgart. Dort ist er als Software-Entwickler im Projektumfeld mit Javascript und .NET tätig. Nebenberuflich ist er als freier Entwickler mit seiner WebAtlas GbR unterwegs und macht seinen IT-Fachwirt um seine Kompetenzen in Unternehmensführung und BWL zu erweitern. Mit Ionic beschäftigt er sich seit dem Erscheinen der 3. Alpha-Version Ende November 2013. Ende 2014 gründete er die deutsche Ionic Community „Ionic Germany“. Er ist Mitglied der Ionic Alpha-Gruppe, welche die aktuellsten Neuerungen und Tools von Ionic zum Testen erhält. Nebenbei ist er auf Konferenzen als Speaker. Dort referiert er überwiegend über das Ionic Framework.
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.