Anzeige
Anzeige
How-To
Artikel merken

Datenmodellierung in Angular: Die Architektur einer Angular-Applikation richtig gestalten

Das JavaScript-Framework Angular eignet sich besonders, um Single-Page-Applikationen umzusetzen. Tipps und Lösungen für funktionierende Konventionen zur Datenmodellierung in Teams.

7 Min. Lesezeit
Anzeige
Anzeige

Die schematische Darstellung des Informationsflusses in einer Angular-App mit ihren verschiedenen Komponenten und Data-Services (Grafik: Google)

Wer Angular effizient nutzen will, sollte sich mit den Datenquellen und -flüssen seiner Applikation sowie deren Synchronisierung beschäftigen. Dazu sollten Angular-Entwickler der
Aufbau und die Komponenten ihrer Applikation bekannt sein. Wenn saubere Konventionen zur Datenmodellierung nämlich fehlen, kommt es spätestens dann zu Problemen, wenn ein Team eine App programmiert, bei der die gleichen Informationen an unterschiedlichen Stellen auftauchen oder modelliert werden sollen.

Die Angular-Komponenten

Anzeige
Anzeige

Die wichtigsten Bausteine einer Angular-Applikation sind die Komponenten. Eine Angular-App gleicht einem ganzen Baum von Komponenten. Die so genannte Root-Komponente verzweigt sich dabei in Sub-Komponenten zu einer baumförmigen Programmstruktur. Die Flexibilität dieses Konstrukts können Entwickler zusätzlich erhöhen, wenn sie zur Laufzeit Komponenten ein- oder aushängen. Darüber hinaus kann der Angular-Router den Status einer Applikation in der URL abbilden und basierend darauf Komponentenbäume an bestimmten Stellen der Applikation einhängen.

Doch zurück zur Komponente: Diese besteht aus einem Template, einer Komponentenklasse und optional einem Stylesheet. Das Template ist für die Strukturierung der Benutzeroberfläche und die Präsentation der Informationen zuständig. Die Komponentenklasse stellt die View-Logik zur Verfügung, die zum Betrieb der Oberfläche erforderlich ist. Die eigentliche Business-Logik einer Applikation lagern Entwickler in separaten Services aus. Das sind spezielle Klassen, die sich mittels Dependency Injection in Komponenten einbinden lassen und eine Interaktion über definierte Schnittstellen bieten.

Anzeige
Anzeige

Eine einfache Angular-Komponente:

@Component({
 selector: ‘app’,
 templateUrl: ‘app.component.html’
})
class AppComponent {
 public title = ‘Hello World’;
}

Eine wichtige Anforderung an Komponenten ist, dass sie von anderen Komponenten so unabhängig wie möglich sein sollten. Das reduziert die Kopplung zwischen den Komponenten, erhöht also ihre Wiederverwendbarkeit und erleichtert insgesamt das Testen.

Anzeige
Anzeige

Informationsflüsse

Ein sehr einfaches Beispiel zeigt den Informationsfluss einer Applikation ganz gut: Eine Auflistung aller Benutzer-Accounts. Die erste Komponente, die zur Lösung dieser Aufgabe notwendig ist, ist die Liste selbst. Innerhalb der Komponentenklasse holen Angular-Entwickler durch die Dependency Injection eine Instanz des http-Services und starten eine GET-Anfrage an den Server, mit der die entsprechenden Daten eingeholt werden.

Für die Darstellung der Datensätze sorgt eine Kindkomponente, die die App in einer Schleife mehrfach wiedergibt. Doch: Wie kommen die Informationen in die Kindkomponenten? An dieser Stelle kommt das Property-Binding von Angular ins Spiel. Im Template der Elternkomponente weisen Entwickler dazu einfach der Eigenschaft „account“ der Kindkomponente dem Objekt aus der Elternkomponente zu.

Anzeige
Anzeige

Implementierung der Elternkomponente:

