<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Angular-DE Archives - Nerd Corner</title>
	<atom:link href="https://nerd-corner.com/de/category/angular-de/feed/" rel="self" type="application/rss+xml" />
	<link>https://nerd-corner.com/de/category/angular-de/</link>
	<description>Craft your dreams!</description>
	<lastBuildDate>Wed, 26 Mar 2025 09:20:51 +0000</lastBuildDate>
	<language>de</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.8.2</generator>

<image>
	<url>https://nerd-corner.com/wp-content/uploads/2019/10/cropped-LogoNerdCorner-2-32x32.png</url>
	<title>Angular-DE Archives - Nerd Corner</title>
	<link>https://nerd-corner.com/de/category/angular-de/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Deployment einer WebApp mit Kubernetes und Caddy</title>
		<link>https://nerd-corner.com/de/deployment-einer-webapp-mit-kubernetes-und-caddy/</link>
					<comments>https://nerd-corner.com/de/deployment-einer-webapp-mit-kubernetes-und-caddy/#respond</comments>
		
		<dc:creator><![CDATA[Nerds]]></dc:creator>
		<pubDate>Sun, 16 Feb 2025 17:20:29 +0000</pubDate>
				<category><![CDATA[Angular-DE]]></category>
		<category><![CDATA[App Entwicklung]]></category>
		<category><![CDATA[Software-DE]]></category>
		<category><![CDATA[angular]]></category>
		<category><![CDATA[Caddy]]></category>
		<category><![CDATA[Caddy Server]]></category>
		<category><![CDATA[Cluster]]></category>
		<category><![CDATA[ClusterIP]]></category>
		<category><![CDATA[ConfigMap]]></category>
		<category><![CDATA[Container]]></category>
		<category><![CDATA[Deployment]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[Domain]]></category>
		<category><![CDATA[Domain Registrierung]]></category>
		<category><![CDATA[Hetzner]]></category>
		<category><![CDATA[Images]]></category>
		<category><![CDATA[Ingress]]></category>
		<category><![CDATA[Ingress Controller]]></category>
		<category><![CDATA[IP Adresse]]></category>
		<category><![CDATA[K3s]]></category>
		<category><![CDATA[Kubectl]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[LoadBalancer]]></category>
		<category><![CDATA[mySql]]></category>
		<category><![CDATA[Nest.js]]></category>
		<category><![CDATA[Node Port]]></category>
		<category><![CDATA[Pods]]></category>
		<category><![CDATA[Redis]]></category>
		<category><![CDATA[Reverse Proxy]]></category>
		<category><![CDATA[Schritt für Schritt Anweisung]]></category>
		<category><![CDATA[Server]]></category>
		<category><![CDATA[Service]]></category>
		<category><![CDATA[Traefik]]></category>
		<guid isPermaLink="false">https://nerd-corner.com/?p=1687</guid>

					<description><![CDATA[<p>Nachdem im letzten Beitrag beschrieben wurde, wie man produktionsreife Docker-Images erstellt und auf Docker Hub hochlädt, geht es nun darum, diese Images auf einem Server &#8230; </p>
<p>The post <a href="https://nerd-corner.com/de/deployment-einer-webapp-mit-kubernetes-und-caddy/">Deployment einer WebApp mit Kubernetes und Caddy</a> appeared first on <a href="https://nerd-corner.com/de">Nerd Corner</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>Nachdem im letzten Beitrag beschrieben wurde, wie man produktionsreife Docker-Images erstellt und auf Docker Hub hochlädt, geht es nun darum, diese Images auf einem Server bereitzustellen. Ziel ist es, die Webanwendung über eine eigene Domain für alle zugänglich zu machen. Dafür nutzen wir einen Virtual Private Server (VPS) von Hetzner und setzen Kubernetes (<span style="font-size: 1.125rem;">k3s) </span><span style="font-size: 1.125rem;">mit Caddy als Reverse Proxy ein.</span></p>
<p><em><strong>Das könnte dich ebenfalls interessieren: </strong><a href="https://nerd-corner.com/de/docker-images-erstellen-und-auf-docker-hub-hochladen/">Docker Images erstellen und auf Dockerhub hochladen</a></em></p>
<h2>Einen VPS bei Hetzner einrichten</h2>
<p>Hetzner bietet oft Empfehlungslinks mit Guthaben-Vorteilen für Neukunden. Selbstverständlich kann man auch andere Anbieter nutzen, aber Hetzner ist preislich attraktiv und bietet solide Leistungen.</p>
<h3>Was ist ein VPS?</h3>
<p>Ein Virtual Private Server (VPS) ist ein virtueller Server, der auf einer physischen Maschine betrieben wird und als eigenständiger Server agiert. Er bietet mehr Kontrolle als klassisches Shared Hosting und ist eine kostengünstige Alternative zu dedizierten Servern. Der Zugriff erfolgt in der Regel per SSH (Secure Shell), wodurch wir den Server über die Kommandozeile steuern können.</p>
<h3>SSH-Zugriff auf den VPS</h3>
<p data-start="1137" data-end="1384">Nach dem Erstellen eines VPS erfolgt die Verwaltung meist über eine <strong data-start="1205" data-end="1227">Secure Shell (SSH)</strong>. SSH ist ein Protokoll, das verschlüsselte Verbindungen zu entfernten Servern ermöglicht. Um sich mit dem Server zu verbinden, nutzt man folgenden Befehl:</p>
<p data-start="1137" data-end="1384"><span style="background-color: #e9ebec; color: #222222; font-family: Monaco, Consolas, 'Andale Mono', 'DejaVu Sans Mono', monospace; font-size: 15px;">ssh root@&lt;IP-Adresse-des-Servers&gt;</span></p>
<p data-start="1430" data-end="1582">Falls ein SSH-Schlüssel hinterlegt wurde, kann die Authentifizierung per <strong data-start="1503" data-end="1535">Public-Key-Authentifizierung</strong> erfolgen, was sicherer als ein Passwort ist.</p>
<h3>Server bei Hetzner erstellen</h3>
<ol>
<li>Nach der Anmeldung in der Hetzner Cloud navigieren wir zu „Projekte“ und erstellen ein neues Projekt.</li>
<li>Danach wählen wir „Server hinzufügen“ und können eine Instanz konfigurieren.</li>
<li>Für den Anfang reicht oft das günstigste Modell aus. Ich empfehle jedoch, die Option für eine IPv4-Adresse zu aktivieren, da rein IPv6-basierte Setups oft Kompatibilitätsprobleme verursachen.</li>
</ol>
<h2>Domain einrichten</h2>
<p>Um die Anwendung später unter einer eigenen Domain zu erreichen, muss man eine Domain registrieren und mit dem Server verknüpfen.</p>
<h3>Domain bei Hetzner beantragen</h3>
<ol>
<li>In der Hetzner ConsoleH eine neue Domain registrieren oder eine bestehende Domain hinzufügen.</li>
<li>Um DNS-Einträge zu verwalten, müssen wir den DNS-Zugriff aktivieren.</li>
</ol>
<h3>Nameserver setzen</h3>
<p>Folgende Nameserver sollten genutzt werden:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">helium.ns.hetzner.de. 
hydrogen.ns.hetzner.com. 
oxygen.ns.hetzner.com.</pre>
<p>Diese neuen Nameserver bieten bessere Performance und Flexibilität im Vergleich zu den alten Hetzner-Nameservern:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">ns1.first-ns.de.
robotns2.second-ns.de.
robotns3.second-ns.com.</pre>
<p>Beide Nameserver Varianten sind aber möglich! Die DNS Änderungen brauchen etwas Zeit. Mit Tools wie <a href="https://mxtoolbox.com/">MXToolbox</a> können wir aber überprüfen, ob die Änderungen bereits stattgefunden haben.</p>
<h3>Domain mit dem Server verbinden</h3>
<p data-start="3155" data-end="3229">Nun muss die <strong data-start="3168" data-end="3219">IP-Adresse des Servers mit der Domain verknüpft</strong> werden:</p>
<ol data-start="3231" data-end="3512">
<li data-start="3231" data-end="3283">In der Hetzner Cloud zu <strong data-start="3258" data-end="3271">DNS-Zonen</strong> wechseln.</li>
<li data-start="3284" data-end="3323">Die registrierte Domain auswählen.</li>
<li data-start="3324" data-end="3411">Einen neuen <strong data-start="3339" data-end="3351">A-Record</strong> erstellen und die <strong data-start="3370" data-end="3386">IPv4-Adresse</strong> des Servers eintragen.</li>
<li data-start="3412" data-end="3512">Falls vorhanden, den <strong data-start="3436" data-end="3458">IPv6-Record (AAAA)</strong> entfernen, um Kompatibilitätsprobleme zu vermeiden.</li>
</ol>
<p data-start="3514" data-end="3621">Mit <a href="https://mxtoolbox.com/" target="_blank" rel="noopener" data-start="3518" data-end="3553">MXToolbox</a> kann man auch hier prüfen, ob die DNS-Änderungen bereits übernommen wurden.</p>
<h2>Kubernetes einrichten</h2>
<p>Kubernetes ist ein leistungsfähiges Orchestrierungstool für Container. Ich verwende <strong data-start="3756" data-end="3763">k3s</strong>, eine schlanke Kubernetes-Variante, die sich besonders gut für kleinere Umgebungen eignet.</p>
<h3 data-start="3858" data-end="3902">K3s auf dem Server installieren</h3>
<p data-start="3903" data-end="3980">Per SSH auf den Server verbinden und k3s mit folgendem Befehl installieren:</p>
<pre><code class="language-sh">curl -sfL https://get.k3s.io | sh - </code></pre>
<p><span style="font-size: 1.125rem;">Das Skript installiert k3s und startet den Kubernetes-Dienst. </span>Nach der Installation kann k3s mit folgendem Befehl überprüft werden:</p>
<pre><code class="language-sh">kubectl get nodes
</code></pre>
<p>k3s bringt eine eigene <code>kubectl</code>-Version mit, sodass keine separate Installation nötig ist.</p>
<h3>YAML-Files für FE, BE, MySQL und Redis erstellen</h3>
<p>Für das Deployment unserer Anwendung brauchen wir YAML-Dateien für:</p>
<ul>
<li>Frontend (Angular)</li>
<li>Backend (NestJS)</li>
<li>Datenbank (MySQL)</li>
<li>Session-Management (Redis)</li>
</ul>
<p>Eine <strong data-start="4641" data-end="4661">Deployment-Datei</strong> für das Backend könnte so aussehen:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend
spec:
  replicas: 1
  selector:
    matchLabels:
      app: backend
  template:
    metadata:
      labels:
        app: backend
    spec:
      containers:
        - name: backend
          image: dockerhub-user/backend:latest
          ports:
            - containerPort: 3000
---
apiVersion: v1
kind: Service
metadata:
  name: backend-service
spec:
  selector:
    app: backend
  ports:
    - protocol: TCP
      port: 80
      targetPort: 3000
  type: ClusterIP
</pre>
<h2>Was sind Deployments und Services?</h2>
<ul>
<li><strong>Deployments</strong> verwalten die Bereitstellung und Skalierung von Containern.</li>
<li><strong>Services</strong> sorgen für eine stabile Netzwerkverbindung zwischen Containern.</li>
<li>ClusterIP bedeutet, dass der Service nur innerhalb des Kubernetes-Clusters erreichbar ist.</li>
</ul>
<h2>Caddy als Reverse Proxy einrichten</h2>
<p>Damit eingehender Traffic richtig verteilt wird, wird ein Reverse Proxy benötigt. K3s bringt standardmäßig <strong>Traefik</strong> mit, allerdings habe ich mich für eine einfachere Lösung entschieden: <strong>Caddy</strong>. Ich war echt erstaunt wie wenig Anleitungen bzw. Dokumentation es zu Caddy in Kombination mit Kubernetes gibt.</p>
<h3>Warum Kubernetes mit Caddy?</h3>
<ul>
<li>Automatische Let’s Encrypt SSL-Zertifikate</li>
<li>Einfache Konfiguration per <code>Caddyfile</code></li>
<li>Built-in Load Balancer</li>
</ul>
<h3>Traefik entfernen</h3>
<pre><code class="language-sh">kubectl delete helmrelease traefik -n kube-system
</code></pre>
<h3>Caddy Deployment erstellen</h3>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">apiVersion: apps/v1
kind: Deployment
metadata:
  name: caddy
spec:
  replicas: 1
  selector:
    matchLabels:
      app: caddy
  template:
    metadata:
      labels:
        app: caddy
    spec:
      containers:
        - name: caddy
          image: caddy
          volumeMounts:
            - name: caddy-config
              mountPath: /etc/caddy/Caddyfile
      volumes:
        - name: caddy-config
          configMap:
            name: caddy-config
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: caddy-config
data:
  Caddyfile: |

    example.com {
        reverse_proxy backend-service:3000
    }

---
apiVersion: v1
kind: Service
metadata:
  name: caddy-service
spec:
  type: LoadBalancer
  selector:
    app: caddy
  ports:
    - port: 80
      targetPort: 80
    - port: 443
      targetPort: 443</pre>
<p>Wichtig: Da Let’s Encrypt ein Rate-Limit hat, sollten Tests zunächst mit Staging-Zertifikaten erfolgen!</p>
<h2>Fazit</h2>
<p>Nach diesen Schritten läuft die Anwendung nun in einem Kubernetes-Cluster auf einem Hetzner VPS und ist über eine eigene Domain erreichbar. Als nächstes würde es sich anbieten eine automatische CI/CD-Pipeline einzurichten, um neue Versionen ohne manuellen Aufwand zu deployen.</p>
<p>The post <a href="https://nerd-corner.com/de/deployment-einer-webapp-mit-kubernetes-und-caddy/">Deployment einer WebApp mit Kubernetes und Caddy</a> appeared first on <a href="https://nerd-corner.com/de">Nerd Corner</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://nerd-corner.com/de/deployment-einer-webapp-mit-kubernetes-und-caddy/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Docker Images erstellen und auf Docker Hub hochladen</title>
		<link>https://nerd-corner.com/de/docker-images-erstellen-und-auf-docker-hub-hochladen/</link>
					<comments>https://nerd-corner.com/de/docker-images-erstellen-und-auf-docker-hub-hochladen/#respond</comments>
		
		<dc:creator><![CDATA[Nerds]]></dc:creator>
		<pubDate>Wed, 15 Jan 2025 11:44:13 +0000</pubDate>
				<category><![CDATA[Angular-DE]]></category>
		<category><![CDATA[App Entwicklung]]></category>
		<category><![CDATA[Software-DE]]></category>
		<category><![CDATA[angular]]></category>
		<category><![CDATA[Anleitung]]></category>
		<category><![CDATA[Backend]]></category>
		<category><![CDATA[Backend Server]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[Docker Hub]]></category>
		<category><![CDATA[Docker Images]]></category>
		<category><![CDATA[Docker Repository]]></category>
		<category><![CDATA[frontend]]></category>
		<category><![CDATA[google cloud mysql]]></category>
		<category><![CDATA[Images]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[mySql]]></category>
		<category><![CDATA[Nest]]></category>
		<category><![CDATA[Nest.js]]></category>
		<category><![CDATA[Redis]]></category>
		<category><![CDATA[redis session management]]></category>
		<category><![CDATA[redis session management nest.js]]></category>
		<category><![CDATA[Repository]]></category>
		<category><![CDATA[Schritt für Schritt Anweisung]]></category>
		<guid isPermaLink="false">https://nerd-corner.com/?p=1680</guid>

					<description><![CDATA[<p>In diesem Beitrag zeige ich, wie man production-ready Docker Images für eine Web Anwendung mit Angular, NestJS, MySQL und Redis erstellt und anschließend auf Docker &#8230; </p>
<p>The post <a href="https://nerd-corner.com/de/docker-images-erstellen-und-auf-docker-hub-hochladen/">Docker Images erstellen und auf Docker Hub hochladen</a> appeared first on <a href="https://nerd-corner.com/de">Nerd Corner</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>In diesem Beitrag zeige ich, wie man production-ready Docker Images für eine Web Anwendung mit Angular, NestJS, MySQL und Redis erstellt und anschließend auf Docker Hub veröffentlicht. Voraussetzung ist eine installierte Docker Umgebung.</p>
<p><em><strong>Das könnte dich ebenfalls interessieren:</strong> <a href="https://nerd-corner.com/de/erfahrungen-aus-der-praxis-nestjs-auf-vercel-hosten/">NestJS auf Vercel hosten</a></em></p>
<h2>Erstellung der Docker Compose Yml</h2>
<p>Mit Docker Compose können alle Komponenten einer Anwendung über eine einzige Konfigurationsdatei definiert und gemeinsam gebuildet bzw. gestartet werden.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">version: '3.8'

services:
  frontend:
    build: ./frontend
    ports:
      - "80:80"
    depends_on:
      - backend

  backend:
    build: ./backend
    ports:
      - "3000:3000"
    depends_on:
      - mysql
      - redis
    environment:
      - DATABASE_URL=mysql://user:password@mysql:3306/db
      - SESSION_STORE=redis://redis:6379

  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: db
    ports:
      - "3306:3306"

  redis:
    image: redis:latest
    ports:
      - "6379:6379"</pre>
<h2>Erstellung der Env Datei</h2>
<p>Um Umgebungsvariablen zentral zu verwalten, erstellen wir eine <code>.env</code> Datei:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">DATABASE_URL=mysql://user:password@mysql:3306/db 
SESSION_STORE=redis://redis:6379</pre>
<p>Wichtig: Alle ENV-Variablen, die im Code verwendet werden, müssen auch in <code>docker-compose.yml</code> vorkommen!</p>
<h2>Docker-Image für das Frontend</h2>
<p>Das Angular-Frontend muss für die Produktion gebaut werden. Hier ein Beispiel-<code>Dockerfile</code>:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">FROM node:20 AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install
COPY . .
RUN npm run build --prod

FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf</pre>
<p>Da wir für den Build auf nginx angewiesen sind brauchen wir auch eine entsprechende Config Datei:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">server {
  listen 80;
  server_name _;

  location / {
    root /usr/share/nginx/html;
    index index.html;
    try_files $uri $uri/ /index.html;
  }
}</pre>
<h2>Docker-Image für das Backend</h2>
<p>Auch das NestJS-Backend muss gebaut werden. Hier ein optimiertes <code>Dockerfile</code></p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic"># Build stage
FROM node:20 AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install
COPY . .
RUN npm run build

# Production stage
FROM node:20-alpine
WORKDIR /app

COPY --from=build /app/dist ./dist
COPY package.json package-lock.json ./
RUN npm install --only=production
CMD ["node", "dist/main.js"]</pre>
<h2>Builden der App mit Docker Compose</h2>
<p>Nachdem alle Dockerfiles konfiguriert wurden können jetzt die Images gebuildet werden. Mit Docker compose ist das ganze wirklich einfach.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">docker compose up -d --build</pre>
<p>Anschließend sind die Images fertig, die Container gebaut und die App kann lokal direkt ausgetestet werden! Als letzer Schritt müssen die Images noch auf DockerHub hochgeladen werden, damit sie später leichter für das Deployment auf einem Server genutzt werden können.</p>
<h2>Hochladen auf Docker Hub</h2>
<p>Das Hochladen wird nachfolgend Schritt für Schritt erklärt:</p>
<ol>
<li>Erstelle einen Account bei <a href="https://hub.docker.com/">Docker Hub</a>.</li>
<li>Erstelle ein Repository für das Frontend und Backend (1 privates Repo ist aktuell kostenlos).</li>
<li>Baue die Images und tagge sie:
<pre class="EnlighterJSRAW" data-enlighter-language="generic">docker tag &lt;image-id&gt; dockerAccountName/frontend:latest
docker tag &lt;image-id&gt; dockerAccountName/backend:latest</pre>
</li>
<li>Melde dich an und pushe die Images:
<pre class="EnlighterJSRAW" data-enlighter-language="generic">docker login
docker push dockerAccountName/frontend:latest
docker push dockerAccountName/backend:latest</pre>
</li>
</ol>
<h2>Ausblick: Deployment mit Kubernetes</h2>
<p>Da die Images nun auf Docker Hub sind, steht dem Deployment nichts mehr im Wege. Ich habe mich für ein Kubernetes-Cluster auf einem Hetzner-VPS entschieden. <a href="https://nerd-corner.com/de/deployment-einer-webapp-mit-kubernetes-und-caddy/">Mehr Infos dazu hier</a>.</p>
<p>The post <a href="https://nerd-corner.com/de/docker-images-erstellen-und-auf-docker-hub-hochladen/">Docker Images erstellen und auf Docker Hub hochladen</a> appeared first on <a href="https://nerd-corner.com/de">Nerd Corner</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://nerd-corner.com/de/docker-images-erstellen-und-auf-docker-hub-hochladen/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Wie man eine Reactive Angular Form SVG mit klickbaren Elementen erstellt</title>
		<link>https://nerd-corner.com/de/wie-man-eine-reactive-angular-form-svg-mit-klickbaren-elementen-erstellt/</link>
					<comments>https://nerd-corner.com/de/wie-man-eine-reactive-angular-form-svg-mit-klickbaren-elementen-erstellt/#comments</comments>
		
		<dc:creator><![CDATA[Nerds]]></dc:creator>
		<pubDate>Thu, 30 Dec 2021 15:00:43 +0000</pubDate>
				<category><![CDATA[Angular-DE]]></category>
		<category><![CDATA[Software-DE]]></category>
		<category><![CDATA[angular]]></category>
		<category><![CDATA[Angular reactive forms]]></category>
		<category><![CDATA[create SVG]]></category>
		<category><![CDATA[ng]]></category>
		<category><![CDATA[reactive]]></category>
		<category><![CDATA[reactive form with SVG]]></category>
		<category><![CDATA[reactive forms]]></category>
		<category><![CDATA[SVG]]></category>
		<category><![CDATA[SVG binding]]></category>
		<category><![CDATA[SVG plan]]></category>
		<category><![CDATA[TDD]]></category>
		<guid isPermaLink="false">https://nerd-corner.com/?p=1128</guid>

					<description><![CDATA[<p>Während der Corona Pandemie haben viele Firmen und Mitarbeiter Home-Office für sich entdecket. Dadurch fiel auf, dass nicht jeder Angestellte einen eigenen Arbeitsplatz braucht. Mittlerweile &#8230; </p>
<p>The post <a href="https://nerd-corner.com/de/wie-man-eine-reactive-angular-form-svg-mit-klickbaren-elementen-erstellt/">Wie man eine Reactive Angular Form SVG mit klickbaren Elementen erstellt</a> appeared first on <a href="https://nerd-corner.com/de">Nerd Corner</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>Während der Corona Pandemie haben viele Firmen und Mitarbeiter Home-Office für sich entdecket. Dadurch fiel auf, dass nicht jeder Angestellte einen eigenen Arbeitsplatz braucht. Mittlerweile bieten die Firmen stattdessen ein Shared-Desk Modell. Das bedeutet, dass sich die Mitarbeiter flexibel die Arbeitsplätze teilen. Häufig kann man sich aber seinen Wunscharbeitsplatz schon vorab reservieren. Diese Arbeitsplatz Buchungsfunktion hat mich schon länger gereizt. Besonders interessant finde ich die Möglichkeit in Angular eine Reactive Form zu erstellen, welche auch einen Büroplan beinhaltet. In der Angular Reactive Form SVG gibt es dabei die Möglichkeit einen Arbeitsplatz aus dem Büroplan anzuklicken bzw. farblich zu markieren. Sobald ein Arbeitsplatz ausgewählt wurde soll die Form valid werden und der Schreibtisch somit buchbar. Nachfolgend wird Schritt für Schritt die Umsetzung erklärt.</p>
<p><strong><em>Das könnte dich auch interessieren: </em></strong><a href="https://nerd-corner.com/de/wie-man-eine-angular-fassade-mit-push-architektur-programmiert/"><em>Wie man in Angular eine PUSH Architektur programmiert.</em></a></p>
<h2>Liste der Komponenten</h2>
<ul>
<li>Entwicklungsumgebung (z.B. VS Code)</li>
<li>Angular CLI</li>
<li><a  data-e-Disable-Page-Transition="true" class="download-link" title="" href="https://nerd-corner.com/de/download/1134/?tmstv=1756257708" rel="nofollow" id="download-link-1134" data-redirect="false" >
	SVG File</a>
</li>
</ul>
<p><iframe id="angularFrame" title="Angular Seat Booker App" src="https://nerd-corner.com/angular/SVG-form/" width="600" height="380" frameborder="0"><span data-mce-type="bookmark" style="display: inline-block; width: 0px; overflow: hidden; line-height: 0;" class="mce_SELRES_start">﻿</span><span data-mce-type="bookmark" style="display: inline-block; width: 0px; overflow: hidden; line-height: 0;" class="mce_SELRES_start">﻿</span><span data-mce-type="bookmark" style="display: inline-block; width: 0px; overflow: hidden; line-height: 0;" class="mce_SELRES_start">﻿</span><span data-mce-type="bookmark" style="display: inline-block; width: 0px; overflow: hidden; line-height: 0;" class="mce_SELRES_start">﻿</span><br />
</iframe></p>
<h2>Erstellung eines Büroplans</h2>
<p><img fetchpriority="high" decoding="async" class="zoooom aligncenter wp-image-1138" src="https://nerd-corner.com/wp-content/uploads/2021/12/Office-plan.jpg" alt="SVG office plan with clickable elements Angular Form SVG" width="720" height="497" srcset="https://nerd-corner.com/wp-content/uploads/2021/12/Office-plan.jpg 726w, https://nerd-corner.com/wp-content/uploads/2021/12/Office-plan-300x207.jpg 300w" sizes="(max-width: 720px) 100vw, 720px" /></p>
<p>Ich würde beinahe behaupten, dass die Erstellung des Büroplans der schwerste Teil der Aufgabe war. Der Büroplan kann auf unterschiedliche Weisen erstellt werden. Wichtig dabei ist, dass es sich um eine SVG Datei handeln muss. Ich habe hierfür Adobe Xd genutzt und mit Linien, Rechtecken und Kreisen gearbeitet. Das Ergebnis könnte schöner sein, aber es reicht um die Funktionalität zu demonstrieren. Du kannst meinen Plan benutzen oder auch einen eigenen erstellen.</p>
<p>Nachdem das SVG Bild erstellt wurde, müssen die Bereiche die einen Arbeitsplatz darstellen zu einer Gruppe zusammengefasst werden. Ich habe den Gruppe noch gleich die Klasse „seat“ zugeteilt. Das sieht dann so aus:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="html" data-enlighter-theme="atomic">&lt;g class="seat" &gt;
    &lt;g id="table-0" transform="translate(19 59)"&gt;
      &lt;rect width="66" height="35" stroke="none" /&gt;
      &lt;rect x="2.5" y="2.5" width="61" height="30" fill="none" /&gt;
    &lt;/g&gt;
    &lt;g id="screen-0" transform="translate(31 84)"&gt;
      &lt;rect id="fill" width="42" height="4" stroke="none" /&gt;
      &lt;path
        d="M0,1.5h42M39.5,0v4M42,2.5h-42M2.5,4v-4"
        fill="none"
        clip-path="url(#clip)"
      /&gt;
    &lt;/g&gt;
    &lt;g id="chair-0" transform="translate(40 30)"&gt;
      &lt;ellipse cx="12.5" cy="11" rx="12.5" ry="11" stroke="none" /&gt;
      &lt;ellipse cx="12.5" cy="11" rx="10" ry="8.5" fill="none" /&gt;
    &lt;/g&gt;
  &lt;/g&gt;</pre>
<p>Der gezeigte Codeausschnitt entspricht einem Arbeitsplatz, wie beispielsweise der in dem roten Kreis hier:</p>
<p><img decoding="async" class="zoooom aligncenter wp-image-1140" src="https://nerd-corner.com/wp-content/uploads/2021/12/one-seat-1.png" alt="SVG office plan place selected Angular Form SVG" width="1140" height="625" srcset="https://nerd-corner.com/wp-content/uploads/2021/12/one-seat-1.png 1151w, https://nerd-corner.com/wp-content/uploads/2021/12/one-seat-1-300x164.png 300w, https://nerd-corner.com/wp-content/uploads/2021/12/one-seat-1-1024x561.png 1024w, https://nerd-corner.com/wp-content/uploads/2021/12/one-seat-1-768x421.png 768w" sizes="(max-width: 1140px) 100vw, 1140px" /></p>
<h2>Angular Reactive Form erstellen</h2>
<p>Um den Büroplan in eine reactive Form einzubinden, erstellen wir ein neues Angular Projekt. Ich habe mich bei dem Projekt vom deutschen YouTuber Unleashed Design inspirieren lassen: <a href="https://www.youtube.com/watch?v=NOuVxP7FxBc">https://www.youtube.com/watch?v=NOuVxP7FxBc</a>. Allgemein kann ich seine Videos sehr empfehlen! Man lernt bei ihm immer wieder neue Tipps und Tricks, besonders im Umgang mit Angular.</p>
<p>Wir brauchen für die Reactive Angular Form SVG das ReactiveFormsModule. Anschließend erstellen wir eine FormGroup und FormControl Felder. Die FormGroup habe ich reservationForm getauft. Sie beinhaltet ein Feld für den Namen, ein Feld für den Wochentag und ein Feld für den Platz den man buchen möchte.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="typescript" data-enlighter-theme="atomic">public reservationForm: FormGroup = new FormGroup({
    name: new FormControl('', [Validators.required], []),

    weekday: new FormControl('', [Validators.required], []),

    seat: new FormControl('', [Validators.required], []),
  });</pre>
<p>Um sich das Formular anzeigen lassen zu können ist etwas html Code notwendig:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="html" data-enlighter-theme="atomic">&lt;form [formGroup]="reservationForm"&gt;
  &lt;label for="name"&gt; Your name:&lt;/label&gt;
  &lt;input id="name" type="text" formControlName="name" placeholder="Name" /&gt;

  &lt;label for="weekday"&gt; Enter the reservation day:&lt;/label&gt;
  &lt;input
    id="weekday"
    type="text"
    formControlName="weekday"
    placeholder="Monday"
  /&gt;
&lt;/form&gt;</pre>
<p>Zur besseren Demonstration macht es Sinn sich auch den Wert des Formulars anzeigen zu lassen:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="html" data-enlighter-theme="atomic">&lt;p&gt;Values: {{ reservationForm.value | json }}&lt;/p&gt;
&lt;p&gt;Form valid: {{ reservationForm.valid }}&lt;/p&gt;</pre>
<h2>Seats Komponente für die Angular Form SVG erstellen</h2>
<p>Um ein Custom Formular Feld zu bauen wird als erstes eine neue Komponente benötigt. Hier können wir gleich einen Tipp von Unleashed Design nutzen. Angular bietet nämlich die Möglichkeit SVG Bilder als templateUrl einzubinden. Wir löschen die seats.component.html und ersetzen diese durch unseren SVG Büroplan mit dem Namen seats.component.svg. In der seats.component.ts können wir dadurch die templateUrl durch die SVG Datei ersetzen:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="typescript" data-enlighter-theme="atomic">selector: 'app-seats',
templateUrl: './seats.component.svg',
styleUrls: ['./seats.component.sass'],</pre>
<p>Das Custom Formular Feld kann jetzt in die reactive Form eingebunden werden:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="html" data-enlighter-theme="atomic">&lt;app-seats id="seat" formControlName="seat"&gt;&lt;/app-seats&gt;</pre>
<p>In der Sass Datei kann der SVG Büroplan beliebig angepasst werden. Farben, Strichdicken, und vieles mehr können hier eingestellt werden. Ich habe es so angepasst, dass der Büroplan rot wird, wenn das SVG Formularfeld invalid wird:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="scss" data-enlighter-theme="atomic">$successColour: #19e619

:host
    &amp;.ng-touched, &amp;.ng-dirty
            &amp;.ng-invalid
                svg
                    .wall, .seat
                        stroke: red

    svg
        margin: 25px 0 25px 0
        
        .wall
            stroke: black
            fill: #fff 
            stroke-width: 5
        .separators
            stroke: black
            fill: none
            stroke-width: 5
        .separate-room
            stroke: black
            fill: none
            stroke-width: 5

        .seat
            stroke: grey
            cursor: pointer
            fill: #fff 
            transition: all 250ms
            stroke-width: 4
            &amp;.selected
                fill: $successColour
                stroke-width: 1
</pre>
<h2>ControlValueAccessor hinzufügen</h2>
<p>Wir können zwar schon unseren Büroplan in der Reactive Form sehen, aber er ist noch ohne Funktion. Um das zu ändern wird ein ControlValueAccessor benötigt. Der ControlValueAccessor dient als Schnittstelle für eine benutzerdefinierte Formularsteuerung, die sich in Angular Formulare integrieren lässt. Der ControlValueAccessor verlangt nach diesen vier Methoden:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="typescript" data-enlighter-theme="atomic">interface ControlValueAccessor {
  writeValue(obj: any): void
  registerOnChange(fn: any): void
  registerOnTouched(fn: any): void
  setDisabledState(isDisabled: boolean)?: void
}</pre>
<p>Mehr Informationen zum ControlValueAccessor finden sich in der Doku: <a href="https://angular.io/api/forms/ControlValueAccessor">https://angular.io/api/forms/ControlValueAccessor</a></p>
<p>Damit die seats.component.ts Komponente Zugriff auf den VALUE_ACCESSOR erhält müssen wir folgenden Provider konfigurieren:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="typescript" data-enlighter-theme="atomic">providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() =&gt; SeatsComponent),
      multi: true,
    },
  ],</pre>
<h2>Variablen und Methoden für den Angular Forms ControlValueAccessor</h2>
<p>Der nächste Schritt sieht komplizierter aus, als er eigentlich ist. Im Prinzip handelt es sich um feste Variablen und Methoden, die jedes custom Formularfeld benötigt.</p>
<p>Jedes Custom reactive Form braucht die nachfolgenden Variablen. Die Variable onTouched, damit das Formularfeld später zu touched werden kann. Außerdem onChange und die statische Variable touched als boolean, um zu wissen ob das Ganze schon touched ist. Ein Formularfeld kann auch disabled sein, daher wird ebenfalls die statische Variable isDisabled als boolean benötigt. Selbstverständlich braucht jedes custom Formular auch einen value.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="typescript" data-enlighter-theme="atomic">public onTouched = () =&gt; {};
public onChange = (value: string) =&gt; {};
public touched: boolean = false;
public isDisabled: boolean = false;
public value: string = '';</pre>
<p>Jedes Custom Reactive Form braucht auch die Möglichkeit diese Variablen zu verändern. Hier kommt die bereits im vorherigen Abschnitt besprochenen ControlValueAccessor Methoden zum Einsatz. Zum einen die writeValue Funktion, die der value Variable einen neuen Wert gibt. Die Funktion markAsTouched ruft onTouched auf und setzt die Variable touched auf true, falls das custom Formularfeld vorher noch nicht angeklickt wurde. Zum Schluss werden noch die beiden Methoden registerOnChange und registerOnTouched benötigt. Beide updaten eigentlich nur ihren jeweiligen Wert. Die Funktion registerOnChange updated den Wert onChange und registerOnTouched updated den Wert onTouched. Das reactive Form Module kann auch ganze Formulare disablen. Damit auch das custom reactive Formfeld darauf hört, wird die Methode setDisabledState genutzt. Mit dieser Methode kann die Variable isDisabled verändert werden.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="typescript" data-enlighter-theme="atomic">writeValue(value: string): void {
  this.value = value;
}
markAsTouched(): void {
  if (!this.touched) {
    this.onTouched();
    this.touched = true;
  }
}
registerOnChange(onChange: any): void {
  this.onChange = onChange;
}
registerOnTouched(onTouched: any): void {
  this.onTouched = onTouched;
}

setDisabledState(isDisabled: boolean): void {
  this.isDisabled = isDisabled;
}</pre>
<h2>SVG Gruppen mit den Formularvariablen verknüpfen</h2>
<p>Damit der SVG Büroplan auch funktional nutzbar ist brauchen wir noch die Möglichkeit einzelne Arbeitsplätze auszuwählen. Hierfür nutzen wir die Angular Bindingmöglichkeiten. Eine Funktion isSelected() wird benötigt, um das Sytling des Arbeitsplatzes zu erkenn bzw. zu verändern.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="html" data-enlighter-theme="atomic">[class.selected]="isSelected('seat-0')"
(click)="select('seat-0')"</pre>
<p>Außerdem eine Funktion select(), welche den ausgewählten Platz anhand eines Namens ( „seat-1“, „seat-2“…) erkennt. Innerhalb dieser select() Funktion wird zuerst überprüft, ob das Formular allgemein disabled ist. Wenn das nicht der Fall ist überprüft eine zweite Abfrage, ob der Platz bereits ausgewählt wurde. Falls das der Fall ist wird der value zu nichts, ansonsten wird der value zu dem Namen des Arbeitsplatzes, also beispielsweise „seat-3“.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="typescript" data-enlighter-theme="atomic">public select(option: string): void {
  if (!this.isDisabled) {
    if (this.isSelected(option)) {
      this.value = '';
    } else {
      this.value = option;
    }
    this.onChange(this.value);
    this.markAsTouched();
  }
}

public isSelected(option: string): boolean {
  return option === this.value;
}</pre>
<h2>Dateien zum Herunterladen</h2>
<ul>
<li><a href="https://github.com/hanneslim/Angular-form-with-SVG-options">Projekt auf Github</a></li>
</ul>
<p>The post <a href="https://nerd-corner.com/de/wie-man-eine-reactive-angular-form-svg-mit-klickbaren-elementen-erstellt/">Wie man eine Reactive Angular Form SVG mit klickbaren Elementen erstellt</a> appeared first on <a href="https://nerd-corner.com/de">Nerd Corner</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://nerd-corner.com/de/wie-man-eine-reactive-angular-form-svg-mit-klickbaren-elementen-erstellt/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>Wie man eine Angular Fassade mit PUSH Architektur programmiert</title>
		<link>https://nerd-corner.com/de/wie-man-eine-angular-fassade-mit-push-architektur-programmiert/</link>
					<comments>https://nerd-corner.com/de/wie-man-eine-angular-fassade-mit-push-architektur-programmiert/#comments</comments>
		
		<dc:creator><![CDATA[Nerds]]></dc:creator>
		<pubDate>Sat, 27 Nov 2021 11:04:22 +0000</pubDate>
				<category><![CDATA[Angular-DE]]></category>
		<category><![CDATA[Software-DE]]></category>
		<category><![CDATA[angular]]></category>
		<category><![CDATA[Angular Fassade]]></category>
		<category><![CDATA[Angular PUSH Architektur]]></category>
		<category><![CDATA[angularjs]]></category>
		<category><![CDATA[BehaviourSubject]]></category>
		<category><![CDATA[Facade]]></category>
		<category><![CDATA[Facade Pattern]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[Observable]]></category>
		<category><![CDATA[PULL]]></category>
		<category><![CDATA[PUSH]]></category>
		<category><![CDATA[PUSH Architektur]]></category>
		<category><![CDATA[rxjs]]></category>
		<category><![CDATA[rxjs observables]]></category>
		<category><![CDATA[typescript]]></category>
		<guid isPermaLink="false">https://nerd-corner.com/?p=1100</guid>

					<description><![CDATA[<p>Typischerweise wird in der Softwareentwicklung eine PULL basierte Architektur verwendet und nur selten eine PUSH Architektur. Um Daten von einem Server anzufragen wird ein Service &#8230; </p>
<p>The post <a href="https://nerd-corner.com/de/wie-man-eine-angular-fassade-mit-push-architektur-programmiert/">Wie man eine Angular Fassade mit PUSH Architektur programmiert</a> appeared first on <a href="https://nerd-corner.com/de">Nerd Corner</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>Typischerweise wird in der Softwareentwicklung eine PULL basierte Architektur verwendet und nur selten eine PUSH Architektur. Um Daten von einem Server anzufragen wird ein Service aufgerufen, welcher anschließend einen Request an den Server schickt und die benötigten Daten erhält. Während dieses Prozesses ist der Main UI Thread aber blockiert.</p>
<p>Angular selbst überprüft regelmäßig das View Model auf Änderungen. Sollte sich aufgrund einer State Änderung das View Model verändern, rendert Angular die View neu, weil Angular automatisch das UI anpasst, wenn sich das Model verändert. Durch diese kontinuierlichen Beobachtungen wird die ganze Anwendung langsamer.</p>
<p>Zudem steigt mit dem traditionellen Pull Ansatz die Komplexität. Besonders, wenn mehrere Views dieselben Daten benötigen. Was passiert, wenn sich die Daten ändern? Wie werden die Views darüber informiert, dass neue Daten verfügbar sind? Callback Funktionen könnten helfen, aber dann wird die Anwendung schnell unübersichtlich. Außerdem wirken sie sich besonders bei größeren Anwendungen negativ auf die Handhabung und Wartung aus. Schließlich müssen die Objekte einzeln initialisiert werden und die Abhängigkeiten bzw. die Reihenfolge der Methoden beachtet werden.</p>
<p>Um dieses Problem zu umgehen kann eine Push basierte Architektur genutzt werden. Angular bietet hier mit RxJS und dem Fassaden Entwurfsmuster eine hervorragende Möglichkeit zur Implementierung! Die Implementierung wird anhand eines Angular Beispiels in diesem Blog Artikel erläutert und ist für alle Plattformen und Frameworks (Angular, React, Vue.js, usw.) übertragbar.</p>
<h2>Liste der Komponenten:</h2>
<ul>
<li>Entwicklungsumgebung</li>
<li>Angular CLI</li>
</ul>
<p>&nbsp;</p>
<p><img loading="lazy" decoding="async" class="aligncenter wp-image-1110 zoooom" src="https://nerd-corner.com/wp-content/uploads/2021/11/RandomBeerPUSH.png" alt="Push based Architecture Facade Service example" width="650" height="443" srcset="https://nerd-corner.com/wp-content/uploads/2021/11/RandomBeerPUSH.png 659w, https://nerd-corner.com/wp-content/uploads/2021/11/RandomBeerPUSH-300x204.png 300w" sizes="auto, (max-width: 650px) 100vw, 650px" /></p>
<h2>Was ist eine Fassade eigentlich?</h2>
<p>Das Fassaden Entwurfsmuster ist ein zentrales Interface, welches mit verschiedenen Schnittstellen eines oder mehrerer untergeordneter Systeme kommuniziert. Es kann je nach Bedarf zusätzliche Funktionen sowohl vor als auch nach einer Client Anfrage ausführen.</p>
<p>Das Fassaden Objekt bietet eine einheitliche und meist vereinfachte Schnittstelle zu einer Menge von Schnittstellen eines Subsystems.</p>
<p>Es sorgt als Vermittler dafür, dass die Kommunikation bzw. der Zugriff auf die einzelnen Komponenten eines Subsystems vereinfacht und damit auch die direkte Abhängigkeit von diesen Komponenten minimiert wird. Es delegiert die Clientaufrufe so, dass Clients weder die Klassen noch ihre Beziehungen und Abhängigkeiten kennen müssen.</p>
<p>Das ist besonders in großen Systemen hilfreich, wenn ein Subsystem viele technisch orientierte Klassen enthält, die selten von außen verwendet werden. Die Verwendung einer Fassade senkt die Komplexität, da mehrere Schnittstellen zu einer zusammengefasst werden. Außerdem kann das Subsystem durch die lose Kopplung leichter erweitert werden.</p>
<h2>Die PUSH Architektur</h2>
<p>Für eine Push basierte Architektur können fortgeschrittene Entwurfsmuster wie Redux oder NgRx verwendet werden. Allerdings kann auch eine sehr elegante und performante push basierte Lösung mit RxJS erreicht werden.</p>
<p>Mit Hilfe der lang lebigen RxJS Observable streams können Veränderungen der Daten an alle Subscriber gepusht werden. Dazu subscriben die Views dem gewünschten Daten Stream. Verändern sich dann die Daten, werden die Änderungen über den Stream direkt an alle Subscriber gepusht ohne das der UI Thread geblockt wird.</p>
<p>Auf diese Art und Weise wird ein direkter Datenzugang verhindert und die Daten sind read-only. Die eigentliche Datenquelle wird ähnlich wie bei einer API angesprochen, die von den Views verwendet wird. Als zentrale Schnittstelle dient hier die eingangs beschriebene Fassade. Diese besteht aus Streams, die Daten liefern, wenn sich die Daten ändern und Methoden, um Änderungen an den Daten anzufordern oder bestimmte benutzerdefinierte Streams anzufordern.</p>
<p>Die eigentlichen Rohdaten sind erst verfügbar, nachdem sie durch den/die Stream(s) gepusht worden sind. Diese Abschirmung zentralisiert die gesamte Logik und zwingt die Views passiv auf die eingehenden Daten zu reagieren. Mit den Push basierten Diensten werden Angular View Komponenten hoch performant und nutzen dabei sowohl ChangeDetectionStrategy.OnPush, als auch die async Pipe für die gelieferten Stream Daten.</p>
<p>Daher ist das System lazy loading. Das UI-Framework braucht nicht nach Zustandsänderungen des View-Modells suchen und wartet stattdessen lazy darauf, dass ein neuer Zustand gepusht wird.</p>
<h2>Praxisbeispiel</h2>
<p>Als Beispiel für eine Push basierte Architektur dient eine einfache Anwendung, welche über den HTTP Request <a href="https://random-data-api.com/api/beer/random_beer?size=1">https://random-data-api.com/api/beer/random_beer?size=1</a> eine zufällige Bier Sorte erhält. Eine View zeigt die Biersorte an. Über ein kleines Input Feld kann die Anzahl der anzuzeigenden Biere variiert werden. Eine Fassade als zentrales Element pusht automatisiert die neuen Biersorten an die View, sobald diese verfügbar sind.</p>
<p><img loading="lazy" decoding="async" class="aligncenter wp-image-1120 zoooom" src="https://nerd-corner.com/wp-content/uploads/2021/11/facade_explanation-1.png" alt="PUSH vs PULL based architecture facade design Angular" width="1275" height="717" srcset="https://nerd-corner.com/wp-content/uploads/2021/11/facade_explanation-1.png 1280w, https://nerd-corner.com/wp-content/uploads/2021/11/facade_explanation-1-300x169.png 300w, https://nerd-corner.com/wp-content/uploads/2021/11/facade_explanation-1-1024x576.png 1024w, https://nerd-corner.com/wp-content/uploads/2021/11/facade_explanation-1-768x432.png 768w" sizes="auto, (max-width: 1275px) 100vw, 1275px" /></p>
<p>&nbsp;</p>
<p>Als Vorbild für das Entwurfsmuster dient der Artikel von Thomas Burleson: <a href="https://thomasburlesonia.medium.com/push-based-architectures-with-rxjs-81b327d7c32d">https://thomasburlesonia.medium.com/push-based-architectures-with-rxjs-81b327d7c32d</a></p>
<h3>Als erstes muss das State Management aufgesetzt werden:</h3>
<pre class="EnlighterJSRAW" data-enlighter-language="js" data-enlighter-theme="atomic">export interface Beer {
  brand: string;
  name: string;
  style: string;
  hop: string;
  alcohol: string;
}

export interface BeerState {
  beerArray: Beer[];
  size: number;
}</pre>
<h3>Als nächstes initialisieren wir die Werte des Statemanagements der Fassade:</h3>
<pre class="EnlighterJSRAW" data-enlighter-language="js" data-enlighter-theme="atomic">export interface BeerState {
  beerArray: Beer[];
  size: number;
}

let _state: BeerState = {
  beerArray: [],
  size: 4
};</pre>
<h3>Die Fassade nutzt RxJS Streams um Daten direkt an die Views zu pushen. Diese Streams können auch automatisiert bei einer Änderung der States einen Rest API call ausführen:</h3>
<pre class="EnlighterJSRAW" data-enlighter-language="js" data-enlighter-theme="atomic">combineLatest(this.size$)
      .pipe(
        //switchMap: Maps values to observable. Cancels the previous inner observable.
        switchMap(([size]) =&gt; {
          return this.findBeerArray(size);
        })
      )
      .subscribe((beerArray) =&gt; {
        this.updateState({ ..._state, beerArray });
      });</pre>
<h3>Die Streams werden so aufgebaut, dass sie dauerhaft erhalten bleiben und nur bei einer Datenänderung aktiv werden:</h3>
<pre class="EnlighterJSRAW" data-enlighter-language="js" data-enlighter-theme="atomic">export class BeerFacade {

  beerArray$ = this.state$.pipe(
    map((state) =&gt; state.beerArray),
    distinctUntilChanged()
  );

  size$ = this.state$.pipe(
    map((state) =&gt; state.size),
    distinctUntilChanged()
  );

  private updateState(state: BeerState) {
    this.store.next((_state = state));
  }
}
</pre>
<h3>Für die bessere Handhabung werden die Streams zu einem Einzigen zusammengefasst:</h3>
<pre class="EnlighterJSRAW" data-enlighter-language="js" data-enlighter-theme="atomic">vm$: Observable&lt;BeerState&gt; = combineLatest(this.beerArray$, this.size$).pipe(
    map(([beerArray, size]) =&gt; {
      return { beerArray, size };
    })
  );</pre>
<h5>Der HTTP Request kann in einen seperaten Data Service ausgelagert werden. Da es sich hierbei aber nur um ein kleines Beispielprogramm handelt, wird der Restcall innerhalb der Facade implementiert und ausgeführt:</h5>
<pre class="EnlighterJSRAW" data-enlighter-language="js" data-enlighter-theme="atomic">/** RandomBeer REST call */
  private findBeerArray(size: number): Observable&lt;Beer[]&gt; {
    const url = `https://random-data-api.com/api/beer/random_beer?size=${size}`;
    return this.http.get&lt;Beer[]&gt;(url);
  }</pre>
<h3>Die Fassade und der View Model Stream können nun einfach initialisiert und genutzt werden. Es ist unglaublich wenig Code notwendig:</h3>
<pre class="EnlighterJSRAW" data-enlighter-language="js" data-enlighter-theme="atomic">export class AppComponent {
  vm$: Observable&lt;BeerState&gt; = this.facadeService.vm$;

  //facadeService is public for direct usage in html
  constructor(public facadeService: RandomBeerFacadeService) {} 
  title = 'RandomBeerApp';
}</pre>
<h3>Die Daten des View Model Streams werden mit folgendem Codeschnippsel angezeigt:</h3>
<pre class="EnlighterJSRAW" data-enlighter-language="js" data-enlighter-theme="atomic">&lt;div *ngIf="vm$ | async as vm"&gt;  
  &lt;ul&gt;
    &lt;li id="random-beer-list" *ngFor="let u of vm.beerArray"&gt;
      Brand: {{ u.brand }}, Alcohol: {{ u.alcohol }}, Hop: {{ u.hop }}
    &lt;/li&gt;
  &lt;/ul&gt;
  &lt;input id="input-rand-beer" type="number" (change)="facadeService.updateSize($event)" /&gt;
&lt;/div&gt;</pre>
<p>Dabei sorgt die Async Pipe von Angular dafür, dass immer die aktuellsten Daten angezeigt werden. <a href="https://angular.io/api/common/AsyncPipe" target="_blank" rel="noopener">Hier findet sich eine genaue Erklärung der Async Pipe.</a></p>
<h5>Da der Fassaden Service als public deklariert wurde, werden die Inputs direkt an die updateSize Funktion der Fassade weitergegeben:</h5>
<pre class="EnlighterJSRAW" data-enlighter-language="js" data-enlighter-theme="atomic">updateSize(selectedSize: any) {
    const size = selectedSize.target.value;
    this.updateState({ ..._state, size });
  }</pre>
<p>Als Resultat zeigt die Applikation zunächst 4 zufällige Biere an. Anschließend kann durch das Input Feld die Anzahl auf 7 erhöht werden. Dadurch wird die Funktion updateSize() aufgerufen, welche mittels updateState() den State des size$ streams aktualisiert. Als Folge dessen wird automatisiert ein neuer REST call ausgeführt und das Ergebnis an das View Model gepusht. Mit Hilfe der async Pipe werden somit die neusten Daten angezeigt. In unserem Fall die 7 zufälligen Biersorten.</p>
<p><img loading="lazy" decoding="async" class="aligncenter wp-image-1110 zoooom" src="https://nerd-corner.com/wp-content/uploads/2021/11/RandomBeerPUSH.png" alt="Push based Architecture Facade Service example" width="650" height="443" srcset="https://nerd-corner.com/wp-content/uploads/2021/11/RandomBeerPUSH.png 659w, https://nerd-corner.com/wp-content/uploads/2021/11/RandomBeerPUSH-300x204.png 300w" sizes="auto, (max-width: 650px) 100vw, 650px" /></p>
<h3>Abschließend macht es Sinn die Funktionalität der einzelnen Fassaden Bestandteile zu testen:</h3>
<pre class="EnlighterJSRAW" data-enlighter-language="js" data-enlighter-theme="atomic">it('should get individual Observable "stream" of vm data', (done) =&gt; {
    testFacade.vm$.subscribe((vm) =&gt; {
      expect(vm.size).toEqual(initStateMock.size);
      done();
    });
  });

  it('should update state values', (done) =&gt; {
    const updatedStateMock: TestBeerState = {
      beerArray: [
        {
          brand: 'Pabst Blue Ribbon',
          name: 'Two Hearted Ale',
          style: 'Merican Ale',
          hop: 'Sorachi Ace',
          alcohol: '2,9%',
        },
        {
          brand: 'Bud Light',
          name: 'La fin Du Monde',
          style: 'Stout',
          hop: 'Bullion',
          alcohol: '2,7%',
        },
      ],
      size: 2,
    };
    testFacade['updateState'](updatedStateMock);
    testFacade.vm$.subscribe((vm) =&gt; {
      expect(vm).toEqual(updatedStateMock);
      done();
    });
  });

  it('should update the size value', (done) =&gt; {
    const newSize = 9;
    const mockEvent = {
      target: {
        value: newSize,
      },
    };
    testFacade['updateSize'](mockEvent);
    testFacade.vm$.subscribe((vm) =&gt; {
      expect(vm.size).toEqual(newSize);
      done();
    });
  });

  it('should perform a mocked http request', (done) =&gt; {
    const httpMock: HttpTestingController = TestBed.inject(
      HttpTestingController
    );

    const mockResponse = {
      brand: 'Pabst Blue Ribbon',
      name: 'Two Hearted Ale',
      style: 'Merican Ale',
      hop: 'Sorachi Ace',
      alcohol: '2,9%',
    };

    testFacade['findBeerArray'](1);
    testFacade.vm$.subscribe((tb) =&gt; {
      expect(tb.beerArray).toBeTruthy();
      expect(tb.beerArray[0].brand).toBe(mockResponse.brand);
      expect(tb.beerArray[0].name).toBe(mockResponse.name);
      expect(tb.beerArray[0].style).toBe(mockResponse.style);
      expect(tb.beerArray[0].hop).toBe(mockResponse.hop);
      expect(tb.beerArray[0].alcohol).toBe(mockResponse.alcohol);

      done();
    });

    const mockRequest = httpMock.expectOne(
      'https://random-data-api.com/api/beer/random_beer?size=1'
    );
    mockRequest.flush(mockResponse);
  });</pre>
<h2>Dateien zum Herunterladen</h2>
<ul>
<li><a href="https://github.com/hanneslim/RandomBeerApp">Source Code in GitHub</a></li>
</ul>
<p>The post <a href="https://nerd-corner.com/de/wie-man-eine-angular-fassade-mit-push-architektur-programmiert/">Wie man eine Angular Fassade mit PUSH Architektur programmiert</a> appeared first on <a href="https://nerd-corner.com/de">Nerd Corner</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://nerd-corner.com/de/wie-man-eine-angular-fassade-mit-push-architektur-programmiert/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
	</channel>
</rss>
