Hier kannst du dir das Gespräch mit Juri Strumpflohner und unserem Trainer Webdave ansehen!
In der modernen Webentwicklung nutzen Teams Monorepos für große Projekte, um ihre Codebasis zu verwalten. Monorepo steht kurz für “Monolithisches Repository” und bedeutet nichts anderes, als dass der gesamte Code für mehrere Projekte in einem einzigen Repository gespeichert wird. Zum Beispiel verwendet Google Monorepos für die Entwicklung seines Chrome Browsers. Das Chromium Project ist ein riesiges Open-Source-Projekt mit über 25 Millionen Codezeilen, das von über 2.000 Entwicklern aus der ganzen Welt gepflegt wird. Anstatt hier separate Repositories für jedes Feature oder Extension zu haben, werden alle Projekte unter einem Dach vereint.
Monorepos bieten viele Vorteile, bringen aber auch Herausforderungen mit sich. Wie schaffe ich es, dass ein riesiges Team effizient zusammenarbeiten kann? Genau hier setzen Juri Strumpflohner und das NX Team an. NX ist ein modernes Tool, das speziell für die Entwicklung in Monorepos entwickelt wurde. NX bietet eine Vielzahl von Funktionen, die euch dabei helfen können, effizienter und produktiver zu arbeiten.
Welche sind das? Was ist Task Parallelisierung und Caching? Was sind bewährte Praktiken in der Softwareentwicklung in großen Teams? Welche realen Probleme löst in der Entwicklung?
Darüber und über noch viel mehr haben sich Web Dave und Juri Strumpflohner, Sr. Director of Developer Experience for Nx unterhalten. Wer Juri ist und wie Juri zum NX Team gestoßen ist, erfahrt ihr natürlich auch. Schaut euch das Videogespräch dazu an.
In diesem Artikel zum Gespräch haben wir euch die Basics zusammengefasst und werden euch NX kurz vorstellen und euch einen Installation Guide aufgeschrieben.
Nx ist ein Build-System mit integrierten Built-In-Tooling und fortgeschrittenen CI-Fähigkeiten. NX ist vor allem für Monorepos gedacht und hilft dir, diese sowohl lokal als auch auf CI zu pflegen und skalieren.
Kurz gesagt, Nx hilft euch bei Builds und Tests, sowohl lokal auf deinem Computer als auch auf CI-Systemen (Continuous Integration, also Systeme, die automatisch deinen Code testen und vorbereiten). Dazu findest du NX Plugins, die es dir ermöglichen, verschiedene Entwicklungs-Tools nahtlos zu integrieren und zu automatisieren.
NX hilft dir dabei:
Nx funktioniert modular, sodass du nur die Features nutzen kannst, die du wirklich brauchst.
Das NX-Paket bietet technologieunabhängige Features wie workspace analysis, task running, caching, distribution, code generation, and automated code migrations. Plugins sind NPM-Pakete, die auf den Funktionen aufbauen, die das Nx-Paket bietet. Nx-Plugins enthalten Code Generatoren, Executer, und eine automatisierte Code-Migration, um deine Werkzeuge up to date zu halten. Devkit ist ein Set von Hilfsmitteln zum Bauen von Nx-Plugins. Nx Cloud hilft dir, dein Projekt auf CI zu skalieren, indem es dir Remote-Caching und eine leichtere Aufgabenstellung bietet. Dazu werden GitHub, GitLab und BitBucket integriert und durchsuchbare, strukturierte Logs bereitgestellt. Nx Console ist eine Erweiterung für VSCode, IntelliJ und VIM. Sie bietet Code-Autovervollständigung, interaktive Generatoren, Visualisierungen des Arbeitsbereichs, leistungsstarke Refaktorisierungen und mehr.
Um einen neuen Nx workspace zu erstellen, verwende zunächst folgenden Befehl:
npx create-nx-workspace
Dieser Prozess führt dich durch das Setup und fragt, ob du einen Monorepo oder eine Standalone App bevorzugst und ob du mit einem leeren oder einer vorkonfigurierten Setup beginnen möchtest.
npx create-nx-workspace@latest
NX Let's create a new workspace [https://nx.dev/getting-started/intro]
✔ Where would you like to create your workspace? · myorg
? Which stack do you want to use? …
None: Configures a TypeScript/JavaScript project with minimal structure.
React: Configures a React application with your framework of choice.
Vue: Configures a Vue application with your framework of choice.
Angular: Configures a Angular application with modern tooling.
Node: Configures a Node API application with your framework of choice.
Nachdem du deinen Arbeitsbereich erstellt hast, kannst du:
vnpx nx <ziel> <projekt>
npx nx run-many <ziel> <projekt>
Führe npx nx run-many -t build
zweimal aus, um zu sehen, wie das leistungsfähige Caching von Nx deinen Build-Prozess beschleunigt.
Erfahre mehr über die running tasks.
Falls du Nx zu einem bestehenden Repository hinzufügen möchtest:
npx nx@latest init
Du kannst das Nx NPM-Paket auch manuell installieren und eine nx.json
zur Konfiguration erstellen.
Lerne mehr, wie du Nx in einem bestehenden Projekt einsetzen kannst:
Du kannst Nx auch global installieren.
Je nachdem, welchen Paketmanager du nutzt, musst du einen der folgenden Befehle verwenden:
npm add --global nx@latest
Der Vorteil einer globalen Installation von NX ist, dass du Befehle für NX nicht mit npx, yarn oder pnpm starten musst. Stattdessen leitet die globale Installation die Ausführung einfach an die lokale NX-Installation in deinem Projektverzeichnis weiter. Dadurch vermeidest du Probleme, die durch veraltete, global installierte Pakete entstehen könnten.
Juri Strumpflohner ist Entwickler, Speaker, Content Creator und Sr. Director of Developer Experience for Nx. Juri ist Softwareentwickler seit über 15 Jahren, vom Backend bis hin zu Web-Apps. In dieser Zeit hat er sowohl in kleinen Startups als auch großen Unternehmen gearbeitet und als Softwarearchitekt auch Fortune-500-Unternehmen beraten. Als Google Developers Expert und Egghead instructor teilt er sein Wissen mit Leidenschaft und ist so eine führende Stimme in der Entwickler Community geworden. Du findest Juris Artikel auf seinem Blog juri.dev, seine Videokurse auf Egghead oder dem Nx YouTube Kanal oder auf unzähligen Konferenzen weltweit.
Natürlich findest du Juri auch auf Social Media:
]]>Kurz nach dem Erscheinen von Angular 17 hat NgRx eine neue, leichtgewichtige Lösung für das State-Management präsentiert, den Ngrx Signal Store. Dieser baut vollständig auf die seit Angular 16 verfügbaren Signals auf. In diesem Artikel wollen wir euch diesen neuen Store vorstellen, uns die Vorteile gegenüber dem bereits etablierten ngrx/store ansehen und ein kleines Code-Beispiel auf Basis des Signal Stores erstellen.
Ein Signal ist ein Wrapper um einen Wert, der interessierte Verbraucher benachrichtigen kann, wenn sich dieser Wert ändert. Signale können jeden Wert enthalten, von einfachen Grundelementen bis hin zu komplexen Datenstrukturen. Zum Thema Signals kannst du folgenden Artikel auf angular.de nachlesen:
Angular kurz erklärt: Signals
Beim State Mangement geht es darum, den Status der gesamten Applikation für alle Komponenten zentral zu verwalten und bereitzustellen. Die Vorteile von zentralem State Management werden in diesem Artikel gut beschrieben:
Hosteurope: Zentrales State Management für Angular
Ngrx stellt mit ngrx/store bereits eine Lösung für das State Management zur Verfügung, warum also jetzt der neue Signal Store? Er bietet einige Vorteile, wie z.B.:
Hinweis: Das gesamte Beispiel kann auf Stackblitz angesehen und getestet werden.
Zuerst müssen wir das neue Signal-Paket installieren:
npm install @ngrx/signals
Der Signal Store wird grundlegend über folgende 4 Funktionen definiert:
Für unser Beispiel wollen wir einen einfachen Todo-Store erstellen. Als API verwenden wir Dummyjson.
Um einen Store zu erstellen, rufen wir die signalStore-Methode auf:
export const TodoStore = signalStore(
{providedIn: 'root'},
withState({
todos: [] as TodoItem[]
})
);
Wir haben hier einen neuen Signal Store erstellt und diesen mit “{providedIn: ‘root’}” global verfügbar gemacht. Weiter haben wir den State definiert, in diesem Fall eine Liste von Todo-Items.
Wichtig zu betonen ist, alle in withState angegebenen Properties sind automatisch bereits Signale mit all deren zusätzlichen Funktionalitäten!
Mit dem withMethods-Feature können wir nun eigene Funktionen hinzufügen. Diese Funktion erwartet eine Factory-Funktion als Eingabeargument welche ein Methodenwörterbuch zurückgeben muß. Auf den Store selbst, einschließlich zuvor definierter Zustände, berechneter Signale und Methoden, kann über die Factory-Eingabe zugegriffen werden:
// ...
withMethods((store) => {
const todoService = inject(TodoService);
return {
async loadAllTodos() {
const todoResult = await todoService.getItems();
patchState(store, {todos: todoResult.todos});
},
async addTodo(todoText: string) {
const newTodo = await todoService.addItem(todoText);
patchState(store, {
todos: [...store.todos(), newTodo]
});
}
}
}
),
// ...
Auf den TodoService, welcher alle Funktionen zum Zugriff auf die Todo-API bündelt, greifen wir über Injection zu (inject(TodoService)). Mit patchState werden die geladenen Todo-Datensätze in den Store übernommen.
Mit dem withComputed-Feature können wir berechnete Signale dem Store hinzufügen. Diese Faktory-Funktion bekommt wieder den Store als Argument, wir verwenden in diesem Beispiel aber nur das Todos-Array (oder besser gesagt das Signal welches dieses Array liefert).
Diese Methode ist vergleichbar mit den Selectors im klassischen ngrx/store und ermöglicht es uns, Daten aus dem Store zu lesen und zu komponieren:
withComputed(({todos}) => ({
countTodos: computed(() => todos().length)
}),
)
Wir können zusätzlich auch auf die Erstellung und Zerstörung des Stores mit dem withHooks-Feature reagieren. Wollen wir z.B. alle Todos sofort bei Erstellung des Stores laden, wäre das folgendermaßen möglich:
withHooks({
onInit({loadAllTodos}) {
loadAllTodos();
},
onDestroy() {
console.log('on destroy');
},
})
Zusammengefasst sieht unser Signal Store nun folgendermaßen aus:
export const TodoStore = signalStore(
{providedIn: 'root'},
withState({
todos: [] as TodoItem[]
}),
withComputed(({todos} ) => ({
countTodos: computed(() => todos().length)
}),
),
withMethods((store) => {
const todoService = inject(TodoService)
return {
async loadAllTodos() {
const todoResult = await todoService.getItems();
patchState(store, {todos: todoResult.todos});
},
async addTodo(todoText: string) {
const newTodo = await todoService.addItem(todoText);
patchState(store, {
todos: [...store.todos(), newTodo]
});
}
}
}
),
withHooks({
onInit({loadAllTodos}) {
loadAllTodos();
},
onDestroy() {
console.log('on destroy');
},
})
);
Nachdem wir unseren Store definiert haben können wir ihn nun in unseren Komponenten verwenden. Da wir unseren Store in dem Beispiel als {providedIn: ‘root’} gekennzeichnet haben, kann er automatisch mit der inject-Methode in unseren Komponenten verfügbar gemacht werden:
public readonly store = inject(TodoStore);
// ...
this.store.addTodo(this.form.value.todoText ?? '');
// ...
In den Templates können wir nun auf alle Signale bzw. die berechneten Signale des Stores zugreifen und diese verwenden:
@for (todoItem of store.todos(); track todoItem.id) {
<div>
{{todoItem.todo}}
</div>
}
<div>Gesamt: {{store.countTodos()}}</div>
Da es sich wie bereits beschrieben bei allen Properties des Store automatisch um Signale handelt, greifen wir über eine Methode auf die Elemente zu, d.h. wir müssen store.todos() verwenden (statt store.todos).
Was den Signal Store besonders flexibel macht ist die Möglichkeit, eigene Store-Features zu erstellen. Der Vorteil, den man dadurch bekommt: Diese Features können auch in anderen Stores wiederverwendet werden und man kann bestimmte Funktionalitäten kapseln.
Ein benutzerdefiniertes Feature wird erstellt, indem man die signalStoreFeature-Funktion aufruft. Diese akzeptiert eine Sequenz von denselben Basis-Funktionen oder auch anderer benutzerdefinierten Funktionen wie ein Signal Store selbst.
Wir wollen in unserem Beispiel ein Feature erstellen, welches uns einen Ladezustand angibt. D.h. solange die Daten geladen werden, soll der Zustand true sein, wenn die Daten fertig geladen sind, wieder false.
Als erstes erstellen wir das Feature in der Datei loading.feature.ts:
export function withLoading() {
return signalStoreFeature(
withState({loading: false}),
withMethods((store) => {
return {
setLoading () {
patchState(store, {loading: true});
},
setCompleted () {
patchState(store, {loading: false});
}
}
})
)
}
Wie wir hier sehen, kann das Feature eigene State-Variablen definieren, welche dem übergeordneten Store, in welchem wir das Feature verwenden, hinzugefügt werden.
Wir können nun in unserem Store das loading-Feature folgendermaßen verwenden:
export const TodoStore = signalStore(
// …
withLoading(),
withMethods((store) => {
const todoService = inject(TodoService)
return {
async loadAllTodos() {
store.setLoading();
const todoResult = await todoService.getItems();
patchState(store, {todos: todoResult.todos});
store.setCompleted();
},
// ...
}
}),
// …
);
Mit withLoading() haben wir unser Feature dem Store hinzugefügt. In den danach folgenden Methoden wurde der Store bereits um die Funktionen unseres Features, nämlich setLoading() und setCompleted() erweitert, sodaß wir diese in den Methoden verwenden können.
Hinweis: Man muss hier auf die richtige Reihenfolge der Funktionen achten: würden wir withLoading() erst nach withMethods() hinzufügen, könnten wir das Feature nicht verwenden, da der Store noch nicht um dessen Eigenschaften erweitert wurde!
Ngrx bietet zur Verwaltung von Entitäten eine eigene Erweiterung, welche die immer wiederkehrenden Aufgaben, wie z.B. hinzufügen, aktualisieren oder löschen von Elementen aus Sammlungen vereinfacht. Um die Erweiterung zu verwenden, definieren wir in unserem Store mit der withEntities-Methode, welchen Typ von Entity wir verwalten wollen:
export const TodoStore = signalStore(
{providedIn: 'root'},
withEntities<TodoItem>()
);
Wie man sieht ist keine withState-Methode mehr notwendig, die withEntities-Methode liefert uns gleich folgende Eigenschaften:
ids: Signal<EntityId[]>
: die Ids aller Elementeentities
: Signal<TodoItem[]>: ein Array aller ElementeentityMap
: Signal<EntityMapZu beachten ist, dass das Entity ein Feld ‘id’ haben muss, welche das Element eindeutig identifizieren kann und vom Typ EntityId (string oder number) ist.
Weiters erhalten wir folgende Methoden, welche wir in patchState verwenden können:
addEntity, addEntities
: fügt ein oder mehrere neue Elemente hinzusetEntity, setEntities
: tauscht ein oder mehrere Elemente aussetAllEntities
: löscht die Liste komplett und setzt sie auf die übergebenen ElementeupdateEntity, updateEntities
: ändert einzelne Eigenschaften für ein oder mehrere ElementeupdateAllEntities
: ändert einzelne Eigenschaften für alle ElementeremoveEntity, removeEntities
: löscht ein- oder mehrere Elemente aus der ListeUnser Todo-Beispiel würde dann mit den neuen Methoden folgendermaßen aussehen:
export const TodoStore = signalStore(
{providedIn: 'root'},
withEntities<TodoItem>(),
withMethods((store) => {
const todoService = inject(TodoService)
return {
async loadAllTodos() {
const todoResult = await todoService.getItems();
patchState(store, setEntities(todoResult.todos));
},
async addTodo(todoText: string) {
const newTodo = await todoService.addItem(todoText);
patchState(store, addEntity(newTodo));
},
async updateTodo(id: string, todoText: string) {
await todoService.updateItem(id, todoText);
patchState(store, updateEntity({id, changes: {todo: todoText}}));
}
}
}
),
// ...
);
Der NgRx Signal Store bietet eine leichtgewichtige, aber leistungsfähige Lösung für das Zustandsmanagement in Angular-Anwendungen. Durch seine Unterstützung für Signale ermöglicht er eine effiziente Verwaltung und Bereitstellung von Zustandsinformationen, wodurch die Entwicklung komplexer Anwendungen erleichtert wird. Die Verwendung des NgRx Signal Store kann dazu beitragen, die Codebasis zu strukturieren, die Wartbarkeit zu verbessern und die Entwicklungszeit zu verkürzen, indem er eine klare Trennung von Zustandslogik und Benutzerinteraktion ermöglicht. Und nicht zuletzt aufgrund der Erweiterungsmöglichkeiten bietet der NgRx Signal Store viel Flexibilität mit gleichzeitig weniger Boilerplate-Code.
]]>In unserem zweiten Artikel aus der Kurz-erklärt-Serie Artikel führen wir euch schrittweise in das große Thema Hydration in Angular ein und erklären kurz die Schlüsselbegriffe und die Mechanismen des Rendering. Dazu starten wir mit dem Begriff der Hydration. Als nächstes werden wir uns ansehen, was Server-Side-Rendering und Client-Side-Rendering sind und worin sich die Prozesse genau unterscheiden. Danach werfen wir einen kurzen Blick auf die Web Core Vitals. Abschließend befassen wir uns wieder umfassend mit dem Thema Hydration in Angular.
Zunächst muss die Frage gestellt werden: Was ist Hydration? Und in welchem Zusammenhang steht der Begriff mit den Begriffen wie Server-Side Rendering (SSR) und Client-Side Rendering (CSR)? Hydration beschreibt einen Prozess, bei dem eine auf dem Server gerenderte Webseite im Browser des Benutzers “zum Leben erweckt” wird. Dabei geht es darum, dass das statische HTML-Template durch deinen JavaScript-Code interaktiv wird.
Angular selbst definiert den Begriff in seiner Dokumentation wie folgt:
“Hydration is the process that restores the server-side rendered application on the client. This includes things like reusing the > server rendered DOM structures, persisting the application state, transferring application data that was retrieved already by the > server, and other processes.” (Angular Documentation)
Single-Page Applications (SPAs) bieten eine bessere Laufzeit-Performance als klassische Webanwendungen. Ist deine Webanwendung einmal geladen, werden alle Inhalte dynamisch aktualisiert, ohne dass die Website jedes Mal komplett neu gecatcht und gerendert werden muss. Das sorgt für eine flüssige und app-ähnliche User Experience. Allerdings muss der Browser dafür beim initialen Laden der Seite zusätzlich zum HTML große Mengen an JavaScript-Code laden. Dieser Prozess wird der “Initial Load” genannt und definiert den Zeitraum, bis eine Webseite nach deinem HTTP-Request für die User vollständig sichtbar und interaktiv ist.
Während diese verlängerte Ladezeit für Webanwendungen wie z.B. Google Maps oder für soziale Medien wie Instagram oder Twitter kaum ins Gewicht fallen, ist sie in anderen Bereichen kritisch. Insbesondere im E-Commerce-Bereich gilt es, die User Experience so einfach und möglichst ohne Ladezeiten zu gestalten, um die sogenannte “Bounce Rate” (auch Absprungrate genannt) zu minimieren. Deswegen ist es in diesem Zusammenhang gängige Praxis geworden, SPAs serverseitig zu rendern. Der Server generiert eine vollständige HTML-Datei vorab, die sofort vom Browser gerendert werden kann. Damit sehen Nutzer fast sofort zumindest eine statische Version der Seite. Sobald die JavaScript-Bundles geladen sind, können User die Seite auch interaktiv nutzen.
Bis zur Einführung der “Full App Non-destructive Hydration” war dieser Prozess in Angular jedoch “destruktiv”. Das hat bedeutet, dass die bereits auf Serverseite vorab-gerenderten DOM-Strukturen wieder zerstört und für jeden Neustart auf Clientseite wieder neu gerendert wurden. In einfacher Sprache: Es wurde doppelt gerendert. Die Folge war ein berühmt-berüchtigtes Flickern bei jedem Initial Load vieler Webseiten.
Aber warum tritt dieses Flackern auf? Was bedeutet es, wenn DOM-Strukturen serverseitig oder clientseitig gerendert werden? Wie funktioniert Server-Side Rendering und worin unterscheidet es sich vom Client-Side Rendering? Was versteht man unter Full App Non-Destructive Hydration? Und nach welchen Messparametern wird hier überhaupt gemessen?
Damit ihr die vielen bevorstehenden Updates im Hause Angular zum Thema Hydration und zukünftige Artikel hier auf dem Portal besser einordnen könnt, wollen wir euch die Prozesse hinter den Begriffen in diesem Artikel kurz erklären.
Um es kurz und in zwei Sätzen zu sagen. Beim Server-Side Rendering (SSR) ist es die Aufgabe des Servers, deine Webanwendung zu rendern. Beim Client-Side Rendering findet das Rendering in deinem Browser, also auf Client-Seite, statt. Aber um das Ganze besser zu verstehen, werden wir uns die Prozesse des SSR und CSR genauer ansehen.
Beim Server-Side Rendering wird der Inhalt einer Webseite vorab auf dem Server gerendert und als vollständige HTML-Datei an den Browser des Benutzers geliefert. Dadurch wird die Webseite sofort sichtbar, sobald sie vom Browser geladen ist. Vorausgesetzt natürlich, es besteht eine anständige Internetverbindung.
Der entscheidende Vorteil des Server-Side Renderings liegt in einem deutlich verbesserten Initial Load der Webanwendung. Verkürzte Wartezeiten steigern die User Experience erheblich und verbessern das SEO-Ranking auf Google. Zudem ist die Anwendung auch auf langsamen Geräten oder bei schlechter Internetverbindung nutzbar, da der Server die Hauptlast des Renderings trägt. Die Medaille hat natürlich auch eine andere Seite. Das führt zu einer erhöhten Belastung des Servers, was insbesondere bei Webseiten mit hohem Traffic Auswirkungen auf Skalierbarkeit und Kosten haben kann. Außerdem macht das SSR die Architektur deiner Webanwendungen komplexer, da einige Szenarien für das Rendering und die Zustandsverwaltung auf dem Server durchdacht werden müssen. Diese Komplexität ist mit mehr Entwicklungszeit und somit höheren Kosten verbunden. Auch die Wartung wird komplexer. Auch die Wartung wird damit komplexer. Zusammenfassend kann gesagt werden, SSR ist besonders nützlich für content-orientierte Webanwendungen wie E-Commerce-Seiten, bei denen SEO-Rankings und schnelle Ladezeiten entscheidend sind.
Beim Client-Side Rendering (CSR) übernimmt der Browser des Nutzers die Hauptarbeit des Renderings der Webseite. Anders als beim Server-Side Rendering (SSR), bei dem der Server dem Browser eine vollständige HTML-Datei liefert, sendet der Server hier ein minimales HTML-Dokument zusammen mit JavaScript-Dateien, die die Webseite dynamisch generieren und darstellen. Sobald der Browser diese Dateien lädt, wird das JavaScript ausgeführt, um die Webseite dynamisch zu generieren und darzustellen.
Der große Vorteil beim Client-Side Rendering (CSR) ist die reduzierte Serverbelastung, da der Großteil des Renderings direkt im Browser des Users stattfindet. Das schont Serverressourcen insbesondere bei hohen Datenaufkommen, senkt damit Kosten und erleichtert die Skalierbarkeit in Projekten. Insbesondere für datenstarke Projekte ist das ein wichtiger Punkt. Auf der anderen Seite dauert der Inital Load beim CSR deutlich länger. User müssen länger warten, bis sie die erste vollständige Seite sehen, da der Browser zusätzliche Skripte laden und ausführen muss. Das verschlechtert eure Web Core Vitals und damit euer SEO-Ranking. Zusammenfassend eignet sich CSR besonders für Anwendungen, die eine hohe Interaktivität und dynamische Inhaltsaktualisierungen ohne ständiges Neuladen der Webseite erfordern. Als bestes Beispiel kann hier zum Beispiel Google Maps genannt werden. Letztendlich hängt die Wahl zwischen SSR und CSR von den spezifischen Anforderungen eures Projekts und den Prioritäten in Bezug auf Performance, SEO und Nutzererfahrung ab. Allgemein gesprochen, sobald SEO Performance wichtig ist, sollte eure Wahl auf SSR fallen.
CSR eignet sich besonders für Anwendungen mit hoher Interaktivität und dynamischen Inhalten ohne ständiges Neuladen, wie z.B. Google Maps. Die Wahl zwischen SSR und CSR sollte basierend auf den spezifischen Anforderungen des Projekts und den Prioritäten hinsichtlich Performance, SEO und Nutzererfahrung getroffen werden. Generell gilt: Ist SEO-Performance entscheidend, ist SSR die bevorzugte Wahl.
Aber nach welchen Metriken wird eigentlich gemessen? Wann ist eine Webseite schnell?
Um die User Experience auf Webseiten zu messen und zu verbessern, haben sich die Core Web Vitals von Google etabliert. Sie umfassen drei Hauptaspekte: die Ladezeit, die Interaktivität und die visuelle Stabilität von Webseiten. Die Core Web Vitals sind ein wichtiger Faktor für das SEO-Ranking deiner Webseite. Schnelle Ladezeiten und reibungslose Interaktivität führen zu einer besseren User Experience, minimieren die Absprungrate (engl. “Bounce Rate”) und erhöhen die durchschnittliche Verweildauer (engl. “Average Time on Site”). Das hat maßgeblich Einfluss auf das Ranking in den Google-Suchergebnissen.
Der Fokus der Web Core Vitals (und damit haben diese auch den größten Einfluss auf dein SEO-Ranking) liegt dabei auf dem Largest Contentful Paint (LCP), dem First Input Delay (FID) und dem Cumulative Layout Shift (CLS). Sie messen, wie schnell Inhalte für den Nutzer sichtbar und interaktiv werden.
Der LCP ist quasi der erste Eindruck deiner Seite. Gemessen wird, wie lange es dauert, bis das größte Element auf deiner Webseite gerendert ist und für den User sichtbar wird. Meist handelt es sich dabei um ein Bild oder eine Grafik. Für eine gute Bewertung sollte der LCP innerhalb von 2,5 Sekunden nach dem ersten Laden der Seite erfolgen. Der CLS bewertet die visuelle Stabilität während des Ladeprozesses. Verschieben sich Elemente auf dem Bildschirm während des Renderings? Wir alle haben die Erfahrung gemacht, dass Bedienelemente während der Ladezeit im Layout sich plötzlich verschieben. Der FID misst die Zeit, die eine Seite benötigt, um auf den ersten Klick eines Links oder Buttons zu reagieren. Für eine gute Bewertung sollte die Seite eine FID von 100 Millisekunden oder weniger aufweisen.
Für mehr Informationen, Tutorials zur Optimierung von Performance und SEO-Ranking sowie Tools zur Messung besucht am besten die Web Core Vitals Webseite.
Aber jetzt wieder zurück zu Angular und zur Full App Non-Destructive Hydration.
Angular unterstützt serverseitiges Rendering (SSR) bereits seit einiger Zeit durch seine Libary Angular Universal. Dieser Prozess war jedoch bis zum Update auf Angular 16 destruktiv. Um zu verstehen, was das bedeutet, wollen wir uns den Prozess näher ansehen:
Prozess der destruktiven Hydration in Angular Universal bis Version 16:
Wie wir sehen können, zerstört Angular die bereits (im dritten Schritt) gerenderten HTML-Strukturen und lädt in einem letzten Schritt das gesamte Markup neu. Im Grunde wurde die Seite dadurch doppelt gerendert. Das ist ein besonders ineffizienter Prozess für umfangreiche Anwendungen. Das ist auch der Grund für das Flickern. Über die Zeit gab es viele Ansätze, dieses Flickern zu minimieren. Dennoch war dieses in Tools wie Lighthouse und WebPageTest weiterhin sichtbar.
Die Full App Non-Destructive Hydration hat genau da angesetzt, indem das bereits serverseitig gerenderte DOM-Markup wiederverwendet wird. “Non-destructive” meint nichts anderes, als dass das serverseitig gerenderte DOM-Markup nicht zerstört und wieder neu gerendert wird; stattdessen durchläuft Angular die DOM-Struktur, hängt Event-Listener an und bindet Daten, um das Rendering zu vervollständigen.
Die Vorteile von Full App Non-Destructive Hydration sind ein deutlicher Performance-Boost um 40% bis 50% für den Largest Contentful Paint (LCP). Siehe Tweet
Das ist ein immenser Boost vor allem für Projekte mit komplexen Benutzeroberflächen oder dynamischen Inhalten. Ein verbesserter LCP führt wiederum zu einem besseren SEO-Ranking.
Die Full App Non-Destructive Hydration war nur der erste Schritt einer grundsätzlichen Richtungsänderung für das Rendering in Angular. Bisher ist das clientseitige Rendering der Default für alle Projekte in Angular. Das soll sich schrittweise ändern. Wie bereits in der Roadmap angekündigt, plant das Angular Team, zukünftig standardmäßig auf ein hybrides Rendering (Server-Side Rendering und Static Site Generation) zu setzen.
Gleichzeitig hat sich die partielle Hydration in der Webentwicklung etabliert. Hierbei werden Komponenten, die nicht essentiell für die Seite sind oder sich nicht im Viewport befinden, verzögert geladen. Wichtige Begriffe in diesem Zusammenhang sind Lazy Loading und Deferrable Views.
In einem nächsten Update in Angular sollen alle Inhalte innerhalb der festgelegten Defer-Blöcke serverseitig gerendert und nur auf Client-Seite nach Bedarf hydratisiert werden. In diesem Szenario lädt der Browser die JavaScript-Bundles für die eingestellten Deferred Views nicht, bis ein Trigger-Ereignis auftritt. Erst dann lädt Angular das zugehörige JavaScript herunter und hydratisiert diesen Teil der Ansicht. Wenn eine Komponente nicht benötigt wird, wird sie gar nicht erst hydratisiert. Das macht auch Sinn. Wenn man einen Flug suchen oder buchen will, ist es für die User Experience wichtiger, dass alle interaktiven Elemente in diesem Zusammenhang möglichst schnell geladen sind. In eher seltenen Fällen priorisieren Nutzerinnen die Marketing- oder Bonusangebote wie Restauranttipps. Und wenn diese Angebote gar nicht erst wahrgenommen werden, warum sollten sie überhaupt hydratisiert werden?
In diesem Artikel haben wir euch die Begriffe Hydration, Server-Side Rendering (SSR) und Client-Side Rendering (CSR) kurz erklärt. Das ist wichtig, um zu verstehen, warum die Einführung der Full App Non-Destructive Hydration für Angular ein Meilenstein war. Wir haben uns außerdem die Bedeutung der Core Web Vitals angesehen. Insbesondere für angehende Webentwickler ist es wichtig, ein allgemeines Verständnis für die technischen Prozesse aufzubauen. Je fester das Fundament ist, desto höher lässt sich bauen. Und im Falle der Angular Hydration erwarten uns die nächsten Monate viele Neuigkeiten. Umso wichtiger ist es, dass wir das Thema umfassend beleuchten. Deswegen werden wir eine Reihe an Artikel zu den Themenkomplexen Performance und Server-Side-Rendering in Angular veröffentlichen.
Im nächsten Artikel widmen wir uns der praktischen Implementierung der Full App Non-destructive Hydration, Lazy Loading und Deferred Views in Angular. Dazu wird uns wieder Webdave via Twitch einige detaillierte Codebeispiele geben, die euch helfen, die Materie noch besser zu verstehen. Wir freuen uns!
Immer bessere und feinere Steuerungsmöglichkeiten, bedeuten aber auch höhere Anforderungen an euch. Umso wichtiger ist es, eine Community zu haben und Best Practices auszutauschen. Wie immer laden wir euch deshalb ein, Teil unserer Angular Community auf Discord zu werden. Seit 2013 bieten wir euch hier Tutorials, Artikel und Schulungen rund um das Angular Framework. Gestartet durch unsere Begeisterung für die modernen Möglichkeiten der Webentwicklung hat sich mittlerweile eine ganze Community dazu entwickelt. Mit mittlerweile 18 Meetups, die insgesamt über 10.000 Angular-Entwicklerinnen:innen als Plattform für regelmäßigen Austausch dienen, sind wir damit in Europa die Region mit den meisten Angular-Entwicklerinnen. Werde Teil unserer Community!
]]>Wir leben im Zeitalter der Superlative. ‘Größer, schneller, besser’ war gestern. Heute ist es “am größten, am schnellsten, am besten”. Technologische Fortschritte müssen in nicht weniger als Superlativen gemessen werden, um Aufsehen zu erregen. Das gilt natürlich auch für die Welt der Frameworks. Und Angular nimmt da keine Sonderstellung ein. Folgerichtig wurde das Update auf Angular 16 im Mai dieses Jahres als das größte Update seit dem initialen Release des Frameworks beworben. Und nicht ohne Grund, denn das Update hatte es in sich: das signalbasierte Reaktivitätsmodell, die Full-App Non-Destructive Hydration, die Unterstützung von TypeScript 5.0 und vieles mehr.
Aber was kommt eigentlich nach dem Superlativ? Die Antwort auf diese fast philosophische Frage hat uns jetzt Minko Gechev gegeben: eine Renaissance. Das ist ein Begriff, der wortwörtlich übersetzt die Wiedergeburt bedeutet und eine Erneuerung verspricht. Bereits Anfang 2023 hat Sarah Drasner, als Director of Engineering bei Google und Leiterin des Angular-Teams, die neue Ära der ‘Angular Renaissance’ auf X, ehemals Twitter, angekündigt. Und mit diesem Begriff hat jetzt auch der Technical Lead und Manager des Angular Teams das neue Update auf Angular 17 angekündigt.
Was damit gemeint ist und ob das neue Update wirklich der Beginn einer neuen Ära ist, wollen wir uns in diesem Artikel zu Beginn des neuen Jahres nochmal gemeinsam anschauen.
Und das Beste: Wenn du bereits zu müde zum Lesen bist, kannst du dir das Ganze alternativ auch von unserem Trainer Webdave auf seinem Twitch-Kanal ansehen. Übrigens findest du alle Codebeispiele aus dem Video in Webdaves Stackblitz! Wie immer: Danke, Webdave!
Das auffälligste Update ist mit Sicherheit das neue Logo. Angular verlässt sein traditionell rotes Schild-Symbol, das mit AngularJS eingeführt wurde und auch nach der Neuentwicklung von Angular im Jahr 2016 beibehalten wurde. Stattdessen wurde nach unserer Meinung ein durchaus gelungenes stilisiertes “A” mit einem frischen und zukunftsorientierten Erscheinungsbild gewählt.
Ein weiteres primär visuelles Update ist die neue Angular-Präsenz im Web. Angular.dev löst damit Angular.io als Hauptquelle für Informationen und Ressourcen ab. Das neue Portal hat ein völlig überarbeitetes neues Design bekommen und dient zukünftig als zentrale Anlaufstelle für alles rund um Angular. Das beinhaltet auch eine überarbeitete Dokumentation und eigene Tutorials. Darüber hinaus bietet angular.dev nun auch eine interaktive Lernumgebung, um neue Features oder erste Schritte direkt im Browser auszuprobieren und zu lernen. Angular.dev ist ein weiterer Schritt, einen leichteren Einstieg insbesondere für Junior Entwickler und Quereinsteiger in das Framework zu ermöglichen.
Zunächst wollen wir kurz die vielen Tooling-Updates in Angular 17 betrachten.
Wie versprochen ist hier auch das Video von Webdave zum Anguglar 17 Update.
Die Angular CLI hat bedeutende Updates erfahren. Standalone-Projekte und Routing sind jetzt der neue Standard, und die erforderliche Node-Version wurde auf 18 aktualisiert. Besonders für Junior Entwickler werden dadurch die Einstiegshürden in komplexe Projekte gesenkt. Erfahrene Entwickler werden die gesteigerte Effizienz und Flexibilität schätzen, die durch diese Updates ermöglicht werden.
Die neue View Transition API erlaubt eine verbesserte Steuerung beim Laden und Darstellen von Grafiken. Diese Funktion bietet Entwicklern mehr Flexibilität und Kontrolle über die visuellen Aspekte ihrer Anwendungen. Vor allem dynamische und reaktionsfähige visuelle Elemente lassen sich mit größerer Präzision steuern. Das ist besonders relevant für Anwendungen mit komplexen visuellen Elementen, wie eingebetteten Maps in einer Booking-Anwendung.
Mit den Updates am Component Decorator und der Einführung von individuellen Styles und Templates haben Entwickler jetzt die Möglichkeit, ihre individuellen Styles direkt in Komponenten zu integrieren. Dadurch werden weniger HTTP-Requests für externe Stylesheets benötigt, was wiederum die Initialladezeit deiner Anwendung verbessern kann.
Das Feature der “Deferrable Views”, welches sich aktuell noch in Developer Preview befindet, ermöglicht neue Möglichkeiten für das Lazy Loading von Komponenten. Mit Hilfe von @defer
-Blöcken können Entwickler das Laden und Rendern von Komponenten gezielt verzögern:
@defer (on viewport) {
<meine-komponente />
}
Die Teile deiner Angular Anwendung, die nicht sofort im sichtbaren Bereich liegen, werden dadurch erst dann geladen und gerendert, wenn sie tatsächlich benötigt werden. Das schont Browser-Ressourcen in erheblichem Ausmaß, da nur die für den aktuellen Benutzerkontext relevanten Teile der Anwendung geladen werden. Hier hast du folgende Möglichkeiten:
Die genannten Benchmarks sind bemerkenswert: bis zu 90% schnellere Laufzeiten und bis zu 87% schnellere Builds für hybrides Rendering sowie 67% für Client-seitiges Rendering.
Vite und Esbuild sind nun Standard in der Angular CLI für neue Projekte implementiert. Auch dieses Feature wurde bereits in der Version 16 zunächst in der Developer Preview eingeführt. Die standardmäßige Integration der Werkzeuge ist ein signifikanter Schritt nach vorn in Effizienz und Geschwindigkeit und markiert nicht weniger als einen Wendepunkt in der Build-Infrastruktur des Frameworks. Nach Angular-eigenen Zahlen werden bei der Nutzung von serverseitigem Rendering (SSR) und Static Site Generation (SSG) in ng build bis zu 87% schnellere Build-Zeiten und eine um 80% verbesserte Edit-Refresh-Schleife in ng serve gemeldet.
In einem nächsten Patch- oder Minor Release werden zusätzlich Schemata zur automatischen Migration bestehender Projekte, die Hybrid-Rendering verwenden, bereitgestellt. Wer die neue Application Builder-Technologie jetzt schon testen möchte, dem bietet die Angular-Dokumentation eine entsprechende Hilfestellung.
Weitere Informationen findest in der Angular Documentaion: Getting started with the Angular CLI’s new build system
Die in Angular 16 eingeführte Full App Non-Destructive Hydration ermöglicht eine effizientere Synchronisation zwischen Backend und Frontend in Single-Page-Applikationen (SPAs). Ein Kernvorteil dieser Technologie ist die signifikante Reduzierung des “Flickerns”, das normalerweise beim Übergang vom serverseitigen zum clientseitigen Rendering auftritt.
Anstatt bei jedem Client-Start die gesamte DOM-Struktur neu zu rendern, nutzt Angular vorhandene serverseitig gerenderte DOM-Knoten. Wenn der Client die vom Server gerenderte HTML-Datei empfängt, identifiziert Angular diese existierenden DOM-Knoten und aktualisiert lediglich deren Eigenschaften und Attribute, anstatt eine vollständige Neuerstellung vorzunehmen.
Durch die Wiederverwendung der serverseitig gerenderten DOM-Knoten verringert sich die Zeit, die benötigt wird, um die Anwendung zu rendern. Das Resultat ist ein schnelleres Initialrendering und flickerfreie Übergänge. Da keine Notwendigkeit besteht, neue DOM-Knoten zu erstellen, wird der Speicherverbrauch der Anwendung reduziert. Dies ist insbesondere für Geräte mit begrenzten Ressourcen, wie mobile Endgeräte, von Vorteil.
Mehr dazu findest in der Angular Documentaion: Hydration
Angular Signals sind nun stabil und nicht länger nur in der Developer Preview verfügbar. Signals sind das neueste Tool in der reaktiven Programmierung in Angular. Sie wurden erstmals im Mai dieses Jahres mit dem Update auf Version 16 als Developer Preview eingeführt und haben sich nun mit der Veröffentlichung von Angular 17 als festes Standbein des Frameworks etabliert.
Signals sind insbesondere für Junior Angular-Entwickler*innen ein hilfreiches Tool, da sie das reaktive Programmieren entscheidend erleichtern. Im Grunde sind sie - wie der Name es vermuten lässt - ein Signalgeber mit einem Wert, der alle verbundenen Consumer benachrichtigt, wenn sich dieser Wert ändert. Diese Werte können einfache Strings, ein Array oder auch komplexere Datentypen sein. Ändern sich diese Werte, werden automatisch alle Konsumenten informiert und aktualisieren ihre Werte. Im Vergleich zu Observables und RxJS bieten Angular Signals eine intuitivere und klarere Syntax. Dies führt zu einer verbesserten Lesbarkeit und Wartbarkeit des Codes und verbessert nebenbei auch signifikant die Leistung der Angular Anwendung.
Weitere Informationen zum Thema der Angular Signals findet ihr in den unsere bereits veröffentlichten Artikel auf Angular.de:
Developer Ergonomics mit Angular Signals
Die neue Control Flow-Syntax in Angular 17 ist noch im Developer Preview. Sie ermöglicht eine direktere und effizientere Kontrolle über das Template einer Komponente. Dies ist ein signifikanter Fortschritt gegenüber der vorherigen Situation, in der Strukturdirektiven wie ngIf, ngFor und ngSwitch zwar für die Template-Kontrolle verwendet wurden, aber in einem anderen Kontext als die Komponente selbst standen. Diese Direktiven gehörten zwar zum Framework, aber nicht direkt zur Komponentenlogik, was zu einer Diskrepanz zwischen Komponentenlogik und Template-Kontrolle führte.
Zum Beispiel kann eine Komponente nun direkt auf Änderungen im Template reagieren, ohne auf Umwege über Strukturdirektiven angewiesen zu sein. Ein Beispiel für diese neue Syntax könnte so aussehen:
@template {
<!-- Bisher -->
<div *if="bedingung">Inhalt anzeigen</div>
<!-- Neu -–>
@if(bedingung){
<div >Inhalt anzeigen</div>
}
Hier findest du den Link zum passenden Stackblitz unseres Trainers Webdave
Zusammenfassend können wir sagen, dass das Update auf Angular v17 eine Reihe von innovativen neuen Features und Tooling-Optimierungen mit sich gebracht hat. Im Ergebnis liefert das Angular Team wie gewohnt ein Paket signifikanter Performance-Boosts für das Framework. Damit zementiert Angular wieder einmal seinen Status als einer der Branchenführer für die professionelle Entwicklung von Enterprise Software.
Das Highlight der Feature-Updates in Angular 17 ist mit Sicherheit der neue Control Flow. Dieser befindet sich noch in der Developer Preview. Das bedeutet, dass das Feature weitgehend stabil ist, aber das Angular Team in den nächsten Monaten weiter am Feature arbeiten und Details entsprechend dem Feedback aus der Angular-Community anpassen wird. Die Angular Signals hingegen sind nun stabil, genauso wie die Integration von Vite und Esbuild in die Angular CLI.
Dieses Mal ging es jedoch um mehr als nur funktionelle Verbesserungen. Mit dem Update hat Angular auch einen neuen Look bekommen. Dazu gehören das neue Logo-Design, eine neue aktualisierte Dokumentation und insbesondere das neue Lernportal angular.dev.
Ist das jetzt schon eine Renaissance oder der Beginn einer neuen Ära?
Ja, und wir sind sogar bereits mitten in der “Angular Renaissance”!
Betrachten wir die Entwicklung des Frameworks der letzten Jahre, wird eines deutlich: die Angular-User-Experience hatte mindestens eine ähnliche Priorität wie die Performance des Frameworks. Features wie Signals oder der neue Control Flow erhöhen nicht nur die Performance des Frameworks, sondern vereinfachen vor allem das Arbeiten mit Angular. Auch das neue moderne Design muss in dieser Logik gedacht werden.
Die Entwicklung ist kein Zufall.Über die letzten Jahre hat sich das Angular-Team wieder verstärkt auf das Feedback und die Bedürfnisse der Angular-Community fokussiert und aktiv in die Weiterentwicklung des Frameworks integriert. Insbesondere die Ergebnisse der jährlichen Angular-Umfrage tragen zur Priorisierung für das nächste Jahr bei, wie es Minko Gechev selbst sagt:
One of the strongest guiding indicators which has been highly influential in our prioritization process are the results from the Angular developer survey.
Die Angular-Community hat gesprochen, und die Angular-User-Experience wurde in der Roadmap 2024 als oberste Priorität ausgerufen. Das Angular-Team will vor allem attraktiver für Anfänger werden. Dazu werden nach und nach alle funktionellen Pain Points abgearbeitet, um es angehenden Angular-Entwicklern so leicht wie möglich zu machen, in die Welt von Angular einzutauchen.
Das sind sehr gute Neuigkeiten für alle Mitglieder der Angular-Community, insbesondere für unsere deutsche Angular.de Community). Schließlich bedeutet es, dass wir aktiv an der Zukunft des Frameworks teilhaben können. Eure Diskussionen und euer Feedback haben offiziel die höchste Priorität im Hause Angular. Deshalb ermutigen wir euch: Engagiert euch in unserer Angular Community auf Discord. Nehmt Teil an unseren 18 Meetups, die insgesamt über 10.000 Angular-Entwicklerinnen und -Entwickler als Plattform für regelmäßigen Austausch dienen. Wir sind damit in Europa die Region mit den meisten Angular-Entwicklerinnen.
]]>Angular Signals sind das neue Tool in der reaktiven Programmierung mit Angular. Sie wurden erstmals im Mai dieses Jahres mit dem Update auf Version 16 als Developer Preview eingeführt und haben sich nun mit der Veröffentlichung von Angular 17 als festes Standbein des Frameworks etabliert.
Signals sind insbesondere für Junior Angular-Entwickler*innen ein hilfreiches Tool, da sie das reaktive Programmieren entscheidend erleichtern. Im Grunde sind sie - wie der Name es vermuten lässt - ein Signalgeber mit einem Wert, der alle verbundenen Consumer benachrichtigt, wenn sich dieser Wert ändert. Diese Werte können einfache Strings, ein Array oder auch komplexere Datentypen sein. Ändern sich diese Werte, werden automatisch alle Konsumenten informiert und aktualisieren ihre Werte. Im Vergleich zu Observables und RxJS bieten Angular Signals eine intuitivere und klarere Syntax. Dies führt zu einer verbesserten Lesbarkeit und Wartbarkeit des Codes und verbessert nebenbei auch signifikant die Leistung der Angular Anwendung.
Warum Signals leichter zu verstehen sind, was Observables und RxJS sind und inwiefern die Leistung der Angular App verbessert wird, wollen wir euch in diesem Artikel kurz erklären.
Für unser Beispiel betrachten wir einen Onlineshop. Angenommen ihr erhaltet ein Ticket, den Einkaufswagen mit Angular Signals zu programmieren. Es ist gefordert, dass im Einkaufswagen sofort angezeigt wird, wenn jemand ein Produkt in den Warenkorb legt.
Das wichtigste zuerst: Um ein Signal zu erstellen, wird die Funktion signal
aus der Angular-Core Bibliothek genutzt.
import { Component, inject, OnInit, signal } from '@angular/core';
import { data, data2 } from './data';
@Component({
selector: 'signal-warenkorb',
standalone: true,
template: `
<h2>Hier wird ein Signal genutzt </h2>
<div class="container">
<div class="child">
@for(itm of sortiment;track $index){
{{itm}}<button (click)="addItm(itm)">add</button><br>
}</div>
<div class="child">
<ol>
@for(itm of warenkorb();track $index){
<li>{{itm}}</li>
}
</ol>
</div>
</div>
`,
})
export class SignalWarenkorbComponent implements OnInit {
i = 0;
sortiment = data2;
// so definiert man ein signal
warenkorb = signal(['']);
ngOnInit() {
//ein weg um das value eines Signal zu setzen
this.warenkorb.set(data);
}
addItm(newItm: string) {
// ein web um das Value einen Signal upzudaten
this.warenkorb.update((value) => [...value, newItm]);
this.i++;
}
}
In diesem Beispiel verwenden wir in der Komponente ListComponent ein Signal mit dem Namen warenkorb. Das warenkorb-Signal speichert die Listenelemente und wird initial mit data gesetzt. Der “Add”-Button löst die addItm-Methode aus, die wiederum warenkorb reaktiv mit neuen Elementen aus data2 aktualisiert. Eine Aktualisierung wird durch warenkord.update ausgelöst, was das Echtzeit-Update im Template bewirkt.
Kudos an Dave aka Webdave für das Code-Beispiel auf Stackblitz. Hier findest du den gesamten Angular Prototypen.
Producer und Consumer sind zwei grundlegende Konzepte in Angular, die für die Kommunikation zwischen Komponenten verwendet werden. Producer sind für die Erzeugung von Daten verantwortlich, während Consumer diese Daten verarbeiten. Beispiele für Producer sind Functions, Promises, oder Observables. Consumer verarbeiten diese Werte dann weiter. Natürlich können Producer auch gleichzeitig Consumer sein. Signals spielen eine entscheidende Rolle in diesem Muster, indem sie eine vereinfachte Schnittstelle zwischen Datenproduzenten und -konsumenten bieten.
In unserem Code-Beispiel oben können wir ebenfalls zwischen einem Producer und einem Consumer unterscheiden:
Als Producer können wir die addItm-Methode benennen. Sie wird immer dann aufgerufen, wenn Nutzer:innen auf unseren “Add”-Button klicken. Mit ihr erzeugen wir neue Daten. Das ist in unserem Beispiel das Hinzufügen eines neuen Listenelements aus data2.
Der Consumer in diesem Beispiel ist das Template der Komponente, das auf diese Daten reagiert und sie anzeigt. Jedes Mal, wenn das warenkorb-Signal durch die addItm-Methode aktualisiert wird, wird die Liste im Template entsprechend aktualisiert.
Für erfahrene Angular-Entwickler*innen sind diese Konzepte ein alltäglicher Bestandteil ihrer Arbeit. “Observer” und “Observable” sind in der Bibliothek RxJS, die eng mit Angular integriert ist, zentrale Konzepte für die reaktive Programmierung und die Verwaltung von Datenströmen. Observer und Observables sind die reaktiven Äquivalente von Consumer und Producer. Der Observer ist der Consumer, der die Daten vom Observable empfängt. Das Observable ist der Producer, der die Daten an den Observer aussendet. Man spricht in diesem Zusammenhang auch von Subscriptions und Push. Dieses Beziehungsmuster bildet das Herzstück der reaktiven Programmierung in Angular. Zur Veranschaulichung betrachten wir wieder unser Code-Beispiel mit dem Warenkorb in unserem Onlineshop des Vertrauens. Dieses Mal setzen wir das ganze “klassisch” mit Hilfe der Bibliothek RxJS um.
import { AsyncPipe } from '@angular/common';
import { Component, OnInit } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { data, data2 } from './data';
@Component({
selector: 'observable-list',
standalone: true,
imports: [AsyncPipe],
template: `
<hr>
Hier wird ein Signal genutzt <br/>
<button (click)="addItm()">Add</button>
<ul>
@for(itm of list| async;track $index){
<li>{{itm}}</li>
}
</ul>
`,
})
export class ObservableListComponent implements OnInit {
i = 0;
// So definierst du ein Subject
list = new BehaviorSubject(['']);
ngOnInit() {
//So kannst du den value eines Subjects setzen
this.list.next(data);
}
addItm() {
const newItm = data2[this.i];
const newValue = [...this.list.value, newItm];
// Wichtig! Ein Subject kann nicht ubgedatetd werden.
// Es muss neu gesetzt werden.
this.list.next(newValue);
this.i++;
}
}
Auch in diesem Beispiel können Nutzer:innen neue Elemente zu der Liste über den Button “Add” hinzufügen. Bei einem Klick wird erst die addItm-Methode und dann warenkorb.next(newValue) aufgerufen. Jedes Mal, wenn warenkorb einen neuen Wert durch warenkorb.next() bekommt, wird die Anzeige automatisch aktualisiert, um die neuesten Daten zu zeigen. Ein BehaviorSubject ist eine Art von Observable, das zwei Hauptmerkmale hat: Anfänglicher Wert: Ein BehaviorSubject benötigt einen Anfangswert, den es seinen Abonnenten sofort zur Verfügung stellt, wenn sie sich abonnieren. Das bedeutet, dass jeder Abonnent sofort einen Wert erhält, wenn er sich abonniert, im Gegensatz zu normalen Observables, die möglicherweise keinen Wert senden, bis ein Ereignis eintritt. Speicherung des letzten Wertes: Ein BehaviorSubject speichert den letzten emittierten Wert und gibt diesen an jeden neuen Abonnenten weiter. Das ist nützlich, wenn Sie sicherstellen möchten, dass der Abonnent immer den aktuellen Stand eines Wertes hat, auch wenn er sich abonniert, nachdem der Wert emittiert wurde.
Kudos an Dave aka Webdave für das Code-Beispiel auf Stackblitz. Hier findest du den gesamten Angular Prototypen.
Unterscheiden sich dann Angular Signals und Observer/Observables in RxJS überhaupt?
Ein “Observer” ist ein Objekt mit Callbacks, das auf die Werte reagiert, die von einem Observable geliefert werden. Es enthält in der Regel drei Hauptmethoden: next(), error() und complete(). Die next() muss aufgerufen werden, um jeden neuen Wert im Datenstrom zu verarbeiten. Die error() wird aufgerufen, wenn im Datenstrom ein Fehler auftritt, und die complete() signalisiert das Ende des Datenstroms. Bisher musste aber für jedes Observable immer manuell eine Subscription gesetzt und gemanagt werden. Das ist nicht nur zeitintensiv und code-lastig, sondern erfordert auch tiefgreifende Kenntnisse der Web-Entwickler*in in RxJS.
Angular Signals übermitteln ihre Werte (im Gegensatz zu den Datenströmen bei Observables!) automatisch und müssen nicht manuell ein- und abgestellt werden. Das spart nicht nur wertvolle Arbeitszeit, sondern erleichtert auch den Einstieg für angehende Angular Entwickler*innen!
Ist das also der Anfang vom Ende von RxJS in Angular?
Jein!
Für Aufgaben, bei denen es um fortlaufende und asynchrone Datenströme geht, sind Observables in Angular immer noch die erste Wahl. RxJS bietet eine umfangreiche API, mit der Entwickler*innen asynchrone Datenflüsse filtern, transformieren und kombinieren können. Ein Beispiel für asynchrone Ereignisse in Angular ist eine HTTP-Anfrage, die Daten von einem Server holt. Diese Anfrage gibt ein Observable zurück, das irgendwann in der Zukunft ein Ergebnis liefert. Dies macht Observables flexibler und mächtiger in komplexen asynchronen Szenarien gegenüber Angular Signals. In absehbarer Zeit wird sich das wohl auch nicht ändern.
Oder in einfacher Sprache:
Stell dir Observables in Angular wie einen Wasserhahn vor, aus dem Wasser (Daten) in einem kontinuierlichen Fluss kommt. Dieser Wasserfluss kann schnell oder langsam sein, und manchmal kommen auch größere Wassermengen auf einmal heraus – das ist wie bei asynchronen Daten, sie kommen nicht immer alle auf einmal. Angular Signals sind eher wie ein Lichtschalter, der einfach an oder aus ist. Sie sind gut, um einfache Dinge schnell zu erledigen. Wenn du nun kompliziertere Aufgaben hast, bei denen Daten über einen längeren Zeitraum in verschiedenen Mengen ankommen (wie das Wasser aus dem Hahn), sind Observables besser geeignet. Sie können mit diesen “Wasserströmen” besser umgehen und haben Werkzeuge, um das Wasser (Daten) zu filtern oder umzuleiten.
Der andere große Vorteil der Angular Signals ist, dass für die Change Detection nicht mehr der gesamte Komponentenbaum der Anwendung überprüft werden muss. Es werden nur noch direkt die Views aktualisiert, an denen sich etwas geändert hat. Das ist signifikant performativer und ressourcenschonender.
Was heißt das? Um das besser einzuordnen, werden wir die Begriffe Change Detection und View kurz erklären.
Change Detection ist der Mechanismus, der Statusänderungen in der Ansicht unserer Anwendung erkennt und die HMTL-Browseransicht entsprechend aktualisiert. Als Beispiel: Wenn du im Online-Shop deines Vertrauens Produkte in den Warenkorb legst, sorgt die Change Detection Angular dafür, dass die Änderungen auch in deiner HTML-Ansicht angezeigt werden.
Die visuelle Darstellung einer Komponente wird in Angular eine “View” genannt. Angular Views sind hierarchisch aufgebaut. Das bedeutet, dass Sie eine Haupt-View (oder auch Root-View) haben, in die andere Views eingebettet sind. Diese Verschachtelung kann mehrere Ebenen tiefgehen, was es ermöglicht, komplexe Anwendungen aus kleineren, wiederverwendbaren Teilen zu erstellen. Wenn wir in unserem Beispiel mit dem Warenkorb bleiben: Ein Warenkorb besteht aus verschiedenen Elementen. Oft haben wir einen Button in der Navigationsleiste, dann die Ansicht im Main Bereich, die oft aus einer Tabelle, einer Liste, diversen Buttons und anderen Bedienelementen besteht. Jeder dieser sichtbaren Teile kann in Angular eine eigene “View” sein.
Bisher gab es zwei Hauptstrategien für die Change Detection in Angular: Default und OnPush.
In der Default Methode prüft Angular bei jedem Auslöser, ob sich die Daten geändert haben, d.h. bei jeder Änderung wird der gesamte Komponentenbaum der App überprüft. Auslöser können ein Klick oder eine Tastatureingabe - in unserem Beispiel war das der Warenkorb für unseren Online Shop - oder aber HTTP-Anfragen durch HttpClient und Timer wie setTimeout.
Die OnPush-Strategie sagt Angular, dass es nur dann prüfen soll, ob sich Daten geändert haben, wenn sich die Eingabedaten der Komponente (das sind die sogenannten Input Properties) geändert haben. Das hat den Vorteil, dass nur noch die Views aktualisiert werden, bei denen sich etwas geändert hat. Es hat aber den Nachteil, dass für jede Komponente die Change Detection Strategie manuell mit Hilfe von Observables auf On Push gesetzt werden muss.
Verwenden wir nun stattdessen Signals, muss die Change Detection nur noch die Views aktualisieren, die die Liste der Produkte darstellt. Außerdem kann das Signal von mehreren Komponenten abonniert werden. Damit können wir mit geringem Aufwand auch Änderungen für das Warenkorb-Icon im Hauptmenü an die Shop-Besucher kommunizieren.
Oder in einfacher Sprache:
Wir können die Change Detection in Angular anhand eines Briefkastens veranschaulichen. In der Default Change Detection sind wir uns nicht sicher, wann die Post kommt. Daher gehen wir ständig hinaus und überprüfen den Briefkasten, selbst wenn kein neuer Brief angekommen ist. Das ist ineffizient, weil Sie viel Energie und Zeit verbrauchen, nur um festzustellen, dass das nichts Neues ist. Bei der OnPush Change Detection läutet der Briefträger deine Klingel, wenn er einen neuen Brief einwirft. Solange kein neuer Brief kommt, bleibt alles ruhig. Du musst jedoch jedem Briefträger oder Paketdienstleister oder Werbe-Prospektverteiler manuell Bescheid geben, dass er Glocke klingeln muss. Und du weißt nicht, was angekommen ist, solange du nicht zum Briefkasten gehst. In der Angular Signals Change Detection ist dein Briefkasten “intelligent”. Er weiß nicht nur, dass ein Brief eingeworfen wurde, sondern auch, ob es ein Brief, Paket oder Werbung ist. Der Briefkasten sendet dir ein spezifisches Signal, je nach Art des Briefes.
Wir haben in diesem Artikel die wichtigsten Konzepte zu Signals kurz erklärt. Dazu gehören eine kurze Einführung in Signals, die Unterschiede zwischen Signals und Observables, die Vorteile der Signals für die Change Detection und einige Beispiele für die Verwendung von Signals. Angular Signals sind in kürzester Zeit bereits nicht mehr wegzudenken aus dem Alltag für Angular-Entwickler*innen. Ihr großer Vorteil liegt in ihrer einfachen Syntax und verbesserten Performance. Für fortgeschrittene Anwendungsfälle, in denen asynchrone Datenflüsse eine Rolle spielen, bleiben dennoch RxJS Observables weiterhin die erste Wahl.
Für die erfahrenen Angular Entwickler*innen unter euch, haben wir ein paar Fragen: *Nutzt ihr Signals zusammen mit der On Push-Change Detection? *In welchen Fällen findet ihr es besser, Signals statt Observables auch bei asynchronen Datenströmen zu nutzen? *Habt ihr bereits Signals in Kombination mit Promises verwendet?
Wie immer laden wir euch ein, Teil unserer Angular Community auf Discord zu werden. Seit 2013 bieten wir euch hier Tutorials, Artikel und Schulungen rund um das Angular Framework. Gestartet durch unsere Begeisterung für die modernen Möglichkeiten der Webentwicklung hat sich mittlerweile eine ganze Community dazu entwickelt. Mit mittlerweile 18 Meetups, die insgesamt über 10.000 Angular-Entwicklerinnen:innen als Plattform für regelmäßigen Austausch dienen, sind wir damit in Europa die Region mit den meisten Angular-Entwicklerinnen. Werde Teil unserer Community!
How Angular Signals Solves an Age-Old Problem - betterprogramming.pub
Angular’s Signal Revolution: Effortless Change Detection Explained - netbasal.com
Deep dive into the OnPush change detection strategy in Angular - Max Koretskyi
Understating Angular Change Detection with example - DHANANJAY KUMAR
https://www.thinktecture.com/en/angular/whats-the-hype-onpush/
Future of Change Detection in Angular with Signals - Thomas Laforge
Angular hat im Laufe seiner nun zehnjährigen Erfolgsgeschichte die moderne Webentwicklung maßgeblich geprägt. Mit der Veröffentlichung der Version 17 geht das Angular Team wieder einen großen Schritt nach vorne. Wenn man den Umfang der angekündigten neuen Features, das neue Design und den Launch des Portals Angular.dev betrachtet, wird klar, dass das Angular Team hier nicht nur die aktuellen Anforderungen im Blick hat, sondern neue Trends im Front End Development setzen will.
Bereits die Veröffentlichung der Angular Version 16 hatte ihren Fokus ganz klar darauf gelegt, dass Angular „junior-freundlicher” wird. Diese Entwicklung wird in Angular 17 mit dem Launch der Plattform Angular.dev fortgesetzt. Innovationen wie Angular Signals, die Full App Non-Destructive Hydration und Co. waren bisher noch in der „Developer Preview”-Phase. Das bedeutet, dass sie zwar weitgehend stabil sind, aber immer noch Gegenstand von Tests, Feedback und möglichen kleinen Änderungen sind. Mit Angular v17 werden Signals und Co. zu einem festen Bestandteil des Frameworks und finden damit ihren Einzug in die ersten kommerziellen Projekte.
Für uns ist das genau der richtige Zeitpunkt, unsere neue Artikelserie „kurz erklärt” einzuführen. Wir erklären Angular Features in einfacher Sprache, auf Deutsch und möglichst auf den Punkt gebracht. Die Artikel sind gedacht für angehende Webentwickler, Junior Engineers, die noch am Anfang ihrer Karriere stehen, oder Seniors, die schnell mal Begriffe erklären und Zusammenhänge aufzeigen wollen.
In den ersten Artikeln werden wir auf das neue Reactivity-Modell mit Angular Signals und die Full App Non-Destructive Hydration eingehen, damit ihr optimal vorbereitet seid für die Neuerungen in Angular Version 17. Was sind Angular Signals? Worin unterscheidet sich ein Signal von einem Observable? Was bedeutet Full App Non-Destructive Hydration? Was ist ein First Meaningful Paint? Und vieles mehr.
Gerade für junge Webentwickler bedeuten Updates immer eine Doppelbelastung. Es ist nicht leicht, eine solide Basis aufzubauen, wenn regelmäßig Minor- und Major-Releases hinzukommen. Hier stoßen Juniors, die sich noch in der Lernphase befinden, oft an ihre Grenzen. Umso wichtiger ist es da, dass es Inhalte in einfacher Sprache gibt und in denen Grundbegriffe hinter den neuen Features kurz erklärt werden.
Aus unserer Erfahrung bei Workshops wissen wir aber auch, dass ein entscheidender Erfolgsfaktor für den Lernerfolg die aktive Teilnahme in der Angular-Community ist. Während offizielle Dokumentationen und Tutorials wertvolle Ressourcen sind, bietet die Community ihr kollektives praktisches Wissen und Unterstützung. Gerade Einsteiger profitieren enorm von Diskussionen und Code-Beispielen, die von erfahrenen Angular-Entwicklern geteilt werden. Oftmals sind es genau diese Community-Beiträge, die Lösungen für spezifische Probleme oder klare Erklärungen komplexer Konzepte bieten.
Deshalb laden wir euch ein, Teil unserer Angular Community auf Discord zu werden. Seit 2013 bieten wir euch hier Tutorials, Artikel und Schulungen rund um das Angular Framework. Gestartet durch unsere Begeisterung für die modernen Möglichkeiten der Webentwicklung hat sich mittlerweile eine ganze Community dazu entwickelt. Mit mittlerweile 18 Meetups, die insgesamt über 10.000 Angular-Entwickler:innen als Plattform für regelmäßigen Austausch dienen, sind wir damit in Europa die Region mit den meisten Angular-Entwicklern.
]]>Vielleicht hast Du es mitbekommen: Mit Angular 16 wurde das, gemessen am Umfang, größte Update nach dem initialen Release von Angular veröffentlicht. Neben Features wie Non-Destructive Hydration oder dem Support für Typescript 5.0 enthält das Update drei Reactivity Primitives und damit einen Mechanismus für Reaktivität. Sarah Drasnger, Director of Engineering bei Google, spricht auf Twitter bzw. X sogar von einer Angular Renaissance.
Während andere Frameworks bereits länger Reactive Primitives anbieten, ist es bei Angular eine Neuheit. Signals sollen Angular leichtgewichtiger machen und in Zukunft auch ermöglichen, ohne Zone.js zu arbeiten. Mit Signals soll der Angular Change-Detection emöglicht werden, fein granularer zu arbeiten und wirklich nur Neuerungen oder Änderungen zu rendern, anstatt den kompletten Component-Tree überprüfen zu müssen.
Beim aktuellen Ansatz für Change Detection mit Zone.js ist der Trigger für Change Detection, dass sich Zone.js meldet mit der Information “Etwas könnte
sich geändert haben”. Zone.js patched die Browser-APIs und kriegt damit immer, wenn sich irgendwas ändert, was eine Änderung hervorrufen könnte (etwa ein Button-Klick), eine Benachrichtigung. Auf diese Benachrichtigung reagiert Angular mit Change Detection, denn es könnte sich etwas geändert haben. Also wird der gesamte Komponentenbaum überprüft, um zu schauen was aktualisiert werden muss. Der bisherige Ansatz für Change Detection mit Zone.js hat in der Vergangenheit sehr gut funktioniert, er bringt allerdings Nachteile mit sich, u. A. für leichtgewichtige Web Components. Mit der Idee von Signals verlässt Angular diesen Ansatz, indem es präzise weiß, was sich geändert hat und somit den Dirty-Check der ganzen Komponente überspringen kann, um direkt die Views zu aktualisieren, an denen sich etwas geändert hat.
RxJS hat in Angular Anwendungen zwei Kernaufgaben: die Koordination von asynchronen Events, also den Umgang mit Race Conditions und komplexen asynchronen Dataflows, und es ist gleichzeitig auch einfach ein reaktiver Baustein. Für das zweitere bietet Angular mit Signals eine Alternative. Während RxJS schon immer Teil von Angular war, hat sich das Angular Team entschieden mit Signals einen anderen Weg zu gehen als ein bisher bekanntes BehaviourSubject
zu benutzen. Im weiteren Verlauf des Artikels gehe ich noch auf die Abgrenzung zu RxJS ein.
Ein Signal in Angular hält immer einen Wert und Konsumenten können diesen Wert lesen. Ein Signal ist ein zyklischer Prozess und jedes Mal, wenn es seinen Zyklus durchläuft, produziert er eine bestimmte Menge an Informationen.
Ist also eine bestimmte Komponente ein Consumer des Signals, weil es die Informationen benötigt, so wird der Consumer informiert über Änderungen. Wie auch RxJS implementiert ein Signal das Observer-Pattern, setzt dieses technisch allerdings anders um.
Angular unterscheidet bei den Signals zwischen „Writable Signals“ und „Computed Signals“.
Damit Du Signals nutzen kann, brauchst du einen Workspace mit Angular 16. Signals werden als Developer Preview mitgeliefert. Du brauchst aber nichts Zusätzliches tun, um sie zu nutzen.
Writable Signals sind Signale, die direkt modifiziert oder aktualisiert werden können, indem neue Werte emittiert werden. Sie dienen dazu, veränderliche Daten oder Zustandsänderungen in einer Anwendung darzustellen und zu handhaben. Komponenten oder Services können Werte an ein schreibbares Signal senden, indem sie die bereitgestellte API verwenden, wodurch Aktualisierungen an Abonnenten weitergegeben werden. Andere Entitäten wie Komponenten oder Services können ein schreibbares Signal abonnieren, um emittierte Werte zu empfangen und darauf zu reagieren.
Die API eines Writable Signals bietet vier Methoden:
set
setzt einen neuen Wert des Signalsupdate
aktualisiert den Wert des Signals basierend auf dem aktuellen Wertmutate
aktualisiert den Wert des Signals mit einer direkten ÄnderungasReadonly
gibt das Signal als Readonly-Signal zurück, die oben genannten Methoden stehen dann nicht zur Verfügung.Lass uns nun ein Writable Signal und damit unser erstes Signal erstellen.
Schritt 1: Wir importieren die Funktion signal von @angular/core
. Diese Funktion erlaubt es uns, ein Writable Signal zu erstellen. Füge dazu einfach den Import am Anfang der Datei hinzu, in der Du das Computed Signal erstellen möchtest:
import { signal } from "@angular/core"
Schritt 2: Nun können wir das Writable Signal erstellen. Dazu nutzen wir die importierte Funktion signal.
Beim Erstellen eines Writable Signals lässt sich der initiale Wert des Signals mitgeben, ähnlich wie bei der Instanziierung von einem BehaviourSubject
.
// Signals
import { signal } from "@angular/core";
invoices = signal([]);
Schauen wir uns das Beispiel oben an, sehen wir, dass die API von Signals sehr ähnlich der von RxJS ist. Ein erster Unterschied lässt sich erkennen im Zugriff auf den jeweiligen Wert. Während der Zugriff auf Observables / Subjects im Template z. B. über die async-Pipe funktioniert, lässt sich der Zugriff auf Signals über einen einfachen und direkten Funktionsaufruf lösen.
Schritt 3: Wir greifen im Template unserer Komponente auf den Wert des Signals zu.
<!-- Signals -->
<p>Anzahl Rechnungen: {{ invoices().length }}</p>
Du kannst erkennen, dass der Zugriff hier ähnlich, wenn auch anders, funktioniert. Es sei gesagt, dass der Zugriff auf ein BehaviourSubject auch anders funktionieren kann. Wir beschränken uns hier allerdings auf diese Variante.
Nun lass uns dem Signal auch einen Wert geben.
Schritt 4: Wir implementieren eine Methode zum Setzen des Werts des Signals.
addInvoice(invoice: Invoice) {
// Weg 1: Mutate
this.invoices.mutate(invoices => invoices.push(invoice));
// Weg 2: Update
this.invoices.update(invoices => [...invoices, invoice]);
// Weg 3: Set
this.invoices.set([...this.invoices(), invoice]);
}
Alle drei Wege fügen die Rechnung zur Liste hinzu. Die Methoden mutate
und update
bieten sich an, wenn der neue Wert vom aktuellen Wert abhängt. update
ermöglicht es, mit Immutables zu arbeiten (z. B. für Performance-Gründe).
Computed Signals sind Signale, die ihre Werte aus einem oder mehreren Eingabe-Signalen ableiten, indem eine definierte Berechnungs- oder Transformationslogik angewendet wird. Computed Signals werden verwendet, um berechnete oder abgeleitete Daten basierend auf vorhandenen Signalen zu erzeugen, anstatt die Signale selbst direkt zu modifizieren. Computed Signals verwenden eine Berechnungsfunktion oder -logik, die Eingabe-Signale als Parameter erhält und einen Ausgabewert erzeugt. Die Berechnungsfunktion wird ausgeführt, wenn sich die Eingabe-Signale ändern. Computed Signals sind intelligent optimiert und werden nur dann neu berechnet, wenn dies erforderlich ist, d.h., wenn sich die Eingabe-Signale ändern. Dies gewährleistet Effizienz und vermeidet unnötige Neuberechnungen.
Bei der Erstellung von Computed Signals unterscheidet sich der Ansatz stark vom RxJS-Ansatz. Während man in RxJS die Berechnung eines Werts mit einer pipe und unterschiedlichen RxJS-Operatoren durchführt, funktioniert es bei Signals mit einer Funktion computed
, die dann entsprechend Wert berechnet.
Schritt 1: Wir importieren signal und computed von @angular/core. Diese Funktionen erlauben es uns, Writable und Computed Signals zu erstellen. Füge dazu einfach den Import am Anfang der Datei hinzu, in der Du das Computed Signal erstellen möchtest:
import { signal, computed } from '@angular/core;
Schritt 2: Nun können wir das Computed Signal erstellen. Dazu nutzen wir die importierte Funktion computed und
import { signal, computed } from '@angular/core;
const pageCount = computed(() => Math.ceil(this.invoiceCount() / 5));
Hurra! 🎉 Du hast ein Computed Signal angelegt. Der Zugriff auf das Signal funktioniert analog zu dem eines Writable Signals.
Schauen wir uns nun an, wie sich Werte aus mehreren Signals ableiten lassen. Dazu erstellen wir wieder ein Computed Signal.
// Werte, aus denen wir ableiten wollen
const permissions = signal(["create_invoice", "delete_invoices"]);
const isAuthenticated = signal(false);
// Abgeleiteter Wert
const canDeleteInvoice = computed(() => permissions().includes("delete_invoices") && isAuthenticated());
Das Beispiel zeigt das Signal canDeleteInvoice, welches seinen Wert von den beiden Signals permissions
und isAuthenticated
ableitet. Immer, wenn sich der Wert für permissions
oder isAuthenticated
ändert, weiß Angular, dass alles was aus diesen Signals ableitet auch aktualisiert werden muss.
Das Pendant zum obigen Beispiel wäre mit RxJS in etwa folgendes:
const permissions$ = new BehaviourSubject(["create_invoice", "delete_invoices"]);
const isAuthenticated$ = new BehaviourSubject(false);
const canDeleteInvoice$ = combineLatest([permissions$, isAuthenticated$]).pipe(
map(([permissions, isAuthenticated]) => permissions.includes("delete_invoices") && isAuthenticated)
);
Würden sich also mit diesem Ansatz die Streams permissions$
und isAuthenticated$
aktualiseren, so kann es durchaus passieren, dass unerwartete Ergebnisse herauskommen: Angenommen der Benutzer wird angemeldet (isAuthenticated$ wird true
emitten) und die Berechtigung “createInvoice” wird entzogen zur gleichen Zeit. Das würde kurzzeitig dazu führen, dass canDeleteInvoices$
true emitted bevor es dann mit dem Emit von permissions$
wieder false
wird. Man nennt das auch das Diamond-Problem.
Effects sind Operationen, die immer dann ausgeführt werden, wenn sich ein oder mehrere Signalwerte ändern. Ähnlich wie berechnete Signale verfolgen Effekte ihre Abhängigkeiten dynamisch. Sie wissen also, von welchen Signalen sie abhängen. Lass uns nun einen Effect erstellen.
Schritt 1: Wir importieren die Funktion effect von @angular/core.
import { effect } from '@angular/core';
Schritt 2: Wir erstellen den Effect.
@Component({ ... })
export class MyComponent {
effect(() => console.log(`Aktueller Login-Status: ${ isAuthenticated() }`);
}
Nachdem der Effect einmal ausgeführt wurde mit dem initialen Wert von isAuthenticated
, wird er nun immer dann einen Log in der Konsole erstellen, wenn sich der Wert von isAuthenticated ändert.
Um Signals und RxJS einordnen zu können, müssen wir ein wenig ausholen.
Bei deklarativer Programmierung geht es darum, zu sagen, was getan werden muss, und bei der imperativen Programmierung geht es darum, zu sagen, wie es getan werden muss. Reaktives Programmieren mit RxJS lässt sich der deklarativen Programmierung zuordnen.
Die meisten Entwickler beginnen mit imperativer Programmierung weil es allgemein intuitiver ist und meistens auch bekannt. Reaktive deklarative Programmierung erfordert oft eine komplette Änderung der Denkweise und das Erlernen von neuen Konzepten und Patterns, wie zum Beispiel Observables. Mit Signals wird es möglich diese beiden Paradigmen zu vereinen. Signals funktionieren reaktiv imperativ. Sobald Entwickler die Grundkonzepte von Reaktivität mit Angular verinnerlicht haben, ist es einfach möglich die Reaktivität auf ein deklaratives Level zu haben. Die sehr steile Lernkurve wird damit ein wenig abgeflacht, weil der Start mit imperativer Programmierung damit intuitiver ist.
Um von reaktiver imperativer Programmierung mit Sigals zu reaktiver deklarativer Programmierung zu wechseln bietet Angular auch entsprechende Funktionen: fromObservable
und fromSignal
erlauben es, Observables zu Signals bzw. Signals zu Observables zu transformieren.
Demnächst wird es sogar möglich sein, Signal-based Inputs zu nutzen (Hier geht’s zum RFC). Mithilfe der Input Transforms, die mit Angular 16.1 veröffentlicht wurden, lassen sich Inputs ganz einfach als Signals benutzen.
@Component({
signals: true,
selector: 'invoices-overview',
template: '<p>Client: {{ clientName() }}</p>',
})
export class InvoicesOverview {
clientName = input<string>(); // Signal<string|undefined>
}
Die Property clientName
ist vom Typ Signal<string|undefined>
und beinhaltet ein Readonly Signal. Falls Du einen initialen Wert setzen möchtest, kannst Du dies auch machen: input<string>('Unbekannt')
. Das würde dann dazu führen, dass das Signal vom Typ Signal<string>
ist und auch initial einen string-Wert (‘Unbekannt’) hat.
Aktuell sind Signals zwar noch in der Developer Preview aber im Ganzen lässt sich erkennen, dass Angular mit Signals den Weg in eine Zukunft ohne Zone.js geht. Dazu wird die Arbeit mit Signals allerdings nicht unbedingt notwendig. Man kann sie eher als eine Möglichkeit betrachten, deklarativ reaktiv zu programmieren.
]]>Nach zwei Jahren coronabedingter Pause konnte die NG-DE Angular Community Konferenz 2022 endlich stattfinden. Angular-Fans, Speaker:innen, Trainer:innen und Neulinge trafen sich im Oktober 2022 für drei Tage voller Angular-Talks, Workshops und Meetups in Berlin. Das Event war ein voller Erfolg, den wir hier noch einmal zusammenfassen möchten.
Eine Angular Konferenz für den deutschsprachigen Raum zu veranstalten, bedarf einiger Vorbereitung. Dafür hat sich Organisator Robin Böhm ein Team aus insgesamt acht Freiwilligen zusammengestellt, die gemeinsam neun Monate lang daran arbeiteten, die Konferenz vom 5. bis 7. Oktober in den Bolle Festsälen in Berlin auf die Beine zu stellen. Neben Robin halfen Carina Rettig, Joe Ray Gregory, Sarah Peraste, David Müllerchen, Antony Konstantinidis, Maria Korneeva und Martina Kraus dabei, die ehrgeizigen Pläne umzusetzen und der Angular-Community ein unvergleichliches Event zu bieten.
Als Sponsoren, ohne die die Konferenz nicht möglich gewesen wäre, konnten E.ON, storyblok, safetyio, SUM.CUMO SAPIENS, auth0, adesso, Ma-Tea und celonis gewonnen werden. Nachdem die Grundvoraussetzungen stimmten, ging es für das Orga-Team dann an den Call for Papers.
Diesen gestalteten sie in Form eines Blind Votings. Alle eingereichten Talks wurden anonymisiert, bevor die Jury sie zu Gesicht bekam, um jeglichen Bias gegenüber den Speaker:innen zu verhindern. Das Ergebnis: ein wunderbar diverses und abwechslungsreiches LineUp.
Anfang Oktober war es schließlich so weit und die Angular Community Konferenz startete mit einem Tag voller Workshops. Am 6. und 7. Oktober fand dann das Main Event statt, bei dem Hunderte Teilnehmer:innen gespannt den Talks lauschten, sich über die neuesten Angular-Themen austauschten und neue Kontakte knüpften. Um dir einen besseren Überblick, lassen wir mal kurz die Zahlen sprechen:
Bei den sechs Workshops am 5. Oktober gingen die Speaker:innen ordentlich ins Detail und gaben ihr geballtes Wissen an die Teilnehmer:innen weiter. Die Themen der Workshops waren unter anderen RxJS, Cypress, Desktop-Class Productivity Apps und NgRx.
Bei den Talks verzauberten die 16 Speaker:innen die Teilnehmer:innen mit perfekt vorbereiteten Vorträgen zu ihren Lieblings-Angular-Themen. Für alle, die bei der Konferenz nicht dabei sein konnten, gibt es jetzt gute Nachrichten. Alle Talks wurden aufgezeichnet und stehen dir kostenlos auf dem NG-DE YouTube-Channel zur Verfügung!
Das NG-DE-Team bedankt sich bei allen Helfer:innen, Teilnehmer:innen, Speaker:innen und Workshop-Leiter:innen für das unvergessliche Event. Wir freuen uns schon riesig auf die nächste Konferenz von der Community für die Community!
]]>Angular 15 ist da und es wurden wieder viele neue Features hinzugefügt.
Wie an der Version schon zu sehen, gibt es auch Breaking changes. Diesmal seit langem mal wieder eines welches manuelles eingreifen erfordert.
Aber der Reihe nach.
Angular unterstützt jetzt Node.js in den folgenden Versionen 14.20.x, 16.13.x und 18.10.x. Alle früheren Versionen werden nicht mehr unterstützt. Grund hierfür ist u.a. EOL.
In der CLI wurder der build prozess optimiert. Der prozess wurde von Webpack und ESBuild auf ESBuild verkleinert. Dadurch konnte die build Zeit um 57% veringert werden.
Angular 15 setzt auf Typescript 4.8. was einige Änderungen und neue Features mitbringt. Damit kommt der BreakingChange der manuellen eingriff erfordert. Alle Ändrungen findet ihr hier
Interessant ist dabei aber eine Änderung die mit Version 3.7 schon kam. Es geht hier um die art wie Klassen attribute definiert werden. Typesscript ist beim initialen implementieren von einer inoffiziellen lösung ausgegangen. Warum interessiert uns das? Weil diese implementierung vom dependency Framework innerhalb von Angular genutzt wird. Es gint zwar ein Flag um die Alte implementierung beizubehalten, diese wird auch bei Angular 15 per default gesetzt, aber damit entfernt man sich vom ES2022 standard. Genaueres dazu findet ihr in der Offizielen releasenote dazu.
Wie sieht jetzt aber die korrekte imolementierung von DI in Angular 15 aus?
Das ganze wird durch eine schon aus Angular 14 bekannte funktion gelöst. inject
Dependency Injection bisher sah so aus:
@Component({
selector: "my-app",
template: `{{ service.data$ | async }}`,
})
export class AppComponent {
constructor(public service: myService) {}
}
Ab Angular 15 ist dieser Weg ein möglich (und meiner Meinung nach auch schöner)
@Component({
selector: "my-app",
template: `{{ data$ | async }}`,
})
export class AppComponent {
data$ = inject(MyService).data$;
}
Ein weniger aufregendes Feature (weil schon etwas länger bekannt) sind Standalone Components.
Diese sind nun Stable in Angular 15.
Damit werden @NgModule
optional. Noch ist es nicht möglich ein Projekt komplett ohne @NgModule
zu generieren, der entsprechende generator wird vorraussuchtlich mit 15.x nachgeliefert.
Nachfolgend ein kurzes Beispiel.
Wir sehen, dass die NgModule Logik in die Component verschoben wurde. Standalone Components werden daher nicht declariert sondern importiert wie man am Beispiel sieht.
@Component({
selector: "app-root",
standalone: true,
imports: [CommonModule, MyOtherComponent, TopNavComponent],
providers: [],
template: `
<top-nav></top-nav>
<other-component></other-component>
`,
})
export class AppComponent {}
Der Boostrap prozess ist auch leicht angepasst.
import { bootstrapApplication } from "@angular/platform-browser";
bootstrapApplication(AppComponent).catch((err) => console.error(err));
Aber Components sind nicht das einzige was Standalone möglich ist.
Das bringt uns direkt zum Router. Dieser kann jetzt auch ohne die Verwendung von NgModule verwendet werden.
Dafür wird der Router beim bootstraping als Provider übergeben. Auch die schon vertrauten Optionen die sonst der forRoot
übergeben wurde sind weiter verfügbar.
export const lazyRoutes: Routes = [{ path: "", component: PrivateComponent }];
const aboutRoutes: Routes = [
{
path: "about",
component: AboutComponent,
},
{
path: "private",
loadChildren: () =>
import("./feature/private").then((routes) => routes.lazyRoutes),
},
];
bootstrapApplication(AppComponent, {
providers: [
provideRouter(
aboutRoutes,
withDebugTracing(),
withPreloading(PreloadAllModules)
),
],
}).catch((err) => console.error(err));
DIe Guards sind ab sofort als Funktionen zu verfügbar, Klassen bassierte Guards werden vorraussichtlich in v16 deprecated. Diese Änderung kam als request aus der community. Damit soll der Einstieg in Angular leichter gelingen
Sah bisher ein Guard so aus:
@Injectable({ providedIn: "root" })
export class MyGuard implements CanActivate {
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): boolean {
return true;
}
}
const aboutRoutes: Routes = [
{
path: "about",
component: AboutComponent,
canActivate: [MyGuard],
},
];
So können wir nun Guard Funktionalität in Funktionen verpacken. Auch haben wir hier die möglichtkeit die inject funktion zu nutzen.
export const myGuard: CanActivateFn = (
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): boolean => {
return true;
};
const aboutRoutes: Routes = [
{
path: "about",
component: AboutComponent,
canActivate: [myGuard],
},
];
Der HttpClient war schon immer ein Injectable. Wir können auch hier jetzt ohne @NgModule
auskommen, evtl. Interceptoren werden über eine Factory mathode übergeben.
bootstrapApplication(AppComponent, {
providers: [provideHttpClient(withInterceptors([]))],
}).catch((err) => console.error(err));
Ich habe mich sehr über diese Entwicklung gefreut. Und wer schon lange genug dabei ist wird evtl auch ein dejavu haben.
Manchmal gibt es den Wunsch, dass man eine Component definiert die aber von mehreren Klassen erbt. Sowas ist in Typescript nicht vorgesehen. Mit der Directive Composition API können wir mehrere Directives zu einer Directive zusammen führen und behalten zugriff auf die puplic API der einzelnen Directives. Hier ein Beispiel:
@Component({
selector: "mat-menu",
hostDirectives: [
CdkTooltip,
{
directive: CdkMenu,
inputs: ["cdkMenuDisabled: disabled"],
outputs: ["cdkMenuClosed: closed"],
},
],
})
class MatMenu {}
Ich habe mir die Feature in meinem Livestream angesehen und ausprobiert. Es fühlt sich alles sehr gut an. Das Video ist auf meinem Youtube Channel verfügbar. https://www.youtube.com/@webdave_de
Wenn du magst, sei gerne bei meinem Stream dabei, Ich beschäftige mich mit Webtechnologien und streame immer Dienstags 20:00 auf Twitch. https://webdave.tv
]]>Dieses Tutorial erklärt euch die Grundlagen des Frameworks Angular. Wir behandeln hierbei Angular in der Version 2 und höher. Bewusst wird hierbei aber die Versionsnummer weggelassen, da das Framework nun semantische Versionierung benutzt. Kurz gesagt: Es ist einfach Angular.
Diese Einführung ist für Anfänger gedacht, die gerade mit Angular beginnen. Das Beispiel orientiert sich an den ersten Aufgaben unserer Workshop-Inhalte der Angular Intensiv Schulung und unserer Angular Lifetime Schulung.
Unsere Didaktik behandelt dabei die Motivation, die Theorie und dann den Praxis-Teil. Ihr könnt hierbei alle Aufgaben selber programmieren und über unseren kostenlosen Workshops.DE Lifetime Classroom zum Angular Tutorial mit Videos von Hilfestellungen und Musterlösungen für die Aufgaben erhalten.
Zum kostenlosen Angular Tutorial Video Kurs
Dieses Tutorial zeigt dir die grundlegenden Bestandteile einer Angular-Anwendung anhand eines praktischen Beispiels, welches du selber implementieren oder mit fertigen Musterlösungen nutzen und verändern kannst.
Wir werden hierbei folgende Themen behandeln:
Wir werden hierbei die Motivation und den theoretischen Background kurz einleiten, uns jedoch primär auf praktische Beispiele konzentrieren. Wir werden eine kleine Anwendung bauen, welche uns eine Liste von Daten von einer REST-API ausliest und diese anzeigt.
Angular ist ein sehr erfolgreiches, clientseitiges JavaScript-Web-Framework zur Erstellung von Single-Page-Webanwendungen. Es reiht sich neben den anderen großen Frameworks für Single Page Applications ein. Wobei das nicht ganz stimmt, da Angular sich mittlerweile sogar eher zur Plattform weiterentwickelt hat. Es beinhaltet neben der reinen “API” zur Anwendungsentwicklung mittlerweile auch Entwicklungs-Werkzeuge, Generatoren und mitgelieferte Architektur-Konzepte und stellt somit eine Ready-to-Rock Lösung um Enterprise-Anwendungen zu entwickeln dar. Es reiht sich neben den beiden anderen erfolgreichen Frontend Frameworks React und VueJS ein.
Alle drei Bibliotheken, beziehungsweise Frameworks, haben ihre Daseinsberechtigung, Stärken und Schwächen. Je nach Use-Case sollte hier entschieden werden, welche der Alternativen die beste Basis für das aktuelle Projekt liefert.
Angular zielt hierbei ganz klar auf die professionelle Entwicklung von Enterprise Software ab. Durch klare Vorgaben in der Struktur und den Einsatz von Generatoren können langfristig wartbare und skalierbare Softwarelösungen erstellt werden. Konzepte wie Dependency Injection und ein Fokus auf TDD sind seit der ersten Stunde von Angular im Core verankert. Durch die klare Struktur von Projekten ist hierbei explizit die Skalierbarkeit von neuen Entwickler:innen hervorzuheben. Durch dieses massive Grundgerüst wirkt Angular auf den ersten Blick oft etwas schwergewichtig - überzeugt jedoch in Production durch systematische Optimierungen und Erweiterbarkeit.
ReactJS zielt hierbei eher auf einen sehr minimalen Layer auf Komponenten-Ebene ab und ermöglicht/erfordert das Konzipieren einer eigenen Architektur von Grund auf. Das bietet sehr flexible Möglichkeiten, um für individuelle Problemstellungen sehr explizite Lösungen zu bauen. Es gibt eine Auswahl an verschiedensten Modulen für die verschiedenen Anforderungen. Der Aufwand der Integration und Pflege ist hier höher als in Angular, allerdings ist das Projekt dadurch oftmals auch simpler und sehr leichtgewichtig.
VueJS bedient die Anforderungen zwischen diesen beiden Frameworks. Indem das Framework auf einen Generator und klare Strukturen setzt, begünstigt es ebenfalls die Skalierung von Projekt-Teams. Allerdings versucht VueJS gleichzeitig sehr leichtgewichtig zu bleiben und möglichst wenig “Framework-Magic” einzubringen. Es ist also die simple, aber strukturierte Mittellösung.
Dies ist meine persönliche Einschätzung und ich habe bereits sehr gut mit allen diesen Frameworks gearbeitet. Es kommt individuell auf die Problemstellung und das Team an. Falls ihr gerade neu im Bereich Web seid, kann ich euch auch sehr unseren Moderne Webentwicklung und Frontend-Architekur Kurs empfehlen, welcher euch einen Überblick über die moderne Webentwicklung von heute aufzeigt.
Angular selbst hat die Ursprünge in 2009, im “wilden Westen” der Webanwendungsentwicklung. Seitdem ist viel passiert - keine Angst, ich werde jetzt hier keine Geschichtsstunde starten. Es geht eher um diesen Punkt: Wie konnte sich Angular in der wilden Welt von JavaScript Frameworks, in der gefühlt jeden Tag 10 neue Frameworks erscheinen, trotzdem als eines der erfolgreichsten Frameworks beweisen? Dies lässt sich wahrscheinlich am einfachsten mit der Mission von Angular beschreiben:
Durch diese Mission ist ein wunderbares Ökosystem mit einer wahnsinnig tollen Community entstanden. Dabei ist aber der Fokus auf Qualität und Enterprise ebenfalls klar zu spüren. Google selbst nutzt nach eigenen Angaben Angular in über 1600 Projekten. (Google Teams nutzen übrigens AUCH React und VueJS für Projekte, wo dieser Stack besser passt).
In 2016 hat sich das Angular-Team für einen kompletten Rewrite in TypeScript entschieden. Damals wurde die Entscheidung größtenteils negativ wahrgenommen und von anderen Framework-Benutzern zerrissen.
Heute sehen wir die Weitsicht dieser Entscheidungen, da mittlerweile viele andere Frameworks ebenfalls auf TypeScript setzen. Um Breaking Changes einfacher kommunizieren zu können, hat sich das Team ebenfalls für einen fixen Release-Plan entschieden. So können Projektteams Budgets für Updates bereits im Voraus einplanen und werden nicht von Breaking-Changes in einem Release “überrascht”.
Wir bieten Schulungen zum Einstieg in Angular. Ideal sind dafür unsere Angular & TypeScript Schulungen als auch Angular Lifetime Schulungen um dich möglichst effektiv in das Thema Angular zu begleiten. Im Kurs kannst Du die Fragen stellen, die Du nur schlecht googlen kannst, z.B. “Besserer Weg, um meine Applikation zu strukturieren”. Wir können sie Dir beantworten.
Das Ökosystem von Angular ist sehr groß. Die Basis bildet hierbei das Core-Framework. Hier sind die fundamentalen Konzepte implementiert, die für moderne Webanwendungen essenziell sind. Zwei weitere Core-Konzepte, die jedoch separat nutzbar sind, sind die Angular-CLI und die Verwaltung von Komponenten. Diese bilden die Kernfunktionalitäten, welche in fast jeder Anwendung benötigt werden. Weitere Module lassen sich optional einbinden, falls ihr diese benötigt:
In diesem Tutorial werden wir uns primär um das Framework, die Angular CLI und Komponenten kümmern.
Beginnen wir nun mit der Installation von NodeJS. NodeJS ist die sogenannte “JavaScript Runtime” und dafür zuständig, Programme auf unserem Rechner auszuführen, welche in der Sprache JavaScript geschrieben sind, wie z.B. das Command-Line-Interface von Angular, welches wir gleich nutzen werden.
Ihr könnt NodeJS über folgenden Link herunterladen und installieren: https://nodejs.org/download/
Mit NodeJS wird ebenfalls das Kommandozeilenwerkzeug npm
installiert, welches uns ermöglicht, weitere NodeJS Pakete auf unserem Rechner zu installieren.
Nachdem ihr die Installation erfolgreich abgeschlossen habt, könnt ihr nun über euren Terminal folgenden Befehl ausführen:
npm i -g @angular/cli bookmonkey-api
Dieser Befehl installiert die Angular-CLI
global auf eurem Rechner und ermöglicht euch somit nach der Installation mit dem Kommandozeilenwerkzeug ng
zu arbeiten. Als zweites Paket wird das Paket bookmonkey-api
installiert, welches uns als simulierter Backend-Server in unserem Beispiel dient.
Die Angular-CLI wird genutzt, um neue Strukturen innerhalb unserer Anwendungen zu generieren, anstatt wie oft in Projekten die Basis-Strukturen zu kopieren und über potenzielle Fehler bei der Umbenennung zu stolpern. Es ist ein mächtiges Werkzeug, welches euch mit ng --help
einen ausführlichen Hilfetext anbietet.
Um unsere erste Anwendung zu generieren, verwenden wir den new
command, welcher als Argument den Namen eurer Anwendung entgegennimmt. Hierbei werdet ihr gefragt, ob ihr das Routing Module
installieren wollt: Nein. Weiterhin, welches Stylesheet Format ihr nutzen wollt: Hierbei wählt ihr bitte SCSS.
$ ng new angular-de-tutorial --strict false
? Do you want to enforce stricter type checking and stricter bundle budgets in the workspace?
This setting helps improve maintainability and catch bugs ahead of time.
For more information, see https://angular.io/strict
No
? Would you like to add Angular routing?
No
? Which stylesheet format would you like to use?
SCSS
In diesem Tutorial verzichten wir auf den Strict Mode von Angular und TypeScript, damit wir vollen Fokus auf die Angular Features legen können.
Wenn du die Angular CLI später verwendest um Code zu erzeugen, oder das Projekt auszuführen, stellt die CLI die Frage, ob du deine Nutzungsdaten anonymisiert zur Verfügung stellen möchtest, um die Angular CLI zu verbessern.
? Would you like to share anonymous usage data about this project with the Angular Team at
Google under Google’s Privacy Policy at https://policies.google.com/privacy? For more
details and how to change this setting, see http://angular.io/analytics. Yes|No
Nun werden automatisch die Projektstrukturen für euch angelegt. Dies inkludiert eine Startseite, eine Komponente, die ersten End2End Tests, Linter-Regeln, GitIgnore-Regeln und eine TypeScript Konfiguration.
CREATE angular-de-tutorial/angular.json (3809 bytes)
CREATE angular-de-tutorial/package.json (1209 bytes)
CREATE angular-de-tutorial/README.md (1026 bytes)
CREATE angular-de-tutorial/.editorconfig (274 bytes)
CREATE angular-de-tutorial/.gitignore (631 bytes)
CREATE angular-de-tutorial/tsconfig.json (737 bytes)
CREATE angular-de-tutorial/tslint.json (3185 bytes)
CREATE angular-de-tutorial/.browserslistrc (703 bytes)
...
CREATE angular-de-tutorial/src/app/app.module.ts (314 bytes)
CREATE angular-de-tutorial/src/app/app.component.scss (0 bytes)
CREATE angular-de-tutorial/src/app/app.component.html (25725 bytes)
CREATE angular-de-tutorial/src/app/app.component.spec.ts (979 bytes)
CREATE angular-de-tutorial/src/app/app.component.ts (224 bytes)
...
CREATE angular-de-tutorial/e2e/protractor.conf.js (904 bytes)
CREATE angular-de-tutorial/e2e/tsconfig.json (274 bytes)
CREATE angular-de-tutorial/e2e/src/app.e2e-spec.ts (670 bytes)
CREATE angular-de-tutorial/e2e/src/app.po.ts (274 bytes)
Nach dem Generieren werden ebenfalls notwendige Pakete via npm
installiert. Dies kann durchaus einige Minuten dauern. Ist die Installation abgeschlossen, könnt ihr die Entwicklungsumgebung starten.
$ cd angular-de-tutorial
$ ng serve
Angular Live Development Server is listening on localhost:4200
Eure Basisanwendung ist nun generiert und kann im Browser unter http://localhost:4200 aufgerufen werden. Ihr solltet ein ähnliches Bild wie folgendes sehen:
In Angular gibt es zwei primäre Bestandteile des Frameworks, mit welchen wir uns zuerst auseinandersetzen.
Komponenten sind Anzeigeelemente. Sie werden als eigene HTML-Elemente definiert. Abhängig der definierten Anzeige-Logik und den aktuellen Daten stellen diese Elemente den Zustand der Anwendung dar.
Services sind unabhängig von der Anzeige eurer Anwendung. Sie definieren Daten, Logik und Algorithmen der Anwendung. Sie sind modular und wiederverwendbar.
Angular Komponenten sind die sogenannten “building blocks” jeder Anwendung. Die verschiedenen logischen Bausteine einer Anwendung werden also in Komponenten aufgeteilt. Jeder dieser Komponenten übernimmt dabei eine bestimmte Funktion und wird als eigenes HTML-Element definiert.
<todo-title>ToDo App</todo-title>
<todo-list>
<todo-item state="checked">Prepare Workshop</todo-item>
<todo-item>Hold the Workshop</todo-item>
</todo-list>
Wie ihr in diesem kleinen Beispiel einer ToDo-Liste seht, gibt es für die verschiedenen Bereiche eigene Elemente, die in diesem Fall mit dem Prefix todo-
eingeleitet werden. Wie ihr gut an der todo-list
erkennt, ist es möglich und auch absolut üblich, eigene Komponenten ineinander zu verschachteln. Ziel ist es, immer wiederverwendbare und wartbare Elemente zu bauen. Was hierbei die richtige Komponentengröße ist, werdet ihr in euren Projekten selber entscheiden müssen und mit wachsender Erfahrung ein immer besseres Gefühl dafür bekommen. Bei Unsicherheit könnt ihr euch aber auch jederzeit in unserem Discord bei uns melden.
Für Daten und Logik, die nicht zwingend nur an eine Komponente gekoppelt sind, werden in Angular Services genutzt. Ein Service ist eine Klasse, welche Attribute und Methoden definiert, die von Komponenten und anderen Services genutzt werden können.
export class TodoService {
data = [
{
title: "Prepare Workshop",
state: "checked",
},
{
title: "Hold the Workshop",
},
];
}
Die eigentlichen Daten werden also aus einem Service referenziert, denn gegebenenfalls werden auf Basis der aktuellen To-dos auch noch andere Komponenten angezeigt, wie z.B. eine Komponente, welche die aktuell offenen To-dos zählt.
Als erste Übersicht soll dies an dieser Stelle reichen. Wir werden uns später Services noch einmal genauer ansehen.
Wenn wir uns nun die Komponenten-Definition anschauen, kommen wir das erste Mal mit TypeScript in Berührung. TypeScript ist eine Erweiterung von JavaScript, welche uns die Möglichkeit bietet, die Daten unserer Anwendung explizit zu typisieren. Weiterhin führt diese Meta-Sprache auch Features ein, die es in JavaScript (noch) nicht gibt, wie Decorators
. TypeScript “transpiled” unseren geschriebenen Quellcode, sodass der Browser nachher wieder ganz normales JavaScript sieht und interpretieren kann. Es ist also ein Feature, welches uns als Entwickler:innen die tägliche Arbeit angenehmer macht.
Klassen wurden in ES2015 eingeführt, um Konzepte wie Vererbung und Konstruktoren nicht mehr über Prototypen abbilden zu müssen. Diese können nun über eine einfache und saubere Syntax erstellt werden.
Decorator sind strukturierte Metadaten einer Klasse. Ihr kennt diese vielleicht aus anderen Programmiersprachen wie z.B. Java. Das eigentliche fachliche Verhalten der Komponente bilden wir innerhalb der Klasse mit Methoden ab.
Eine Komponenten-Definition besteht primär aus folgenden Bestandteilen:
Unsere erste Komponente wird eine statische Infobox sein. Um diese zu generieren, nutzen wir wieder die Angular-CLI.
Ihr könnt hierzu einen neuen Terminal öffnen oder den laufenden ng serve
kurzzeitig stoppen.
Der Serve-Prozess erkennt aber automatisch Veränderungen innerhalb eures Quellcodes und kompiliert die jeweils aktuelle Version ihrer Anwendung in wenigen Sekunden.
Ich würde euch also empfehlen, ein zweites Terminal zu öffnen und folgenden Befehl zu benutzen:
$ ng generate component info-box
CREATE src/app/info-box/info-box.component.scss (0 bytes)
CREATE src/app/info-box/info-box.component.html (23 bytes)
CREATE src/app/info-box/info-box.component.spec.ts (636 bytes)
CREATE src/app/info-box/info-box.component.ts (277 bytes)
UPDATE src/app/app.module.ts (0 bytes)
Die für uns aktuell relevanten Dateien sind zur Zeit die info-box.component.ts
und unser Template info-box.component.html
. Schauen wir uns zunächst einmal unsere Klasse an.
@Component({
selector: "app-info-box",
templateUrl: "./info-box.component.html",
styleUrls: ["./info-box.component.scss"],
})
export class InfoBoxComponent implements OnInit {
constructor() {}
ngOnInit() {}
}
Hier sehen wir wie erwartet eine Komponente. Unser Selektor hat den automatischen Prefix app-
bekommen. Somit ist unsere neue Komponente nun unter dem HTML-Tag <app-info-box></app-info-box>
nutzbar. Der Einstiegspunkt unserer kompletten Anwendung ist ebenfalls eine Komponente mit dem Namen AppComponent
.
Um unsere frisch generierte Komponente anzuzeigen, müssen wir diese in dem Template unserer Anwendung aufrufen. Hierzu geht ihr in die Datei app.component.html
, löscht dort den kompletten derzeitigen Inhalt und fügt eure Komponente via HTML-Tag ein.
<app-info-box></app-info-box>
Wenn ihr nun eure Anwendung wieder im Browser öffnet, solltet ihr die Ausgabe info-box works!
sehen.
Ihr könnt an dieser Stelle gerne mit eurem Template in info-box.component.html
etwas herumspielen und auch mehrere dieser Info-Boxen erzeugen, indem ihr den HTML-Tag in eurem App-Template einfach kopiert.
Ein historischer Moment – nehmt euch ein paar Sekunden Zeit, um eure erste eigene Komponente zu bewundern. 😉
Eine Komponente mit statischen Inhalten ist natürlich nur sehr begrenzt in einer Anwendung nutzbar. Um variable Daten anzuzeigen, nutzt Angular sogenannte Expressions in den Templates. Diese werden mit doppelten geschweiften Klammern eingeleitet und auch wieder geschlossen.
{{ expression }}
Eine Expression wird von Angular dynamisch auf Basis der aktuellen Properties eurer Klasse ausgewertet.
Führen wir also ein neues Property text
ein und füllen dieses mit einem String, können wir diesen in unserem Template ausgeben.
class InfoBoxComponent implements OnInit {
text = "Additional Info-Text on our Info Box! 🎊";
constructor() {}
ngOnInit() {}
}
<p>info-box works!</p>
<p>{{text}}</p>
Sollte sich die Property text
ändern, z. B. durch externe Events, wird diese automatisch von Angular aktualisiert. Dieses Konzept nennt sich Data-Binding
.
Andere Komponenten können über sogenannte Property- und Event-Bindings eingebunden werden. Angular verbindet sich hierbei mit den Eigenschaften und Events der nativen HTML-Elemente. Somit ist auch das Benutzen von anderen Elementen aus Frameworks wie ReactJS oder VueJS einfach möglich.
Um auf eine Properties von Elementen zuzugreifen, nutzen wir die eckigen Klammern innerhalb unseres HTML Templates. Möchten wir also z.B. die HTMLElement.hidden Property einer Komponente beeinflussen, können wir das wie folgt erreichen:
<p [hidden]="'true'">{{text}}</p>
Hier wird die Eigenschaft hidden
des Elements auf 'true'
gesetzt und somit das Element ausgeblendet.
Um diese Eigenschaft dynamisch zu ändern, haben wir die Möglichkeit, in unserer Klasse selbst eine neue Property einzuführen und diese per Property-Binding
an die Property des p-Elements zu binden.
Hierzu setzen wir statt dem string 'true'
den Namen des Attributes in unserer Klasse auf das Binding:
class InfoBoxComponent implements OnInit {
text = "Additional Info-Text on our Info Box! 🎊";
hidden = true;
constructor() {}
ngOnInit() {}
}
<p>info-box works!</p>
<p [hidden]="hidden">{{text}}</p>
Um die Komponente nun durch User-Interaktion zu ändern, haben wir die Möglichkeit, auf sogenannte Events
zu hören und hierfür ebenfalls ein Event-Binding
zu definieren.
Event-Bindings werden in Angular über Runde Klammern definiert, welche den Namen des Events enthalten.
Wenn wir nun also auf das click Event eines HTML-Elements hören wollen, können wir das wie folgt erreichen.
<button (click)="someFunction()">Button Text</button>
Innerhalb dieser Definition haben wir nun die Möglichkeit, ein sogenanntes Template-Statement
zu definieren. Dies kann sowohl eine Template-Expression
sein, die z. B. direkt Änderungen an Attributen eurer Klasse als auch eine Referenz auf eine Methode in eurer Klasse macht.
Um es einfach zu halten, nutzen wir in diesem Fall erstmal eine Template-Expression
, welche den Wert von hidden
jeweils negiert. Also aus true
wird false
und andersherum.
<p>info-box works!</p>
<button (click)="hidden=!hidden">Toggle</button>
<p [hidden]="hidden">{{text}}</p>
Wir können natürlich auch jedes andere Event, wie z. B. keyup
benutzen. Mit diesem sehr simplen Mechanismus können wir generisch alle Arten von Komponenten benutzen und mit ihnen interagieren. Dies ist unabhängig davon, ob sie in Angular oder einem anderen Framework geschrieben sind.
Ein weiteres Core-Feature ist wie in jedem Framework die Ausgabe von listenartigen Datenstrukturen.
Hierfür gibt es in Angular die Direktive *ngFor
.
Direktiven sind HTML Attribute, welche an DOM-Elementen genutzt werden können. Hierbei können wir zwischen
Attribute Directives
undStructural directives
unterscheiden. Attribute Directives verändern oder beeinflussen das Verhalten eines Elements, an dem sie angehangen werden, wie z. B.ngStyle
zum Setzen von CSS-Styles auf Basis von Daten. Structural directives erzeugen oder entfernen DOM-Elemente, wie z.B.ngIf
oderngFor
. Strukturelle Direktiven werden mit dem Prefix*
gekennzeichnet.
Die Direktive ist angelehnt an eine For-Schleife, iteriert über eine listenartige Struktur und erzeugt für jedes Element eine Kopie des DOM-Elements, auf das es angewandt wird.
<!-- book-list.component.html -->
<ul>
<li *ngFor="let book of books">
<span>{{book.title}}</span> - <small>{{book.subtitle}}</small>
</li>
</ul>
Hierbei wird eine sogenannte Looping Variable
, in unserem Beispiel book
und eine Liste, in unserem books
definiert. Die Variable Buch enthält somit jeweils den Wert des aktuellen Listeneintrags.
Um *ngFor
auszuprobieren, erzeugen wir eine neue Komponente mit der Angular CLI.
Dazu führen wir den command ng generate component book-list
aus.
Damit die Komponente im Browser angezeigt wird, fügen wir das Tag <app-book-list></app-book-list>
in das Template der app.component.html
ein.
Wenn wir also in der BookListComponent
(siehe book-list.component.ts) eine Variable books
mit einer Liste von Büchern definieren, erhalten wir hierfür 3 DOM-Elemente.
books = [
{
title: "Book #1",
subtitle: "Subtitle #1",
},
{
title: "Book #2",
subtitle: "Subtitle #2",
},
{
title: "Book #3",
subtitle: "Subtitle #3",
},
];
Wir bieten Schulungen zum Einstieg in Angular. Ideal sind dafür unsere Angular & TypeScript Schulungen als auch Angular Lifetime Schulungen um dich möglichst effektiv in das Thema Angular zu begleiten. Im Kurs kannst Du die Fragen stellen, die Du nur schlecht googlen kannst, z.B. “Besserer Weg, um meine Applikation zu strukturieren”. Wir können sie Dir beantworten.
Wer genau aufgepasst hat, dem ist aufgefallen, dass die Daten in einer Angular Anwendung nicht in die Komponente gehören. Wir vermischen hier die Anzeige-Logik mit der Verwaltung unserer Daten. Nehmen wir also ein kurzes Refactoring unserer Anwendung vor und extrahieren die Daten in einen separaten Service.
Ein Service sollte sich immer um eine explizite Aufgabe kümmern und dementsprechend auch benannt werden.
In unserem Fall wollen wie die Daten von Büchern verwalten.
Wir nennen unseren Service also BookDataService
.
Um diesen zu generieren, können wir wie gewohnt die Angular-CLI benutzen.
$ ng generate service book-data
export class BookDataService {
books = [
{
title: "Book #1 from Service",
subtitle: "Subtitle #1",
},
{
title: "Book #2 from Service",
subtitle: "Subtitle #2",
},
{
title: "Book #3 from Service",
subtitle: "Subtitle #3",
},
];
constructor() {}
getBooks() {
return this.books;
}
}
Somit haben wir die Daten aus unserer Komponente gezogen.
Die Frage ist jetzt nur: Wie bekomme ich die Daten nun wieder in meine Komponente verbunden?
An dieser Stelle kommt der Begriff Dependency Injection
ins Spiel.
Unter Dependency Injection
versteht man ein Design-Pattern, welches ebenfalls Inversion of Control
genannt wird. Hierbei geht es darum, dass die erforderliche Abhängigkeit (Dependency) nicht von der aufrufenden Stelle selbst erzeugt wird, sondern diese Komponente die Kontrolle abgibt und lediglich definiert, welche Abhängigkeiten bestehen.
In unserem kleinen Beispiel erstellt also die BookListComponent
nicht unseren Service, sondern gibt dem Angular Framework lediglich Bescheid, dass sie einen BookDataService
benötigt, um zu funktionieren.
Innerhalb des Angular Frameworks werden die verschiedenen Services von dem sogenannten Injector
verwaltet.
Dieser gibt der aufrufenden Stelle eine Referenz auf den angefragten Service, sofern dieser definiert ist.
Die Definition der Abhängigkeit wird hierbei über den Konstruktor abgebildet. In dem Beispiel unserer BookListComponent
definieren wir die Abhängigkeit auf BookDataService
und binden diese an das Feld bookData
unserer Komponente.
Innerhalb des Konstruktors rufen wir dann die getBooks()
Methode des Services auf und beschaffen uns unsere Daten.
export class BookListComponent {
books: { title: string; subtitle: string; }[];
constructor(private bookData: BookDataService) {
this.books = this.bookData.getBooks();
}
}
Meist importiert deine IDE den BookDataService
automatisch.
Sollte dies nicht der Fall sein, kannst du dies selbst vornehmen und folgenden import an den Anfang der book-list.component.ts
schreiben.
import { BookDataService } from "../book-data.service";
Die aktuelle Version hat uns die Konzepte von Angular Stück für Stück näher erklärt. In der Realität werden Daten jedoch meist von einem Server asynchron nachgeladen.
Wir laden diese Daten von einer Beispiel-API, welche ihr mit folgendem Befehl starten könnt:
$ npx bookmonkey-api
JSON Server is running on port 4730
Unter folgender URL könnt ihr euch nun die Daten ansehen, welche vom Server ausgegeben werden: http://localhost:4730/books
Im nächsten Schritt wollen wir diese Daten aus unserem BookDataService
heraus abrufen. Dazu benötigen wir den sogenannten HttpClient
Service. Dieser bietet uns eine sehr einfache API, um verschiedene Operationen auf eine HTTP-Schnittstelle auszuführen.
Der Service ist Teil eines separaten Modules und muss explizit eingebunden werden. Wir erreichen dies, indem wir in der Datei app.module.ts
das HttpClientModule
importieren und im Array imports
angeben.
// ...
import {HttpClientModule} from '@angular/common/http';
@NgModule({
declarations: [
AppComponent,
InfoBoxComponent,
BookListComponent
],
imports: [
BrowserModule,
HttpClientModule
],
// ...
Ist dies erledigt, kennt unser Injector
auch einen Service vom Typ HttpClient
, welchen wir nun über den Konstruktor unseres BookDataService
einbinden können.
import { Injectable } from "@angular/core";
import { HttpClient } from "@angular/common/http";
@Injectable({
providedIn: "root",
})
export class BookDataService {
constructor(private http: HttpClient) {}
getBooks() {
return this.http.get("http://localhost:4730/books");
}
}
Der Service bietet uns die Methode .get(url:string)
, welcher wir den API-Endpoint für unsere Abfrage angeben können. Wir nutzen hier die Adresse des lokal gestarteten JSON-Servers.
Der Rückgabewert der get-Methode des HTTP-Services liefert ein Observable zurück. Dies ist eine Datenstruktur, welche uns den Umgang mit asynchronen Daten erleichtert. Angular nutzt dafür die RxJS Observables.
Es hat sich als guter Stil etabliert, Variablen und Felder, welche asynchrone Datenstrukturen halten, mit einem $
postfix zu kennzeichnen. Es hat rein funktional keinen Einfluss, hilft jedoch beim langfristigen Zurechtfinden und der Wartung eurer Anwendung.
export class BookListComponent {
books$: Observable<any>;
constructor(private bookData: BookDataService) {
this.books$ = this.bookData.getBooks();
}
}
Meist importiert deine IDE den Observable
automatisch.
Sollte dies nicht der Fall sein, kannst du dies selbst vornehmen und folgenden import an den Anfang der book-list.component.ts
schreiben.
import { Observable } from "rxjs";
Der Aufruf innerhalb unserer Komponente ändert sich also im Grunde nicht. Die Auswertung innerhalb unseres HTML-Templates muss jedoch etwas angepasst werden. Mithilfe einer sogenannten async
Pipe können wir der *ngFor
Direktive den Umgang mit der asynchronen Datenstruktur ermöglichen.
<ul>
<li *ngFor="let book of books$ | async">
<span>{{book.title}}</span> - <small>{{book.subtitle}}</small>
</li>
</ul>
Die async
Pipe in Verbindung mit *ngFor
registriert sich auf asynchrone Updates der books$
Variable. Durch diese Anpassung unseres Templates können wir nun auch die Daten von unseren JSON-Server wie folgt anzeigen:
Angular ist in vielerlei Hinsicht sehr opinionated (meinungsstark). Dies bedeutet, dass viele Entscheidungen über Architektur und Rendering dem/der Entwickler:in bereits abgenommen werden. Dies hat natürlich den Vorteil, dass sich das Projektteam zu 100% auf die Umsetzung von Features konzentrieren kann und nicht die grundlegende Architektur eigenständig aufbauen muss.
Durch die sehr einheitliche Struktur von Angular Anwendungen lassen sich in Angular ausgebildete Entwickler:innen sehr schnell in das Projekt integrieren, da Angular Anwendungen stets einer gewissen Struktur folgen. Dies macht die Skalierung von Entwickler:innen-Zeit auf dem Projekt deutlich einfacher als mit Individuallösungen der Architektur in anderen Frameworks.
Generell ist es für langlebige Enterprise Projekte sicherlich eine gute Option. Andere Frameworks wie React und VueJS sollten aber ebenfalls in Betracht gezogen werden, um objektiv die beste Entscheidung für die aktuellen Herausforderungen zu treffen.
Wenn Ihr euch weiter mit uns und anderen austauschen wollt, kommt in unseren Discord Chat mit über 2000 wunderbaren anderen Menschen! Zusammen lernt es sich besser! :)
Wir bieten Schulungen zum Einstieg in Angular. Ideal sind dafür unsere Angular & TypeScript Schulungen als auch Angular Lifetime Schulungen um dich möglichst effektiv in das Thema Angular zu begleiten. Im Kurs kannst Du die Fragen stellen, die Du nur schlecht googlen kannst, z.B. “Besserer Weg, um meine Applikation zu strukturieren”. Wir können sie Dir beantworten.