@Component({
 selector: 'account-list',
 template: `
 <ul>
 <account-item let *ngFor="let account of accounts" [account]="account">
 </ul>
 `,
 styleUrls: ['./list.component.css']
})
export class ListComponent implements OnInit {
 private accounts: Account[];
 constructor(private http: Http) {}
 ngOnInit() {
 this.http.get('/accounts')
 .map(res => res.json())
 .subscribe((accounts: Account[]) => {
 this.accounts = accounts;
 });
 }
}

Um in der Kindkomponente einen Zugriff auf das Objekt hinter der account-Eigenschaft zu erhalten, muss man in der Komponentenklasse angeben, dass es sich hierbei um eine Input-Eigenschaft handelt. Dies geschieht über den @Input-Decorator. Dadurch können Entwickler diese Eigenschaft als gewöhnlichen Teil ihrer Komponente behandeln. Allerdings gelangt das Objekt nicht als Kopie, sondern als Referenz in die Kindkomponente. Das bedeutet, dass sich jede Änderung in der Kindkomponente auch auf die Elternkomponente auswirkt.

Das JavaScript-Framework Angular wird von Google bereitgestellt. Es eignet sich besonders für Single-Page-Applikationen. (Screenshot: Angular)

Das JavaScript-Framework Angular wird von Google bereitgestellt. Es eignet sich besonders für Single-Page-Applikationen.

Diese impliziten Änderungen der Informationen in einer Applikation ist nicht erstrebenswert, da dies die Elternkomponente nicht benachrichtigt. Besser ist es, für den Informationsfluss von der Kindkomponente zur Elternkomponente ein eventbasiertes Output-Binding zu verwenden. So erhält die Elternkomponente bei einer Änderung eine Benachrichtigung und Entwickler können einen entsprechenden Eventlistener erstellen.

Für die Implementierung stellt Angular alles nötige bereit. Die Anwender des Frameworks erzeugen in der Komponentenklasse der Kindkomponente zunächst eine Eigenschaft des Typs „EventEmitter“. Die Objekte dieses Typs stellen die emit-Methode bereit, mit der die Nachricht über Veränderungen an die Elternkomponente verschickt wird. Die Eigenschaft markieren Entwickler mit dem @Output-Decorator. Dadurch können sie im Template der Elternkomponente über das Eventbinding von Angular einen Eventhandler an das Event der Kindkomponente anbinden und eine entsprechende Routine auslösen.

Anzeige
Anzeige

Implementierung der Kindkomponente:

@Component({
 selector: 'account-item',
 template: '<li>{{account.name}}</li>'
})
export class ItemComponent {
 @Input() account: Account;
 @Output() accountChanged = new EventEmitter();
}

Data Services

Mit dem bisher vorgestellten Property- und Eventbinding lässt sich der Informationsfluss durch einen Komponentenbaum sehr gut abbilden. Häufig werden die Informationen in einer Applikation jedoch nicht in direkt zusammenhängenden Komponenten dargestellt oder – noch schwieriger – die Informationen sollen an mehreren Stellen modifiziert werden können. Hier können die Informationen entweder über eine gemeinsame Elternkomponente fließen oder es kommen Data Services zum Einsatz. Je nach Umfang der Applikation kann die gemeinsame Elternkomponente relativ weit entfernt liegen. Das bedeutet, dass Entwickler die Informationen durch eine große Anzahl von Komponenten leiten müssen. Dieser Ansatz ist also in vielen Fällen keine Option.

Ein Data Service hat eine event-basierte Architektur, sodass Entwickler Komponenten an verschiedenen Stellen einer Applikation bei diesem Service registrieren und über Aktualisierungen informiert werden können. Dabei müssen sie dieses Event-System nicht von Grund auf erfinden, sondern können auf die RxJS-Bibliothek zurückgreifen und sich so einiges an Arbeit sparen.

Ihre Implementierung hat den Vorteil, dass man Informationen nur einmal in einer Applikation vorhalten muss und sich an dieser zentralen Stelle auch gleich um die Server-Kommunikation kümmern kann. Außerdem bleiben die Informationen immer konsistent, da es eine „Single Source of Truth“ gibt.

