Verschachtelte Ansichten mit AngularJS

the next big thing  –  0 Kommentare

Wer Webanwendungen auf Basis eines Frameworks wie beispielsweise AngularJS entwickelt, benötigt in der Regel Unterstützung für verschachtelte und zusammengesetzte Ansichten.

Zu diesem Zweck enthält AngularJS die ngView-Direktive, deren Nutzen allerdings begrenzt ist: Zwar erlaubt sie es dem Entwickler, eine Ansicht in eine andere zu integrieren, unterstützt dies jedoch nicht verschachtelt. Auch ist es nicht möglich, in einer Ansicht mehrere ngView-Direktiven gleichzeitig zu integrieren.

Einen Ausweg bietet die zusätzliche Komponente UI-Router, die im Rahmen des AngularUI-Projekts entwickelt wird.

Um diese Komponente verwenden zu können, muss man zunächst die entsprechende JavaScript-Datei herunterladen und in die Webanwendung einbetten - nach AngularJS, wohlgemerkt:

<script type="text/javascript" src="angular.js"></script>
<script type="text/javascript" src="angular-ui-states.js"></script>

Innerhalb der Webanwendung kann man den UI-Router dann mit Hilfe des Schlüssels ui.compat als Abhängigkeit laden:

var app = angular.module('app', [ 'ui.compat' ]);

Danach kann man die beiden neuen Dienste $urlRouterProvider und $stateProvider verwenden, um die einzelnen Zustände der Webanwendung zu konfigurieren.

In der einfachsten Variante konfiguriert man einen Zustand und weist diesem eine Adresse, eine Ansicht und gegebenenfalls einen Controller zu:

app.config([ '$stateProvider', function ($stateProvider) {
'use strict';
$stateProvider
.state('dashboard', {
url: '/',
templateUrl: '/dashboard/dashboardLayout.html',
controller: '/dashboard/dashboardController.js'
});
}]);

Benötigt man mehrere derartige Zustände, kann man die Funktion state verkettet aufrufen und auf die gleiche Art beliebige weitere Zustände definieren. Wichtig ist, dass man an Stelle der ngView-Direktive nun die uiView-Direktive verwendet, die der UI-Router zur Verfügung stellt.

Innerhalb einer Ansicht kann man auch mehrere uiView-Direktiven verwenden, muss diesen dann allerdings einen eindeutigen Bezeichner zuweisen:

<div ui-view="mailView"></div>
<div ui-view="calendarView"></div>

Zusätzlich muss man innerhalb der Konfiguration festlegen, welche Ansicht welche Webseite anzeigen soll. Dazu ersetzt man die Eigenschaften templateUrl und controller durch die Eigenschaft views und weist dieser ein Konfigurationsobjekt zu, das die einzelnen Zuordnungen vornimmt:

.state('dashboard', {
url: '/',
views: {
'': {
templateUrl: '/dashboard/dashboardLayout.html',
controller: '/dashboard/dashboardController.js'
},
'mailView@dashboard': {
templateUrl: '/mails/unansweredMailsView.html',
controller: '/mails/unansweredMailsController.js'
},
'calendarView@dashboard:' {
templateUrl: '/calendar/todaysTasksView.html',
controller: '/calendar/todaysTasksController.js'
}
}
});

Die namenlose Ansicht stellt dabei die Elternansicht dar, in der die beiden untergeordneten Ansichten dargestellt werden.

Von Zeit zu Zeit kann es gewünscht sein, für verschiedene, jedoch logisch zusammenhängende Ansichten ein gemeinsames Layout zu definieren. So kann es beispielsweise sinnvoll sein, die Struktur aller E-Mail-Ordner ordnerübergreifend anzuzeigen, aber jeden einzelnen Ordner direkt über eine individuelle Adresse auswählen zu können.

Dazu muss man zunächst den Zustand mails definieren, der das grundlegende Layout für die verschiedenen Ordner enthält. Dies erfolgt auf dem bereits bekannten Weg, weist dem Layout aber keine eigene Adresse zu, da es nicht sinnvoll ist, die Ansicht ohne konkreten Ordner auszuwählen:

.state('dashboard', {
// ...
})
.state('mails', {
templateUrl: '/mails/mailsLayout.html',
controller: '/mails/mailsController.js'
});

Um darüber hinaus auch zu verhindern, dass dieser Zustand per Code aufgerufen werden kann, kann man zusätzlich die Eigenschaft abstract definieren und ihr den Wert true zuweisen:

.state('mails', {
abstract: true,
templateUrl: '/mails/mailsLayout.html',
controller: '/mails/mailsController.js'
});

Danach kann man die untergeordneten Zustände definieren, denen man dann wiederum eine Adresse zuweist. Damit AngularJS die Hierarchie zwischen den Zuständen erkennt, muss man darüber hinaus die Bezeichnungen der untergeordneten Zustände mit dem Namen des übergeordneten als Präfix versehen:

.state('mails', {
// ...
})
.state('mails.inbox', {
url: '/mails/inbox',
templateUrl: '/mails/inboxView.html',
controller: '/mails/inboxController.js'
});

Ruft man anschließend im Webbrowser die Adresse /mails/inbox auf, lädt AngularJS zunächst die übergeordnete Ansicht und bettet die untergeordnete Ansicht danach an der Position der entsprechenden uiView-Direktive ein.

Zu guter Letzt ist nun noch wünschenswert, dass ein Aufruf der bislang nicht zugeordneten Adresse /mails auf die Adresse /mails/inbox weitergeleitet wird. Dies kann man nicht über den $stateProvider definieren, stattdessen muss man hierfür auf den $urlRouterProvider zurückgreifen:

app.config([ '$stateProvider', '$urlRouterProvider',
function ($stateProvider, $urlRouterProvider)
{
'use strict';
$stateProvider
// ...

$urlRouterProvider
.when('/mails', '/mails/inbox');
}]);

Darüber hinaus kann man an dieser Stelle auch eine Standardadresse definieren, auf die AngularJS dann weiterleitet, wenn der Anwender eine nicht zugewiesene Adresse aufruft:

$urlRouterProvider
.when('/mails', '/mails/inbox')
.otherwise('/');

tl;dr: AngularJS unterstützt von Haus aus weder zusammengesetzte noch verschachtelte Ansichten, das kann man aber mit Hilfe des Projekts UI-Router nachrüsten. Es stellt die beiden neuen Dienste $stateProvider und $urlRouterProvider zur Verfügung und ermöglicht eine flexible Konfiguration von Zuständen und Adressen.