Anzeige
Anzeige

Der Data Service ist zunächst ein ganz gewöhnlicher Angular Service, den Entwickler in ihrem Applikationsmodul registrieren müssen, um ihn über die Dependency Injection jedem Modul zur Verfügung zu stellen. Der Service exponiert ein „Behaviour Subject“, über das die Komponenten die aktualisierten Daten erhalten. Dieses Subject soll lediglich die Daten konsumieren. Für Änderungen stellt der Service eine Reihe von Methoden bereit.

Über diese können Angular-Entwickler beispielsweise Datensätze hinzufügen, verändern oder löschen. Neben dieser einheitlichen Schnittstelle für den Datenzugriff speichert der Data Service auch die Informationen auf dem Server – und das spart wiederum Arbeit.

Die Schnittstelle mit dem Service besteht aus verschiedenen Methoden, mit denen sich Datensätze hinzufügen oder verändern lassen. Sie sorgen zunächst für die Persistenz der Informationen zum Server und lösen dann auf dem „Behavior Subject“ ein Event aus, das alle Subscriber benachrichtigt.

Anzeige
Anzeige

Angular Data-Service:

@Injectable()
export class AccountService {
 private accounts: Account[];
 public accounts$: BehaviorSubject<Account[]>;
 constructor(private http: Http) {}
 addAccount(account: Account) {
 this.http.post('/todo', todo).subscribe((res) => {
 this.accounts.push(account);
 this.accounts$.next(this.accounts);
 });
 }
}

Angular-Erweiterung: Flux-Architektur und Redux

Mit den Data Services löst Angular das Problem, bestimmte Informationen zentral vorzuhalten. In einer umfangreichen Applikation existieren jedoch meist zahlreiche Data Services parallel. Das wiederum kann unübersichtlich sein. App-Entwickler können dieses Problem mit einer zentralen Stelle umgehen, die den Applikationsstatus hält und überwacht. Dieses Architekturmuster ist vor allem durch die Flux-Architektur von Facebook populär geworden.

Der Ansatz Flux soll die Umsetzung umfangreicher Applikationen mit React – der View-Bibliothek von Facebook – ermöglichen. Reacts Fokus liegt auf der Implementierung von Benutzerschnittstellen und nicht auf der Gesamtarchitektur einer Applikation. Der Kerngedanke von Flux ist es, dass es eine zentrale Stelle gibt, über die sich der Applikationsstatus verändern lässt: den Dispatcher. Bei ihm sind die verschiedenen Stores registriert, die wiederum die Datenlieferanten für die Views der Applikation sind.

Die Views wiederum können über Actions des Dispatchers die Daten der App ändern. Diese Architekturform erzeugt einen zentralen und gerichteten Informationsfluss, mit dem sich selbst sehr große Applikationen umsetzen lassen. Neben dem ursprünglichen Flux gibt es mittlerweile noch weitere Bibliotheken, die dieses Architekturmuster mit einigen Abweichungen oder Erweiterungen implementieren. Eine der Bekanntesten ist Redux.

Anzeige
Anzeige

Die Flux-Architektur lässt sich jedoch nicht nur auf React-Applikationen anwenden, sondern auch mit Angular kombinieren. Dafür binden Entwickler beispielsweise die Redux-Bibliothek in ihre Angular-Applikation ein und können dann auf die Elemente der Bibliothek zugreifen. Im Gegensatz zu Flux legt Redux fest, dass es nur einen Store für die komplette Applikation geben soll. Dadurch können Entwickler, die Redux einsetzen, auf den Dispatcher verzichten und über die Actions direkt mit dem Store interagieren.

Die Actions implementiert man über reduce-Funktionen. Das sind Funktionen, die aus einem initialen State und einer Action einen neuen State erzeugen. Dadurch entsteht automatisch ein immutable State. Das bedeutet, dass Entwickler den State nicht modifizieren, sondern eine neue Repräsentation erzeugen. Durch diesen Ansatz kann das Framework Optimierungen durchführen, da es davon ausgehen kann, dass der State unveränderbar ist.

Natürlich müssen Angular-Nutzer auch hier das Rad nicht neu erfinden, sondern können bereits existierende Implementierungen wie beispielsweise ng2-redux einsetzen. Wer ng2-redux in seine Applikation eingebunden hat, kann ihn über die Dependency Injection wie einen gewöhnlichen Service nutzen.

Anzeige
Anzeige

Verwendung von ng2-redux:

@Component({
 selector: 'account-form',
 templateUrl: 'form.component.html'
})
class FormComponent {
 private account: Account;
  constructor(private ngRedux: NgRedux<IAppState>) {}
 onSave() {
 this.ngRedux.dispatch({ type: ADD, account: this.account });
 }
}

Fazit

Angular macht zwar einige Vorgaben zum Aufbau einer Applikation. Es liegt jedoch am Entwickler, wie er die Datenflüsse in einer App modelliert. Die Spanne der Möglichkeiten ist dabei groß und reicht von einfachen Data Services bis zur Einbindung zusätzlicher Bibliotheken wie Redux. Die meisten Implementierungen setzen auf einen gerichteten Datenfluss und die RxJS-Bibliothek – aus gutem Grund: So lassen sich Applikationen so strukturieren, dass Änderungen und Erweiterungen einfach möglich sind.

Mehr zu diesem Thema
Fast fertig!

Bitte klicke auf den Link in der Bestätigungsmail, um deine Anmeldung abzuschließen.

Du willst noch weitere Infos zum Newsletter? Jetzt mehr erfahren

Anzeige
Anzeige
Schreib den ersten Kommentar!
Bitte beachte unsere Community-Richtlinien

Wir freuen uns über kontroverse Diskussionen, die gerne auch mal hitzig geführt werden dürfen. Beleidigende, grob anstößige, rassistische und strafrechtlich relevante Äußerungen und Beiträge tolerieren wir nicht. Bitte achte darauf, dass du keine Texte veröffentlichst, für die du keine ausdrückliche Erlaubnis des Urhebers hast. Ebenfalls nicht erlaubt ist der Missbrauch der Webangebote unter t3n.de als Werbeplattform. Die Nennung von Produktnamen, Herstellern, Dienstleistern und Websites ist nur dann zulässig, wenn damit nicht vorrangig der Zweck der Werbung verfolgt wird. Wir behalten uns vor, Beiträge, die diese Regeln verletzen, zu löschen und Accounts zeitweilig oder auf Dauer zu sperren.

Trotz all dieser notwendigen Regeln: Diskutiere kontrovers, sage anderen deine Meinung, trage mit weiterführenden Informationen zum Wissensaustausch bei, aber bleibe dabei fair und respektiere die Meinung anderer. Wir wünschen Dir viel Spaß mit den Webangeboten von t3n und freuen uns auf spannende Beiträge.

Dein t3n-Team

Melde dich mit deinem t3n Account an oder fülle die unteren Felder aus.

Bitte schalte deinen Adblocker für t3n.de aus!
Hallo und herzlich willkommen bei t3n!

Bitte schalte deinen Adblocker für t3n.de aus, um diesen Artikel zu lesen.

Wir sind ein unabhängiger Publisher mit einem Team von mehr als 75 fantastischen Menschen, aber ohne riesigen Konzern im Rücken. Banner und ähnliche Werbemittel sind für unsere Finanzierung sehr wichtig.

Schon jetzt und im Namen der gesamten t3n-Crew: vielen Dank für deine Unterstützung! 🙌

Deine t3n-Crew

Anleitung zur Deaktivierung
Artikel merken

Bitte melde dich an, um diesen Artikel in deiner persönlichen Merkliste auf t3n zu speichern.

Jetzt registrieren und merken

Du hast schon einen t3n-Account? Hier anmelden

oder
Auf Mastodon teilen

Gib die URL deiner Mastodon-Instanz ein, um den Artikel zu teilen.

Anzeige
Anzeige