DEV Community: Julian Finkler The latest articles on DEV Community by Julian Finkler (@devtronic). https://dev.to/devtronic https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F251716%2F0220c5b6-38fa-4af5-95bf-a11024c3be20.jpeg DEV Community: Julian Finkler https://dev.to/devtronic en Sign Android apps using 1Password Julian Finkler Thu, 20 Nov 2025 13:05:22 +0000 https://dev.to/devtronic/sign-android-apps-using-1password-1meh https://dev.to/devtronic/sign-android-apps-using-1password-1meh <p>German is also available: <a href="proxy.php?url=https://dev.to/devtronic/android-apps-mithilfe-von-1password-signiere-472o">https://dev.to/devtronic/android-apps-mithilfe-von-1password-signiere-472o</a></p> <p>Android developers know: If you want to upload an app to the Google Play Store, the app bundle or APK must be signed with a key.</p> <p>As an individual developer, the process is relatively straightforward:</p> <ol> <li>Create a Java Key Store (JKS)</li> <li>Add a signing key to the JKS</li> <li>Create a <code>key.properties</code> file containing details such as the key alias and password</li> <li>Use this to sign the app</li> </ol> <h2> The JKS should not be committed </h2> <p>From a security perspective, neither the JKS nor the properties file should be committed to a VCS. This introduces several challenges:</p> <ul> <li>I need to ensure the JKS is stored in a secure location to avoid loss.</li> <li>When working in a team, every developer must store the JKS and properties file locally.</li> <li>If a new key is added to the JKS, all other developers must update their file accordingly.</li> </ul> <p>This can become very tedious, which is why I looked for an alternative, better approach that avoids dealing with files.</p> <h2> 1Password to the rescue </h2> <p>1Password is primarily a password manager. However, over the years, especially for developers, quite a few useful features have been added. One of them is the 1Password CLI – a very practical tool that allows you to retrieve data from 1Password.</p> <p>That’s when I had an idea: Why store and exchange these highly sensitive files manually if we can manage them centrally in 1Password instead? I hoped for the following advantages:</p> <ul> <li>A single location to store the information</li> <li>Gradle reads the information directly from 1Password – no need to place files manually</li> <li>New keys are automatically available to all developers when the JKS in 1Password is updated</li> <li>No sensitive data in the repository (especially relevant in the age of AI agents)</li> </ul> <h2> Setup Instructions </h2> <h3> 1Password CLI </h3> <p>First, install the 1Password CLI (<code>op</code>). You can find out how in the official documentation: <a href="proxy.php?url=https://developer.1password.com/docs/cli/get-started/" rel="noopener noreferrer">https://developer.1password.com/docs/cli/get-started/</a></p> <p>Then run the command <strong><code>op signin</code></strong> in your terminal to log in.</p> <h3> Create the JKS </h3> <p>If you don’t have a JKS yet, you can create one easily in Android Studio via<br><br> <strong><em>Build &gt; Generate Signed App Bundle or APK</em></strong>.<br><br> It’s important to write down the values you enter there — we will transfer them to 1Password in the next step.</p> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2Fv2%2FD4E12AQGQ0UpaPDvHHg%2Farticle-inline_image-shrink_1000_1488%2FB4EZqg9YEIJ0AQ-%2F0%2F1763637036330%3Fe%3D1765411200%26v%3Dbeta%26t%3Dd3Umz25XnGyF_jLY_JqihlXph6DF2vmxt3peCea-x8U" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2Fv2%2FD4E12AQGQ0UpaPDvHHg%2Farticle-inline_image-shrink_1000_1488%2FB4EZqg9YEIJ0AQ-%2F0%2F1763637036330%3Fe%3D1765411200%26v%3Dbeta%26t%3Dd3Umz25XnGyF_jLY_JqihlXph6DF2vmxt3peCea-x8U" alt="JKS Setup in Android Studio" width="729" height="989"></a></p> <p>Creating a JKS in Android Studio</p> <h3> Store the information in 1Password </h3> <p>Before Gradle can load the information from 1Password, an entry must exist.</p> <p>I first created a new vault (name: <code>AndroidDevelopment</code>). Then I created a new entry of type <strong>password</strong>.</p> <p>As a naming convention, I use something URL-safe. Username and password can be left empty. Instead, add the following fields:</p> <ul> <li> <code>KEY_ALIAS</code> = Text → Enter the key alias</li> <li> <code>KEYSTORE_PASSWORD</code> = Password → Password of the JKS</li> <li> <code>KEY_PASSWORD</code> = Password → Password of the key</li> <li>Finally, add the <code>keystore.jks</code> file as an attachment (the filename will be used exactly as-is)</li> </ul> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2Fv2%2FD4E12AQEgR-mJVCyJGQ%2Farticle-inline_image-shrink_1500_2232%2FB4EZqhBLNYKYAY-%2F0%2F1763638032064%3Fe%3D1765411200%26v%3Dbeta%26t%3Dq-9Q4aJrcWzAPoiqrKFV6A4RDw5EM7fI558Te5ArrME" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2Fv2%2FD4E12AQEgR-mJVCyJGQ%2Farticle-inline_image-shrink_1500_2232%2FB4EZqhBLNYKYAY-%2F0%2F1763638032064%3Fe%3D1765411200%26v%3Dbeta%26t%3Dq-9Q4aJrcWzAPoiqrKFV6A4RDw5EM7fI558Te5ArrME" alt="Signature data in 1Password" width="615" height="699"></a></p> <p>Signature information created in 1Password</p> <p>You can then save the entry.</p> <h3> Adjust the Gradle script </h3> <p>Finally, Gradle needs to be configured to load data from 1Password using the CLI.</p> <p>I've summarized the required changes in this GitHub Gist. The only thing you need to adapt is line 5 (<code>val secretsItem</code>). Here you need to specify the correct path to your 1Password entry (<code>op://VAULT_NAME/ENTRY_NAME</code>). Apart from that, you can use the script 1:1.</p> <p><a href="proxy.php?url=https://gist.github.com/devtronic/8db7a0a8607eabce0afb97c55fd60819" rel="noopener noreferrer">https://gist.github.com/devtronic/8db7a0a8607eabce0afb97c55fd60819</a></p> <h4> How it works </h4> <p>The core of the solution is the <code>_runOp_</code> function, which communicates with the 1Password CLI. Additionally, there are two tasks:</p> <ul> <li> <code>prepareKeystore</code> → Loads the JKS from 1Password and places the file in the correct location</li> <li> <code>cleanupKeystore</code> → Deletes the JKS right after the build process</li> </ul> <p>Hooks for <code>assembleRelease</code> and <code>bundleRelease</code> are then configured to trigger the tasks.</p> <p>The variables <code>keystorePassword</code>, <code>keyAliasValue</code>, and <code>keyPasswordValue</code> are loaded accordingly and later used in the signingConfigs.</p> <h3> Build the app </h3> <p>Now you’re ready to build the app. During the build, you’ll see the familiar 1Password popup asking you to grant access.</p> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2Fv2%2FD4E12AQG1mFhrkJtjCg%2Farticle-inline_image-shrink_1000_1488%2FB4EZqhENz0IoAQ-%2F0%2F1763638828750%3Fe%3D1765411200%26v%3Dbeta%26t%3DWeoppLjU4usxhd8SQBKoDxiwz25yaEh_668UKxCRhmQ" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2Fv2%2FD4E12AQG1mFhrkJtjCg%2Farticle-inline_image-shrink_1000_1488%2FB4EZqhENz0IoAQ-%2F0%2F1763638828750%3Fe%3D1765411200%26v%3Dbeta%26t%3DWeoppLjU4usxhd8SQBKoDxiwz25yaEh_668UKxCRhmQ" alt="Grant access to 1Password" width="383" height="184"></a></p> <p>Grant access to 1Password</p> <h2> Conclusion </h2> <p>With just a few steps, this method ensures that signing Android apps remains simple even in larger teams. Additionally, it allows secure signing within CI/CD pipelines, without needing to commit the JKS or load it via workarounds.</p> android automation appdev security Android Apps mithilfe von 1Password signieren Julian Finkler Thu, 20 Nov 2025 12:58:50 +0000 https://dev.to/devtronic/android-apps-mithilfe-von-1password-signiere-472o https://dev.to/devtronic/android-apps-mithilfe-von-1password-signiere-472o <p>(Zuerst erschienen auf: <a href="proxy.php?url=https://www.linkedin.com/pulse/android-apps-mithilfe-von-1password-signieren-julian-finkler-erlce" rel="noopener noreferrer">https://www.linkedin.com/pulse/android-apps-mithilfe-von-1password-signieren-julian-finkler-erlce</a>)</p> <p>Android Entwickler wissen: Wenn man eine App in den Google Play Store hochladen möchte, muss das App Bundle bzw. das APK mit einem Schlüssel signiert sein.</p> <p>Als einzelner Entwickler ist der Prozess relativ trivial:</p> <ol> <li>Man legt einen Java Key Store, kurz JKS, an</li> <li>Man legt in diesem JKS einen Schlüssel zum signieren ab.</li> <li>Man legt eine key.properties Datei an, in der die die Details, wie Schlüssel oder das Passwort des Schlüssels stehen.</li> <li>Man signiert die App damit.</li> </ol> <h2> Der JKS sollte nicht eingecheckt werden </h2> <p>Aus Sicht der Sicherheit sollten natürlich sowohl der JKS als auch die Properties-Datei nicht in ein VCS eingecheckt werden. Das bringt eine Reihe von Herausforderungen mit sich:</p> <ul> <li>Ich muss mich darum kümmern, dass der JKS an einem sicheren Ort gegen Verlust gesichert wird.</li> <li>Wenn im Team gearbeitet wird, muss jeder Entwickler bei sich den JKS sowie die Properties Datei hinterlegen.</li> <li>Wenn ein Entwickler im JKS einen neuen Schlüssel hinzufügt müssen alle anderen die Datei aktualisieren.</li> </ul> <p>Das ganze kann mitunter sehr mühselig sein, weswegen ich einen alternativen, besseren Weg gesucht habe, als mit Dateien zu hantieren.</p> <h2> 1Password zur Hilfe </h2> <p>1Password ist in erster Linie ein Passwort Manager. Im Laufe der Jahre sind aber, gerade für Entwickler, ziemlich praktische Funktionen dazu gekommen. Darunter ist die 1Password CLI ein sehr praktisches Werkzeug, mit dem man unter anderem Daten aus 1Password lesen kann.</p> <p>So kam mir die Idee: Warum sollte man diese hochsensiblen Daten irgendwo ablegen und austauschen, wenn man es auch zentral in 1Password tun kann. Folgende Vorteile erhoffte ich mir:</p> <ul> <li>Ein Ort, an dem die Information liegt.</li> <li>Gradle liest die Informationen direkt aus 1Password, ohne dass die Dateien von Hand abgelegt werden müssen.</li> <li>Neue Schlüssel stehen automatisch allen Entwicklern zur Verfügung, wenn jemand den JKS in 1Password aktualisiert.</li> <li>Keine sensiblen Daten im Repository (Gerade in Zeiten von KI Agenten ist das relevanter denn je)</li> </ul> <h2> Anleitung zum Einrichten </h2> <h3> 1Password CLI </h3> <p>Zuerst solltest du die 1Password CLI (<em>op</em>) installieren. Wie das geht, erfährst du in der offiziellen Doku: <a href="proxy.php?url=https://developer.1password.com/docs/cli/get-started/" rel="noopener noreferrer">https://developer.1password.com/docs/cli/get-started/</a></p> <p>Anschließend führst du auf der Kommandozeile den Befehl <strong>op signin</strong> aus, um dich einzuloggen.</p> <h3> JKS anlegen </h3> <p>Falls du noch keinen JKS hast, kannst du ihn in Android Studio ganz einfach über <strong><em>Build &gt; Generate Signed App Bundle or APK</em></strong> anlegen. Wichtig ist, dass du dir die Werte dieser Felder irgendwo notierst, diese übernehmen wir im nächsten Schritt in 1Password.</p> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2Fv2%2FD4E12AQGQ0UpaPDvHHg%2Farticle-inline_image-shrink_1000_1488%2FB4EZqg9YEIJ0AQ-%2F0%2F1763637036330%3Fe%3D1765411200%26v%3Dbeta%26t%3Dd3Umz25XnGyF_jLY_JqihlXph6DF2vmxt3peCea-x8U" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2Fv2%2FD4E12AQGQ0UpaPDvHHg%2Farticle-inline_image-shrink_1000_1488%2FB4EZqg9YEIJ0AQ-%2F0%2F1763637036330%3Fe%3D1765411200%26v%3Dbeta%26t%3Dd3Umz25XnGyF_jLY_JqihlXph6DF2vmxt3peCea-x8U" alt="Artikelinhalte" width="729" height="989"></a></p> <p>JKS in Android Studio anlegen</p> <h3> Informationen in 1Password hinterlegen </h3> <p>Bevor die Informationen aus 1Password geladen werden können, muss dazu natürlich ein Eintrag existieren.</p> <p>Ich habe dafür zunächst einen neuen Vault (Name: AndroidDevelopment) angelegt. Anschließend legt man darin ein neues Objekt vom Typen Passwort an.</p> <p>Als Name habe ich mir angewöhnt, etwas URL-sicheres zu vergeben. Benutzername und Passwort können leer bleiben. Stattdessen fügen wir folgende Felder hinzu:</p> <ul> <li>KEY_ALIAS = Text. Dort wird der Alias des Schlüssel eingegeben</li> <li>KEYSTORE_PASSWORD = Passwort. Dort wird das Passwort des JKS eingegeben</li> <li>KEY_PASSWORD = Passwort. Dort wird das Passwort des Schlüssels eingegeben</li> <li>Und als letztes wird noch die keystore.jks als Anhand hinzugefügt. (Der Dateiname wird später genau so weiterverwendet)</li> </ul> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2Fv2%2FD4E12AQEgR-mJVCyJGQ%2Farticle-inline_image-shrink_1500_2232%2FB4EZqhBLNYKYAY-%2F0%2F1763638032064%3Fe%3D1765411200%26v%3Dbeta%26t%3Dq-9Q4aJrcWzAPoiqrKFV6A4RDw5EM7fI558Te5ArrME" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2Fv2%2FD4E12AQEgR-mJVCyJGQ%2Farticle-inline_image-shrink_1500_2232%2FB4EZqhBLNYKYAY-%2F0%2F1763638032064%3Fe%3D1765411200%26v%3Dbeta%26t%3Dq-9Q4aJrcWzAPoiqrKFV6A4RDw5EM7fI558Te5ArrME" alt="Artikelinhalte" width="615" height="699"></a></p> <p>Signatur Informationen in 1Password anlegen</p> <p>Anschließend kann der Eintrag gespeichert werden.</p> <h3> Gradle Script anpassen </h3> <p>Zum schluss muss man Gradle noch dazu bringen, die Daten mittels 1Password CLI aus 1Password zu laden.</p> <p>Die Anpassungen habe ich in diesem Gist auf GitHub zusammengefasst. Das Einzige was von dir noch angepasst werden muss ist Zeile 5 (<strong>val secretsItem</strong>). Dort musst du den bei dir richtigen Pfad zum oben angelegten Eintrag in 1Password hinterlegen (<strong>op://VAULT_NAME/NAME_DES_EINTRAGS</strong>). Ansonsten kannst du das Script 1:1 verwenden.</p> <p><a href="proxy.php?url=https://gist.github.com/devtronic/8db7a0a8607eabce0afb97c55fd60819" rel="noopener noreferrer">https://gist.github.com/devtronic/8db7a0a8607eabce0afb97c55fd60819</a></p> <p>Funktionsweise</p> <p>Kernstück des Ganzen ist die <em>runOp</em> Funktion, welche mit der 1Password CLI kommuniziert. Darüber hinaus gibt es noch zwei Tasks:</p> <ul> <li>prepareKeystore -&gt; Lädt den JKS aus 1Password und legt die Datei an der richtigen Stelle ab</li> <li>cleanupKeystore -&gt; Löscht den JKS direkt nach dem Build Vorgang.</li> </ul> <p>Damit die Tasks ausgeführt werden, werden noch die Hooks für assembleRelease und bundleRelease eingerichtet.</p> <p>In die Variablen keystorePassword, keyAliasValue und keyPasswordValue werden die entsprechenden Werte zum signieren geladen, welche dann später in die signingConfigs übernommen werden.</p> <h3> App bauen </h3> <p>Nun bist du an dem Punkt angekommen, wo du die App bauen darfst. Beim Build Vorgang wirst du das bekannte Fenster von 1Password sehen, wo du den Zugriff auf 1Password genehmigen musst.</p> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2Fv2%2FD4E12AQG1mFhrkJtjCg%2Farticle-inline_image-shrink_1000_1488%2FB4EZqhENz0IoAQ-%2F0%2F1763638828750%3Fe%3D1765411200%26v%3Dbeta%26t%3DWeoppLjU4usxhd8SQBKoDxiwz25yaEh_668UKxCRhmQ" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2Fv2%2FD4E12AQG1mFhrkJtjCg%2Farticle-inline_image-shrink_1000_1488%2FB4EZqhENz0IoAQ-%2F0%2F1763638828750%3Fe%3D1765411200%26v%3Dbeta%26t%3DWeoppLjU4usxhd8SQBKoDxiwz25yaEh_668UKxCRhmQ" alt="Artikelinhalte" width="383" height="184"></a></p> <p>Zugriff auf 1Password genehmigen</p> <h2> Fazit </h2> <p>Mit ein paar Handgriffen kann man mit diesem Vorgehen sicherstellen, dass auch in größeren Teams das signieren von Android Apps einfach gestaltet werden kann. Außerdem besteht somit auch die Möglichkeit, Apps in einer CI/CD Pipeline sicher zu signieren, ohne dass der JKS eingecheckt oder über Umwege geladen werden muss.</p> german android security appdev Install ionCube Loader in Docker Julian Finkler Wed, 06 Nov 2024 07:04:24 +0000 https://dev.to/devtronic/ioncube-loader-in-docker-55n1 https://dev.to/devtronic/ioncube-loader-in-docker-55n1 <p>I wrote something that I need for my projects and I think this could be useful for you too.</p> <p>You can install the ionCube loader automatically during image creation with the following script.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight docker"><code><span class="c"># Change the tag as needed</span> <span class="k">FROM</span><span class="s"> php:8.3-apache</span> <span class="k">COPY</span><span class="s"> --chmod=775 ioncube-loader-setup.sh /tmp/ioncube-loader-setup.sh</span> <span class="k">RUN </span><span class="nb">set</span> <span class="nt">-x</span> <span class="o">&amp;&amp;</span> bash <span class="nt">-c</span> /tmp/ioncube-loader-setup.sh </code></pre> </div> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="c"># ioncube-loader-setup.sh</span> <span class="c">########################</span> <span class="c"># ionCube INSTALLATION #</span> <span class="c">########################</span> <span class="nv">PHP_EXTENSION_DIR</span><span class="o">=</span><span class="si">$(</span>php <span class="nt">-r</span> <span class="s1">'echo ini_get("extension_dir");'</span><span class="si">)</span> <span class="nb">echo</span> <span class="s2">"PHP_EXTENSION_DIR is </span><span class="nv">$PHP_EXTENSION_DIR</span><span class="s2">"</span> <span class="nv">PHP_CONFIG_DIR</span><span class="o">=</span><span class="si">$(</span>php <span class="nt">-i</span> | <span class="nb">grep</span> <span class="nt">-ohP</span> <span class="s2">"with-config-file-scan-dir=([^']+)"</span> | <span class="nb">cut</span> <span class="nt">-d</span><span class="o">=</span> <span class="nt">-f2</span><span class="si">)</span> <span class="nb">echo</span> <span class="s2">"PHP_CONFIG_DIR is </span><span class="nv">$PHP_CONFIG_DIR</span><span class="s2">"</span> <span class="nv">PHP_VERSION</span><span class="o">=</span><span class="si">$(</span>php <span class="nt">-r</span> <span class="s1">'list($major, $minor, $fix) = explode(".", phpversion(), 3); echo $major.".".$minor;'</span><span class="si">)</span> <span class="nb">echo</span> <span class="s2">"PHP_VERSION is </span><span class="nv">$PHP_VERSION</span><span class="s2">"</span> <span class="c"># Detect the correct ionCube loader architecture</span> <span class="nv">IONCUBE_ARCH</span><span class="o">=</span><span class="s2">""</span> <span class="nv">ARCH</span><span class="o">=</span><span class="si">$(</span><span class="nb">uname</span> <span class="nt">-m</span><span class="si">)</span> <span class="k">if</span> <span class="o">[[</span> <span class="nv">$ARCH</span> <span class="o">==</span> x86_64<span class="k">*</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then </span><span class="nv">IONCUBE_ARCH</span><span class="o">=</span><span class="s2">"x86-64"</span> <span class="k">elif</span> <span class="o">[[</span> <span class="nv">$ARCH</span> <span class="o">==</span> i<span class="k">*</span>86 <span class="o">]]</span><span class="p">;</span> <span class="k">then </span><span class="nv">IONCUBE_ARCH</span><span class="o">=</span><span class="s2">"x86"</span> <span class="k">elif</span> <span class="o">[[</span> <span class="nv">$ARCH</span> <span class="o">=</span> aarch64 <span class="o">]]</span><span class="p">;</span> <span class="k">then </span><span class="nv">IONCUBE_ARCH</span><span class="o">=</span><span class="s2">"aarch64"</span> <span class="k">elif</span> <span class="o">[[</span> <span class="nv">$ARCH</span> <span class="o">==</span> arm<span class="k">*</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then </span><span class="nv">IONCUBE_ARCH</span><span class="o">=</span><span class="s2">"armv7l"</span> <span class="k">else </span><span class="nb">echo</span> <span class="s2">"Unsupported ionCube loader architecture!"</span> 1&gt;&amp;2 <span class="nb">exit </span>1 <span class="k">fi </span><span class="nb">echo</span> <span class="s2">"Detected ionCube loader architecture is </span><span class="nv">$IONCUBE_ARCH</span><span class="s2"> based on </span><span class="nv">$ARCH</span><span class="s2">"</span> <span class="nb">echo</span> <span class="s2">"Downloading the loader"</span> curl <span class="nt">-O</span> https://downloads.ioncube.com/loader_downloads/ioncube_loaders_lin_<span class="nv">$IONCUBE_ARCH</span>.tar.gz <span class="nb">echo</span> <span class="s2">"Extracting the loader"</span> <span class="nb">tar</span> <span class="nt">-xzvf</span> ioncube_loaders_lin_<span class="nv">$IONCUBE_ARCH</span>.tar.gz <span class="nv">LOADER_FILE</span><span class="o">=</span><span class="si">$(</span><span class="nb">ls </span>ioncube/<span class="k">*</span>8.3<span class="k">*</span>.so | <span class="nb">cat</span><span class="si">)</span> <span class="nv">LOADER_FILENAME</span><span class="o">=</span><span class="si">$(</span><span class="nb">echo</span> <span class="nv">$LOADER_FILE</span> | <span class="nb">cut</span> <span class="nt">-d</span>/ <span class="nt">-f2</span><span class="si">)</span> <span class="k">if</span> <span class="o">[</span> <span class="o">!</span> <span class="nt">-f</span> <span class="nv">$LOADER_FILE</span> <span class="o">]</span><span class="p">;</span> <span class="k">then </span><span class="nb">echo</span> <span class="s2">"No loader found for PHP </span><span class="nv">$LOADER_LOADER_VERSION</span><span class="s2">"</span> 1&gt;&amp;2 <span class="nb">exit </span>2 <span class="k">fi </span><span class="nb">echo</span> <span class="s2">"Copy loader file </span><span class="nv">$LOADER_FILE</span><span class="s2"> to </span><span class="nv">$PHP_EXTENSION_DIR</span><span class="s2">"</span> <span class="nb">cp</span> <span class="nv">$LOADER_FILE</span> <span class="s2">"</span><span class="nv">$PHP_EXTENSION_DIR</span><span class="s2">"</span> <span class="nb">echo</span> <span class="s2">"Writing PHP config </span><span class="nv">$PHP_CONFIG_DIR</span><span class="s2">/00-ioncube.ini"</span> <span class="nb">cat</span> <span class="o">&lt;&lt;</span><span class="no">EOF</span><span class="sh"> | tee </span><span class="nv">$PHP_CONFIG_DIR</span><span class="sh">/00-ioncube.ini zend_extension=</span><span class="nv">$LOADER_FILENAME</span><span class="sh"> </span><span class="no">EOF </span><span class="c"># Cleanup</span> <span class="nb">rm</span> <span class="nt">-rf</span> ./ioncube <span class="nb">rm </span>ioncube_loaders_lin_<span class="nv">$IONCUBE_ARCH</span>.tar.gz <span class="nb">rm</span> <span class="nt">-f</span> <span class="s2">"</span><span class="k">${</span><span class="nv">0</span><span class="k">}</span><span class="s2">"</span> </code></pre> </div> <p><a href="proxy.php?url=https://gist.github.com/devtronic/e7780dfdb31f4aa6df261739ef987d77" rel="noopener noreferrer">https://gist.github.com/devtronic/e7780dfdb31f4aa6df261739ef987d77</a></p> php docker devops cicd Dependency Injection with Flutter Julian Finkler Fri, 18 Jun 2021 05:57:01 +0000 https://dev.to/devtronic/dependency-injection-with-flutter-54pl https://dev.to/devtronic/dependency-injection-with-flutter-54pl <p>You know dependency injection? You love dependency injection!<br> Unfortunately, Flutter don't provide any built-in DI feature.</p> <p>For this, I created last year the <a href="proxy.php?url=https://pub.dev/packages/flutter_catalyst" rel="noopener noreferrer"><code>flutter_catalyst</code></a> package with is a port of the <a href="proxy.php?url=https://pub.dev/packages/catalyst" rel="noopener noreferrer"><code>catalyst</code></a> package which is only supported for Dart native.</p> <p><code>flutter_catalyst</code> was a good starting point for me to implement DI in my Flutter apps but in large projects it's a mess to configure.</p> <p>In the last two months I created a new package <a href="proxy.php?url=https://pub.dev/packages/catalyst_builder" rel="noopener noreferrer"><code>catalyst_builder</code></a> which supports all platforms and is easy to configure.</p> <p>This package uses the build_runner which performs tasks when you run it. <br> <code>catalyst_builder</code> has a build_runner task that reads annotations from your dart files and generate a service provider for DI.</p> <h2> Setup </h2> <p>Run <code>flutter pub add catalyst_builder</code> or add the package to your <code>pubspec.yaml</code><br> </p> <div class="highlight js-code-highlight"> <pre class="highlight yaml"><code><span class="c1"># pubspec.yaml</span> <span class="na">dependencies</span><span class="pi">:</span> <span class="na">catalyst_builder</span><span class="pi">:</span> <span class="s">^1.0.1</span> </code></pre> </div> <p>Since we use the build_runner you need to add this to your dev_dependencies:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight yaml"><code><span class="c1"># pubspec.yaml</span> <span class="na">dev_dependencies</span><span class="pi">:</span> <span class="na">build_runner</span><span class="pi">:</span> <span class="s">^2.0.4</span> </code></pre> </div> <p>Create a <code>build.yaml</code> beside your <code>pubspec.yaml</code>. This file contains the configuration for the service provider (output file name and provider class name)<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight yaml"><code><span class="na">targets</span><span class="pi">:</span> <span class="na">$default</span><span class="pi">:</span> <span class="na">auto_apply_builders</span><span class="pi">:</span> <span class="kc">true</span> <span class="na">builders</span><span class="pi">:</span> <span class="na">catalyst_builder|buildServiceProvider</span><span class="pi">:</span> <span class="na">options</span><span class="pi">:</span> <span class="na">providerClassName</span><span class="pi">:</span> <span class="s1">'</span><span class="s">AppServiceProvider'</span> <span class="na">outputName</span><span class="pi">:</span> <span class="s1">'</span><span class="s">app_service_provider.dart'</span> </code></pre> </div> <p>Run <code>flutter pub get</code> to install the packages</p> <p>Now run <code>flutter pub run build_runner watch --delete-conflicting-outputs</code> which watches for changes and create the service provider dart file</p> <h2> Usage </h2> <p>You can declare every class as a service with the <code>@Service</code> annotation from the <code>catalyst_builder</code> package:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight dart"><code><span class="nd">@Service</span><span class="p">()</span> <span class="kd">class</span> <span class="nc">MyService</span> <span class="p">{</span> <span class="kd">final</span> <span class="kt">String</span> <span class="n">username</span> <span class="o">=</span> <span class="s">'TestUser'</span><span class="p">;</span> <span class="p">}</span> </code></pre> </div> <p>Ensure that <code>flutter pub run build_runner watch --delete-conflicting-outputs</code> is running. You should see now a <code>app_service_provider.dart</code> file that you can include in your project.</p> <p>Create the service provider and retrieve the service from it:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight dart"><code><span class="kd">var</span> <span class="n">myProvider</span> <span class="o">=</span> <span class="n">AppServiceProvider</span><span class="p">();</span> <span class="n">myProvider</span><span class="o">.</span><span class="na">boot</span><span class="p">();</span> <span class="c1">// This is important</span> <span class="kd">var</span> <span class="n">myService</span> <span class="o">=</span> <span class="n">myProvider</span><span class="o">.</span><span class="na">resolve</span><span class="p">&lt;</span><span class="n">MyService</span><span class="p">&gt;();</span> <span class="c1">// also works: MyService myService = myProvider.resolve();</span> <span class="n">print</span><span class="p">(</span><span class="n">myService</span><span class="o">.</span><span class="na">username</span><span class="p">);</span> <span class="c1">// prints TestUser</span> </code></pre> </div> <p>Thats all for a simple service.</p> <h3> Nested services a.k.a. Dependency Injection </h3> <p>In the real world you've services that depend on other services that depend on configuration parameters etc.</p> <p><code>catalyst_builder</code> also supports this scenario:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight dart"><code><span class="nd">@Service</span><span class="p">()</span> <span class="kd">class</span> <span class="nc">ServiceA</span> <span class="p">{}</span> <span class="nd">@Service</span><span class="p">()</span> <span class="kd">class</span> <span class="nc">ServiceB</span> <span class="p">{</span> <span class="kd">final</span> <span class="n">ServiceA</span> <span class="n">serviceA</span><span class="p">;</span> <span class="n">ServiceB</span><span class="p">(</span><span class="k">this</span><span class="o">.</span><span class="na">ServiceA</span><span class="p">);</span> <span class="p">}</span> <span class="kd">class</span> <span class="nc">ServiceC</span> <span class="p">{}</span> <span class="nd">@Service</span><span class="p">()</span> <span class="kd">class</span> <span class="nc">ServiceD</span> <span class="p">{</span> <span class="kd">final</span> <span class="n">ServiceC</span> <span class="n">serviceC</span><span class="p">;</span> <span class="n">ServiceD</span><span class="p">(</span><span class="nd">@Parameter</span><span class="p">(</span><span class="s">'otherService'</span><span class="p">)</span> <span class="k">this</span><span class="o">.</span><span class="na">ServiceC</span><span class="p">);</span> <span class="p">}</span> <span class="kt">void</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span> <span class="kd">var</span> <span class="n">myProvider</span> <span class="o">=</span> <span class="n">AppServiceProvider</span><span class="p">();</span> <span class="n">myProvider</span><span class="o">.</span><span class="na">boot</span><span class="p">();</span> <span class="c1">// This works:</span> <span class="kd">var</span> <span class="n">serviceB</span> <span class="o">=</span> <span class="n">myProvider</span><span class="o">.</span><span class="na">resolve</span><span class="p">&lt;</span><span class="n">ServiceB</span><span class="p">&gt;();</span> <span class="c1">// This not because ServiceC is not known as a service:</span> <span class="kd">var</span> <span class="n">serviceD</span> <span class="o">=</span> <span class="n">myProvider</span><span class="o">.</span><span class="na">resolve</span><span class="p">&lt;</span><span class="n">ServiceD</span><span class="p">&gt;();</span> <span class="c1">// But this works, because the provider contains a </span> <span class="c1">// parameter with the same name as the required argument:</span> <span class="n">myProvider</span><span class="o">.</span><span class="na">parameters</span><span class="p">[</span><span class="s">'serviceC'</span><span class="p">]</span> <span class="o">=</span> <span class="n">ServiceC</span><span class="p">();</span> <span class="kd">var</span> <span class="n">serviceD</span> <span class="o">=</span> <span class="n">myProvider</span><span class="o">.</span><span class="na">resolve</span><span class="p">&lt;</span><span class="n">ServiceD</span><span class="p">&gt;();</span> <span class="c1">// This also works, because the provider contains a </span> <span class="c1">// parameter with the name which is given in the </span> <span class="c1">// Parameter annotation.</span> <span class="n">myProvider</span><span class="o">.</span><span class="na">parameters</span><span class="p">[</span><span class="s">'otherService'</span><span class="p">]</span> <span class="o">=</span> <span class="n">ServiceC</span><span class="p">();</span> <span class="kd">var</span> <span class="n">serviceD</span> <span class="o">=</span> <span class="n">myProvider</span><span class="o">.</span><span class="na">resolve</span><span class="p">&lt;</span><span class="n">ServiceD</span><span class="p">&gt;();</span> <span class="p">}</span> </code></pre> </div> <h3> Service lifetime </h3> <p>By default, all services are singeltons. You will get the same instance everytime you call <code>resolve&lt;T&gt;</code>.</p> <p>You can specify the lifetime with the lifetime argument in the <code>@Service</code> annotation:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight dart"><code><span class="c1">/// Transient services are always recreated</span> <span class="nd">@Service</span><span class="p">(</span><span class="nl">lifetime:</span> <span class="n">ServiceLifetime</span><span class="o">.</span><span class="na">transient</span><span class="p">)</span> <span class="kd">class</span> <span class="nc">TransientService</span> <span class="p">{}</span> <span class="c1">/// Default is singleton</span> <span class="nd">@Service</span><span class="p">(</span><span class="nl">lifetime:</span> <span class="n">ServiceLifetime</span><span class="o">.</span><span class="na">singleton</span><span class="p">)</span> <span class="kd">class</span> <span class="nc">SingletonService</span> <span class="p">{}</span> </code></pre> </div> <h3> Code Against Interfaces, Not Implementations. </h3> <p>Every <strong>pro</strong>grammer would tell you that you shouldn't depend on implementations but interfaces.</p> <p>Also this is possible with the <code>exposeAs</code> Property in the <code>@Service</code> annotation. Expose as will return the implementation if you request the type that you provide as <code>exposeAs</code>. This also works for nested services.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight dart"><code><span class="c1">// interface</span> <span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">BaseService</span> <span class="p">{}</span> <span class="c1">// implementation</span> <span class="nd">@Service</span><span class="p">(</span><span class="nl">exposeAs:</span> <span class="n">BaseService</span><span class="p">)</span> <span class="kd">class</span> <span class="nc">MyService</span> <span class="kd">implements</span> <span class="n">BaseService</span> <span class="p">{}</span> </code></pre> </div> <h3> Preloading services </h3> <p>Some services are background services (connectivity checks for example).<br> Decorate this services with <code>@Preload()</code> to create a instance of the service while <code>boot()</code>-ing the provider.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight dart"><code><span class="nd">@Service</span><span class="p">()</span> <span class="nd">@Preload</span><span class="p">()</span> <span class="kd">class</span> <span class="nc">MyService</span> <span class="p">{</span> <span class="n">MyService</span><span class="p">(){</span> <span class="n">print</span><span class="p">(</span><span class="s">'Service was created'</span><span class="p">);</span> <span class="p">}</span> <span class="p">}</span> <span class="kt">void</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span> <span class="n">ServiceProvider</span> <span class="n">provider</span><span class="p">;</span> <span class="n">provider</span><span class="o">.</span><span class="na">boot</span><span class="p">();</span> <span class="c1">// prints "Service was created" </span> <span class="n">provider</span><span class="o">.</span><span class="na">resolve</span><span class="p">&lt;</span><span class="n">MyService</span><span class="p">&gt;();</span> <span class="c1">// Nothing printed</span> <span class="p">}</span> </code></pre> </div> <h3> Flutter specific tips: </h3> <ul> <li>Screens (widgets) should be always transient services.</li> <li>You can use <code>resolve&lt;T&gt;</code> in the router: </li> </ul> <div class="highlight js-code-highlight"> <pre class="highlight dart"><code><span class="kd">class</span> <span class="nc">MyApp</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="p">{</span> <span class="nd">@override</span> <span class="n">Widget</span> <span class="n">build</span><span class="p">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="n">MaterialApp</span><span class="p">(</span> <span class="nl">debugShowCheckedModeBanner:</span> <span class="kc">false</span><span class="p">,</span> <span class="nl">initialRoute:</span> <span class="s">'/'</span><span class="p">,</span> <span class="nl">routes:</span> <span class="p">{</span> <span class="s">'/'</span><span class="o">:</span> <span class="p">(</span><span class="n">_</span><span class="p">)</span> <span class="o">=</span><span class="p">&gt;</span> <span class="n">container</span><span class="o">.</span><span class="na">resolve</span><span class="p">&lt;</span><span class="n">HomeScreen</span><span class="p">&gt;(),</span> <span class="p">},</span> <span class="p">);</span> <span class="p">}</span> <span class="p">}</span> </code></pre> </div> <p>Hope you like and use the package ;-)</p> flutter android ios tutorial Debug Mobile Websites with Chrome & Android Chrome Julian Finkler Mon, 29 Jun 2020 05:06:51 +0000 https://dev.to/devtronic/debug-mobile-websites-with-chrome-android-chrome-k71 https://dev.to/devtronic/debug-mobile-websites-with-chrome-android-chrome-k71 <p>Do you already know this cool Google Chrome feature?<br> <a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fpf2nhf330alxsfv08n2h.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fpf2nhf330alxsfv08n2h.png" alt="Remote devices tools" width="385" height="463"></a></p> <p>This allows you to debug your websites directly on your Android device. All you need to do is:</p> <ol> <li>Enable USB Debugging on your device</li> <li>Connect it to your computer</li> <li>Open Chrome on your mobile device</li> <li>Open the Chrome dev tools on the computer</li> <li>Click on the 3 dots menu &gt; More tools &gt; Remote devices</li> <li>Select the device and allow USB-Debugging on your mobile device.</li> <li>Click the "Inspect" button for the site you want to debug: <img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fuhw2y4oc4tci7k1tqy09.png" alt="Inspect button" width="800" height="298"> </li> <li>DEBUG! <img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F9xodi8zhvues8pw6ax28.png" alt="Remote debug" width="800" height="372"> </li> </ol> html javascript webdev debugging Reusable Angular forms in no time Julian Finkler Sun, 07 Jun 2020 12:30:55 +0000 https://dev.to/devtronic/reusable-angular-forms-in-no-time-1m7o https://dev.to/devtronic/reusable-angular-forms-in-no-time-1m7o <p>Imagine this situation:</p> <blockquote> <p>Your team lead assigns you the task to create a list view (e.g. <br> a list of orders) and a form view to edit the list entries. You find out, that the product area which is also part of the software provides the same logic. You start with copying the code of the list view (which is fortunately just an separated component which only needs a data source passed in). Then you start copying step by step the form template. A textfield here, a checkbox there. Oh, I need a select field and the other template doesn't contain it - no prob, HTML is nice AF and I can write the markup on my own. After a while you're done and the form works as expected.</p> </blockquote> <p>Recognize yourself in that workflow? Me too.</p> <h2> What's wrong with it? </h2> <p>Let's check what are the negative aspects of this:</p> <ol> <li>If you copy code you're violating the <a href="proxy.php?url=https://en.wikipedia.org/wiki/Don%27t_repeat_yourself" rel="noopener noreferrer">D.R.Y. principle</a> </li> <li>The style of fields may vary since you need to write some markup on your own.</li> <li>If you want to add a tooltip to all text fields you've to edit all files containing a text field. (You will forget one, believe me 😅)</li> <li>Your edit form is violating the <a href="proxy.php?url=https://en.wikipedia.org/wiki/Single-responsibility_principle" rel="noopener noreferrer">Single-responsibility principle</a> since it must validate all fields against general business logic (Not empty, min, max etc), contains listeners of field selection etc.</li> </ol> <h2> How to make it better? </h2> <p>First of all you can create simple components for each field. For example a <code>TextFieldComponent</code> or a <code>CheckboxComponent</code>. This would solve problem 1, 3 and partially 2. Why partially 2? Because it's still possible to modify the outer markup in the html where you use the component.</p> <h2> What else? Forms without writing HTML? No way! </h2> <p>You're right. Without writing any HTML it's not possible but you can reduce the HTML to a minimum.<br> I created a Angular library which is called <a href="proxy.php?url=https://github.com/mintware-de/mw-form-builder" rel="noopener noreferrer"><code>@mintware-de/form-builder</code></a>.<br> It provides all functionality you need to create reusable, maintainable and extensible forms in Angular.</p> <h2> Real world example </h2> <p>Create a new minimal Angular project using<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>ng new form-example <span class="nt">--minimal</span> <span class="nt">--skip-tests</span> <span class="nt">--inline-style</span> <span class="nt">--inline-template</span> </code></pre> </div> <p>Install the package<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>npm i <span class="nt">-d</span> @mintware-de/form-builder@^2.0.0 </code></pre> </div> <h3> Create a form fields module </h3> <p>In a previous section I mentioned that's an good idea to separate fields into it's own components. First, create a new module called <code>form-fields</code> inside your <code>src/app</code> directory and <code>cd</code> into the module directory.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>ng g m form-fields <span class="nb">cd </span>src/app/form-fields </code></pre> </div> <p>As described in the <a href="proxy.php?url=https://github.com/mintware-de/mw-form-builder/blob/master/docs/getting-started.md" rel="noopener noreferrer">getting started</a> import and export the <code>FormBuilderModule</code> and the <code>ReactiveFormsModule</code> in your <code>FormFieldsModule</code>.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="c1">// ...</span> <span class="nx">imports</span><span class="p">:</span> <span class="p">[</span> <span class="nx">CommonModule</span><span class="p">,</span> <span class="nx">FormBuilderModule</span><span class="p">,</span> <span class="c1">// New</span> <span class="nx">ReactiveFormsModule</span> <span class="c1">// New</span> <span class="p">],</span> <span class="nx">exports</span><span class="p">:</span> <span class="p">[</span> <span class="nx">FormBuilderModule</span><span class="p">,</span> <span class="c1">// New</span> <span class="nx">ReactiveFormsModule</span> <span class="c1">// New</span> <span class="p">]</span> <span class="c1">// ...</span> </code></pre> </div> <p>Import the <code>FormFieldsModule</code> in your <code>AppModule</code>.</p> <h3> Creating form field components </h3> <p>Let's start with creating a text field component which has a inline template, inline styles and no tests.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>ng g c text-field <span class="nt">--inline-template</span> <span class="nt">--inline-style</span> <span class="nt">--skip-tests</span> </code></pre> </div> <p><em>Why using inline template?<br> The template of the form components are really small in the most cases.<br> Additionally to this, mostly you don't need to write TypeScript code in the component itself. <br> That's the reason why I prefer inline templates.</em></p> <h4> Create the options interface and the form type </h4> <p>A form component for the form builder consists of 3 parts:</p> <ol> <li>The Angular component</li> <li>An options interface which is used for configuring the component</li> <li>The form type which connects the component and the options. The form type also defines the validation rules.</li> </ol> <p>Now create the options and the type next to the text-field.component.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight plaintext"><code>|- src/app/form-fields | |- text-field | | |- text-field.component.ts | | |- text-field.options.ts &lt;-- New | | |- text-field.type.ts &lt;-- New </code></pre> </div> <p>Create an empty interface for the text field options. We will add needed properties later.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="c1">// text-field.options.ts</span> <span class="k">export</span> <span class="kr">interface</span> <span class="nx">TextFieldOptions</span> <span class="p">{</span> <span class="p">}</span> </code></pre> </div> <p>The form type must extend the <code>AbstractType&lt;TOptions&gt;</code> class. The naming convention for the class name is the PascalCased file name without the suffix. In this case simply <code>TextField</code>.</p> <p>As <code>TOptions</code> you need to pass the created <code>TextFieldOptions</code> type and implement the abstract member <code>component</code>. Set the value to <code>TextFieldComponent</code>.<br> The referenced component will be used to render the form field.</p> <p>The complete file should look like this:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="c1">// text-field.type.ts</span> <span class="k">import</span> <span class="p">{</span><span class="nx">AbstractType</span><span class="p">,</span> <span class="nx">Constructor</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@mintware-de/form-builder</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span><span class="nx">TextFieldOptions</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./text-field.options</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span><span class="nx">TextFieldComponent</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./text-field.component</span><span class="dl">'</span><span class="p">;</span> <span class="k">export</span> <span class="kd">class</span> <span class="nc">TextField</span> <span class="kd">extends</span> <span class="nc">AbstractType</span><span class="o">&lt;</span><span class="nx">TextFieldOptions</span><span class="o">&gt;</span> <span class="p">{</span> <span class="k">public</span> <span class="k">readonly</span> <span class="na">component</span><span class="p">:</span> <span class="nx">Constructor</span> <span class="o">=</span> <span class="nx">TextFieldComponent</span><span class="p">;</span> <span class="p">}</span> </code></pre> </div> <h4> Write the component code </h4> <p>First of all, add the <code>TextFieldComponent</code> to the <code>FormFieldsModule</code> inside the <code>entryComponents</code> section.<br> This is necessary since the form builder render the components<br> with a <code>ComponentFactory</code>.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="c1">// form-fields.module.ts</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">NgModule</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@angular/core</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">CommonModule</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@angular/common</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">TextFieldComponent</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./text-field/text-field.component</span><span class="dl">'</span><span class="p">;</span> <span class="p">@</span><span class="nd">NgModule</span><span class="p">({</span> <span class="na">declarations</span><span class="p">:</span> <span class="p">[</span> <span class="nx">TextFieldComponent</span><span class="p">,</span> <span class="p">],</span> <span class="na">entryComponents</span><span class="p">:</span> <span class="p">[</span> <span class="nx">TextFieldComponent</span><span class="p">,</span> <span class="c1">// New</span> <span class="p">],</span> <span class="na">imports</span><span class="p">:</span> <span class="p">[</span> <span class="nx">CommonModule</span> <span class="p">]</span> <span class="p">})</span> <span class="k">export</span> <span class="kd">class</span> <span class="nc">FormFieldsModule</span> <span class="p">{</span> <span class="p">}</span> </code></pre> </div> <p>Open the <code>TextFieldComponent</code> and replace the <code>implements OnInit</code> with<br> <code>extends AbstractFormFieldComponent&lt;AbstractType&lt;TextFieldOptions&gt;&gt;</code><br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="c1">// text-field.component.ts</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">Component</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@angular/core</span><span class="dl">'</span><span class="p">;</span> <span class="c1">// modified</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">AbstractFormFieldComponent</span><span class="p">,</span> <span class="nx">AbstractType</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@mintware-de/form-builder</span><span class="dl">'</span><span class="p">;</span> <span class="c1">// new</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">TextFieldOptions</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./text-field.options</span><span class="dl">'</span><span class="p">;</span> <span class="c1">// new</span> <span class="p">@</span><span class="nd">Component</span><span class="p">({</span> <span class="na">selector</span><span class="p">:</span> <span class="dl">'</span><span class="s1">app-text-field</span><span class="dl">'</span><span class="p">,</span> <span class="na">template</span><span class="p">:</span> <span class="s2">` &lt;p&gt; text-field works! &lt;/p&gt; `</span><span class="p">,</span> <span class="c1">// modified</span> <span class="p">})</span> <span class="k">export</span> <span class="kd">class</span> <span class="nc">TextFieldComponent</span> <span class="kd">extends</span> <span class="nc">AbstractFormFieldComponent</span><span class="o">&lt;</span><span class="nx">AbstractType</span><span class="o">&lt;</span><span class="nx">TextFieldOptions</span><span class="o">&gt;&gt;</span> <span class="p">{</span> <span class="c1">// modified</span> <span class="c1">// modified</span> <span class="p">}</span> </code></pre> </div> <p><em>Why not using <code>extends TextField</code> directly?<br> Since the <code>TextField</code> imports the <code>TextFieldComponent</code> a direct use of <code>TextField</code> inside the <code>TextFieldComponent</code> would cause a circular reference.</em></p> <h4> Add the input field </h4> <p>Now we need to add some HTML code which contains the input element. We use the <code>[formControl]</code> attribute on the input to link the input element with the <code>FormControl</code> in the Angular <code>FormGroup</code>.<br> The <code>AbstractFormGroupComponent</code> a property <code>mwElement</code> which contains the form control.</p> <p>Update the template of the <code>TextFieldComponent</code>:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight html"><code><span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">[formControl]=</span><span class="s">"mwElement"</span><span class="nt">&gt;</span> </code></pre> </div> <p>Congrats, you just created your first form field 👏. Let's create a form to use <strong>and reuse</strong> the the form field.</p> <h4> Create a form and use the form field </h4> <p>Open the <code>AppComponent</code> and replace the content with this:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="k">import</span> <span class="p">{</span><span class="nx">Component</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@angular/core</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span><span class="nx">FormModel</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@mintware-de/form-builder</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span><span class="nx">TextField</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./form-fields/text-field/text-field.type</span><span class="dl">'</span><span class="p">;</span> <span class="p">@</span><span class="nd">Component</span><span class="p">({</span> <span class="na">selector</span><span class="p">:</span> <span class="dl">'</span><span class="s1">app-root</span><span class="dl">'</span><span class="p">,</span> <span class="c1">// Display the form by using the MwFormBuilder Component</span> <span class="c1">// Pass the formModel and formData and set a submit action</span> <span class="c1">// The action is only called if the form is valid</span> <span class="na">template</span><span class="p">:</span> <span class="s2">` &lt;mw-form-builder #myForm [mwFormModel]="formModel" [mwFormData]="formData" (mwFormSubmit)="submit($event)"&gt; &lt;/mw-form-builder&gt; &lt;button type="button" (click)="myForm.submit()"&gt;Submit&lt;/button&gt; `</span> <span class="p">})</span> <span class="k">export</span> <span class="kd">class</span> <span class="nc">AppComponent</span> <span class="p">{</span> <span class="c1">// Create a form model. </span> <span class="c1">// The naming and nesting is equal to the formData</span> <span class="k">public</span> <span class="nx">formModel</span><span class="p">:</span> <span class="nx">FormModel</span> <span class="o">=</span> <span class="p">{</span> <span class="na">firstName</span><span class="p">:</span> <span class="k">new</span> <span class="nc">TextField</span><span class="p">({}),</span> <span class="na">lastName</span><span class="p">:</span> <span class="k">new</span> <span class="nc">TextField</span><span class="p">({})</span> <span class="p">};</span> <span class="c1">// Set the initial form data</span> <span class="k">public</span> <span class="nx">formData</span><span class="p">:</span> <span class="p">{</span> <span class="nl">firstName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">lastName</span><span class="p">:</span> <span class="kr">string</span> <span class="p">}</span> <span class="o">=</span> <span class="p">{</span> <span class="na">firstName</span><span class="p">:</span> <span class="dl">'</span><span class="s1">John</span><span class="dl">'</span><span class="p">,</span> <span class="na">lastName</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Doe</span><span class="dl">'</span><span class="p">,</span> <span class="p">};</span> <span class="c1">// Create a submit handler</span> <span class="k">public</span> <span class="nf">submit</span><span class="p">(</span><span class="nx">data</span><span class="p">:</span> <span class="p">{</span> <span class="nl">firstName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">lastName</span><span class="p">:</span> <span class="kr">string</span> <span class="p">}):</span> <span class="k">void</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Form was submitted: %o</span><span class="dl">"</span><span class="p">,</span> <span class="nx">data</span><span class="p">);</span> <span class="p">}</span> <span class="p">}</span> </code></pre> </div> <p>Run <code>ng serve</code> to start the app. <br> <a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqwexmiixhn6nok7xiz4g.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqwexmiixhn6nok7xiz4g.png" alt="Alt Text" width="285" height="87"></a></p> <p>Press the button and something like<br> <code>Form was submitted: {firstName: "John", lastName: "Doe"}</code> <br> is written to the console.</p> <h3> Adding options to the text field </h3> <p>Cool, text fields without labels. That's what I call usability 😬<br> Ok, let's add a few options to our text field:</p> <ul> <li>Label: The string which is used as the label</li> <li>Required: A boolean which defaults to true and marks the field as required or not. </li> </ul> <p>Edit the <code>TextFieldOptions</code> interface and add the fields:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="c1">// text-field.options.ts</span> <span class="k">export</span> <span class="kr">interface</span> <span class="nx">TextFieldOptions</span> <span class="p">{</span> <span class="nl">label</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="c1">// new</span> <span class="nl">required</span><span class="p">?:</span> <span class="nx">boolean</span><span class="p">;</span> <span class="c1">// new</span> <span class="p">}</span> </code></pre> </div> <p>Now update the HTML code of the TextFieldComponent and use the properties. You can access the options object in the <code>mwFieldType</code> property, which comes from <code>AbstractFormFieldComponent</code>.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight html"><code><span class="nt">&lt;div&gt;</span> <span class="nt">&lt;label</span> <span class="na">[for]=</span><span class="s">"mwPath"</span><span class="nt">&gt;</span>{{ mwFieldType.options.label }}<span class="nt">&lt;/label&gt;</span> <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">[formControl]=</span><span class="s">"mwElement"</span> <span class="na">[id]=</span><span class="s">"mwPath"</span> <span class="na">[required]=</span><span class="s">"mwFieldType.options.required"</span><span class="nt">&gt;</span> <span class="nt">&lt;div</span> <span class="na">*ngIf=</span><span class="s">"mwElement.errors &amp;&amp; mwElement.errors.required"</span><span class="nt">&gt;</span> {{mwFieldType.options.label}} is required. <span class="nt">&lt;/div&gt;</span> <span class="nt">&lt;/div&gt;</span> </code></pre> </div> <p>Since the <code>label</code> property is not nullable, you've to set it in the form model in the <code>AppComponent</code>.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="k">public</span> <span class="nx">formModel</span><span class="p">:</span> <span class="nx">FormModel</span> <span class="o">=</span> <span class="p">{</span> <span class="na">firstName</span><span class="p">:</span> <span class="k">new</span> <span class="nc">TextField</span><span class="p">({</span> <span class="na">label</span><span class="p">:</span> <span class="dl">'</span><span class="s1">First name</span><span class="dl">'</span><span class="p">,</span> <span class="c1">// new</span> <span class="p">}),</span> <span class="na">lastName</span><span class="p">:</span> <span class="k">new</span> <span class="nc">TextField</span><span class="p">({</span> <span class="na">label</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Last name</span><span class="dl">'</span><span class="p">,</span> <span class="c1">// new</span> <span class="p">})</span> <span class="p">};</span> </code></pre> </div> <p>Reload the page and voilà, the form fields has labels:<br> <a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fx28w7a6gjlcw7vu07bv7.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fx28w7a6gjlcw7vu07bv7.png" alt="Form with labels" width="285" height="107"></a></p> <p>Almost done. We just need to add the required validation and set the default state to true.</p> <h3> Option defaults &amp; validation </h3> <p>To set default values for optional options, you need to add a constructor to the <code>TextField</code> type. In the constructor you can use <code>Object.assign({}, ...);</code> to set the defaults in the options object. Validators can be added by overriding the <code>validators</code> getter.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="k">import</span> <span class="p">{</span><span class="nx">AbstractType</span><span class="p">,</span> <span class="nx">Constructor</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@mintware-de/form-builder</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span><span class="nx">TextFieldOptions</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./text-field.options</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span><span class="nx">TextFieldComponent</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./text-field.component</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span><span class="nx">ValidatorFn</span><span class="p">,</span> <span class="nx">Validators</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@angular/forms</span><span class="dl">'</span><span class="p">;</span> <span class="k">export</span> <span class="kd">class</span> <span class="nc">TextField</span> <span class="kd">extends</span> <span class="nc">AbstractType</span><span class="o">&lt;</span><span class="nx">TextFieldOptions</span><span class="o">&gt;</span> <span class="p">{</span> <span class="k">public</span> <span class="k">readonly</span> <span class="na">component</span><span class="p">:</span> <span class="nx">Constructor</span> <span class="o">=</span> <span class="nx">TextFieldComponent</span><span class="p">;</span> <span class="nf">constructor</span><span class="p">(</span><span class="na">opts</span><span class="p">:</span> <span class="nx">TextFieldOptions</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// Set the field defaults</span> <span class="k">super</span><span class="p">(</span><span class="nb">Object</span><span class="p">.</span><span class="nf">assign</span><span class="p">({</span> <span class="na">required</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="p">},</span> <span class="nx">opts</span><span class="p">));</span> <span class="p">}</span> <span class="k">public</span> <span class="kd">get</span> <span class="nf">validators</span><span class="p">():</span> <span class="nx">ValidatorFn</span><span class="p">[]</span> <span class="p">{</span> <span class="kd">const</span> <span class="na">validators</span><span class="p">:</span> <span class="nx">ValidatorFn</span><span class="p">[]</span> <span class="o">=</span> <span class="p">[];</span> <span class="c1">// Only add the required validator if the field is required</span> <span class="k">if </span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">options</span><span class="p">.</span><span class="nx">required</span><span class="p">)</span> <span class="p">{</span> <span class="nx">validators</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="nx">Validators</span><span class="p">.</span><span class="nx">required</span><span class="p">);</span> <span class="p">}</span> <span class="k">return</span> <span class="nx">validators</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> </code></pre> </div> <p>Reload the page and clear the inputs. You should see the error message and if you try to submit the form, the <code>submit</code> method is not called since both fields are required by default.</p> <h2> FAQ </h2> <h3> Is the form builder compatible with Angular Material? </h3> <p>Yep</p> <h3> Can I create collection fields and nested forms? </h3> <p>Collections or Array fields and nested forms are both supported.<br> Guides:</p> <ul> <li><a href="proxy.php?url=https://github.com/mintware-de/mw-form-builder/blob/master/docs/collection-form-fields.md" rel="noopener noreferrer">Collections</a></li> <li> <a href="proxy.php?url=https://github.com/mintware-de/mw-form-builder/blob/master/docs/form-groups.md" rel="noopener noreferrer">Form groups</a> for nested forms.</li> </ul> <h3> ...Complex layouts? </h3> <p>Yes, there is a special FormType called <code>AbstractLayoutType</code>. Guide: <a href="proxy.php?url=https://github.com/mintware-de/mw-form-builder/blob/master/docs/layout-types.md" rel="noopener noreferrer">Layout types</a> </p> <p>Example: <a href="proxy.php?url=https://gist.github.com/devtronic/807e8bfc712330ef13a5c9b8bf5a71cf" rel="noopener noreferrer">https://gist.github.com/devtronic/807e8bfc712330ef13a5c9b8bf5a71cf</a></p> <p>I hope everything was clear and you enjoyed reading my post.</p> <p>Questions, Suggestions etc. ➡️ Comments</p> angular typescript form tutorial Server Side: Dart vs. PHP Julian Finkler Wed, 01 Apr 2020 10:44:19 +0000 https://dev.to/devtronic/server-side-dart-vs-php-5872 https://dev.to/devtronic/server-side-dart-vs-php-5872 <p>I'm courious, please comment 🙂</p> <p>If you've to choose between Dart and PHP:</p> <p>Which one do you prefer for server side programming?:<br> ...</p> <p>Why?:<br> ...</p> <p>Why not (Dart|PHP)?:<br> ...</p> poll dart php language TypeScript: Effizient Flat-Daten in einen Baum umwandeln Julian Finkler Mon, 03 Feb 2020 18:34:15 +0000 https://dev.to/devtronic/typescript-effizient-flat-daten-in-einen-baum-umwandeln-39nm https://dev.to/devtronic/typescript-effizient-flat-daten-in-einen-baum-umwandeln-39nm <p>Manchmal stehst du vor der Herausforderung, Flat-Daten in eine Baum-Struktur umzuwandeln. Jeder Flat-Datensatz beinhaltet dabei in der Regel eine Id und eine ParentId wobei letzteres die Id des jeweils übergeordneten Knoten ist. Ist die ParentId <code>null</code> handelt es sich um einen Wurzelknoten.</p> <p>Zum Beispiel sollst du<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight json"><code><span class="p">[</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"Id"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="nl">"Name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1"</span><span class="p">,</span><span class="w"> </span><span class="nl">"ParentId"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"Id"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="nl">"Name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1 - 1"</span><span class="p">,</span><span class="w"> </span><span class="nl">"ParentId"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"Id"</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="nl">"Name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1 - 2"</span><span class="p">,</span><span class="w"> </span><span class="nl">"ParentId"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"Id"</span><span class="p">:</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w"> </span><span class="nl">"Name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1 - 3"</span><span class="p">,</span><span class="w"> </span><span class="nl">"ParentId"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"Id"</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w"> </span><span class="nl">"Name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1 - 2 - 1"</span><span class="p">,</span><span class="w"> </span><span class="nl">"ParentId"</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">]</span><span class="w"> </span></code></pre> </div> <p>in folgende Struktur umwandeln:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight json"><code><span class="p">[</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"Id"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="nl">"Name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1"</span><span class="p">,</span><span class="w"> </span><span class="nl">"ParentId"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"> </span><span class="nl">"Children"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"Id"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="nl">"Name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1 - 1"</span><span class="p">,</span><span class="w"> </span><span class="nl">"ParentId"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="nl">"Children"</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"Id"</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="nl">"Name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1 - 2"</span><span class="p">,</span><span class="w"> </span><span class="nl">"ParentId"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="nl">"Children"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"Id"</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w"> </span><span class="nl">"Name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1 - 2 - 1"</span><span class="p">,</span><span class="w"> </span><span class="nl">"ParentId"</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="nl">"Children"</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">]</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"Id"</span><span class="p">:</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w"> </span><span class="nl">"Name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1 - 3"</span><span class="p">,</span><span class="w"> </span><span class="nl">"ParentId"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="nl">"Children"</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">]</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">]</span><span class="w"> </span></code></pre> </div> <h2> Der rekursive Ansatz </h2> <p>Der erste Ansatz an den man dabei denkt, wäre folgendes rekursives Konstrukt:</p> <ol> <li>Man sucht sich alle Wurzelknoten (ParentId = <code>null</code>) raus und verschiebt diese in ein neues Array.</li> <li>Anschließend iteriert man rekursiv über die restlichen Knoten und prüft, ob die ParentId des aktuellen Knoten der Id einem der Wurzelknoten bzw. deren Kindknoten entspricht.</li> <li>Falls dem so ist, fügt man dem gefundenen Knoten den aktuellen Knoten als Kindknoten hinzu. Falls nicht, schiebt man den Knoten wieder in die Liste zurück.</li> </ol> <p>Ein großer Nachteil:<br> <strong>Wir müssen für jeden Knoten im Worst-Case den kompletten Baum rekursiv runter laufen.</strong></p> <h2> Der Do-While-Shift-Push-Reference-Type Ansatz </h2> <p>Ok, den Namen habe ich mir gerade ausgedacht, gibt aber genau wieder, wie es effizienter und auch sauberer geht.</p> <p>In JavaScript ist alles was kein <a href="proxy.php?url=https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Data_types" rel="noopener noreferrer">primitiver Datentyp ist, ein Objekt</a>. Objekte sind, Reference-Types. Primitive Datentypen sind Value-Types.</p> <p>Wer den Unterschied nicht kennt:</p> <p><a href="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4o7izip7bx5z7l2psscu.gif" class="article-body-image-wrapper"><img src="proxy.php?url=https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4o7izip7bx5z7l2psscu.gif" alt="Reference vs. Value Type" width="470" height="222"></a><br> (Quelle: Internet)</p> <p>Dieses Verhalten können wir uns zu Nutze machen.</p> <p>Dass ein Knoten ein Reference-Type sind, ist denke ich klar. Bei dem <code>Children</code>-Property am Knoten handelt es sich um ein Array mit weiteren Knoten. Ein Array ist ebenfalls kein primitiver Datentyp und somit auch ein Reference-Type.</p> <p>Der Ansatz ist folgender:</p> <ol> <li>Man erzeugt ein leeres Array für den <code>tree</code>.</li> <li>Man erzeugt eine leere Map.</li> <li>In einer do-while (oder while je nachdem was du mehr magst 😅) Iterierst du so lange, bis das Datenarray leer ist. In jeder iteration machst du folgendes: <ol> <li>Erzeuge eine leeres array, welches die Kindknoten für den aktuellen Eintrag halten soll.</li> <li> <code>data.shift()</code> um den nächsten Eintrag aus dem Daten-Array zu holen</li> <li>Prüfe ob der Eintrag ein Wurzelknoten ist. <ul> <li>Falls ja, erzeuge einen Baumknoten und ordne diesem das gerade erzeugte Array als Array für die Kindknoten hinzu. Diesen Baumknoten fügst du in das <code>tree</code> Array ein und legst in der Map einen Eintrag mit der ID des Knoten und den Kindknoten array hinzu.</li> <li>Falls nein und die ParentId in der Map vorhanden ist, wiederhole den vorherigen Schritt mit der Ausnahme, dass du den Baumknoten nicht dem <code>tree</code> Array sondern dem Kindknoten Array aus der Map hinzufügst.</li> <li>Ansonsten machst du ein <code>data.push(node)</code> um den Knoten wieder hinten anzufügen.</li> </ul> </li> </ol> </li> </ol> <p>Als Code könnte es zum Beispiel so aussehen:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="kr">interface</span> <span class="nx">FlatNode</span> <span class="p">{</span> <span class="nl">Id</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span> <span class="nl">Name</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="nl">ParentId</span><span class="p">?:</span> <span class="kr">number</span><span class="p">;</span> <span class="p">}</span> <span class="kr">interface</span> <span class="nx">TreeNode</span> <span class="kd">extends</span> <span class="nx">FlatNode</span> <span class="p">{</span> <span class="nl">Children</span><span class="p">:</span> <span class="nx">TreeNode</span><span class="p">[];</span> <span class="p">}</span> <span class="kd">const</span> <span class="nx">data</span><span class="p">:</span> <span class="nx">FlatNode</span><span class="p">[]</span> <span class="o">=</span> <span class="p">[</span> <span class="p">{</span><span class="na">Id</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="na">Name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">1</span><span class="dl">'</span><span class="p">,</span> <span class="na">ParentId</span><span class="p">:</span> <span class="kc">null</span><span class="p">},</span> <span class="p">{</span><span class="na">Id</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="na">Name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">1 - 1</span><span class="dl">'</span><span class="p">,</span> <span class="na">ParentId</span><span class="p">:</span> <span class="mi">1</span><span class="p">},</span> <span class="p">{</span><span class="na">Id</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span> <span class="na">Name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">1 - 2</span><span class="dl">'</span><span class="p">,</span> <span class="na">ParentId</span><span class="p">:</span> <span class="mi">1</span><span class="p">},</span> <span class="p">{</span><span class="na">Id</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span> <span class="na">Name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">1 - 3</span><span class="dl">'</span><span class="p">,</span> <span class="na">ParentId</span><span class="p">:</span> <span class="mi">1</span><span class="p">},</span> <span class="p">{</span><span class="na">Id</span><span class="p">:</span> <span class="mi">5</span><span class="p">,</span> <span class="na">Name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">1 - 2 - 1</span><span class="dl">'</span><span class="p">,</span> <span class="na">ParentId</span><span class="p">:</span> <span class="mi">3</span><span class="p">},</span> <span class="p">];</span> <span class="kd">const</span> <span class="nx">tree</span><span class="p">:</span> <span class="nx">TreeNode</span><span class="p">[]</span> <span class="o">=</span> <span class="p">[];</span> <span class="kd">const</span> <span class="nx">childrenMap</span> <span class="o">=</span> <span class="p">{};</span> <span class="kd">let</span> <span class="nx">notFoundCounter</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">do</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">next</span> <span class="o">=</span> <span class="nx">data</span><span class="p">.</span><span class="nf">shift</span><span class="p">();</span> <span class="kd">const</span> <span class="nx">nextChildren</span> <span class="o">=</span> <span class="p">[];</span> <span class="k">if </span><span class="p">(</span><span class="nx">next</span><span class="p">.</span><span class="nx">ParentId</span> <span class="o">==</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span> <span class="nx">tree</span><span class="p">.</span><span class="nf">push</span><span class="p">({...</span><span class="nx">next</span><span class="p">,</span> <span class="na">Children</span><span class="p">:</span> <span class="nx">nextChildren</span><span class="p">});</span> <span class="p">}</span> <span class="k">else</span> <span class="k">if </span><span class="p">(</span><span class="nx">next</span><span class="p">.</span><span class="nx">ParentId</span> <span class="k">in</span> <span class="nx">childrenMap</span><span class="p">)</span> <span class="p">{</span> <span class="nx">childrenMap</span><span class="p">[</span><span class="nx">next</span><span class="p">.</span><span class="nx">ParentId</span><span class="p">].</span><span class="nf">push</span><span class="p">({...</span><span class="nx">next</span><span class="p">,</span> <span class="na">Children</span><span class="p">:</span> <span class="nx">nextChildren</span><span class="p">});</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="nx">notFoundCounter</span><span class="o">++</span><span class="p">;</span> <span class="nx">data</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="nx">next</span><span class="p">);</span> <span class="k">continue</span><span class="p">;</span> <span class="p">}</span> <span class="nx">childrenMap</span><span class="p">[</span><span class="nx">next</span><span class="p">.</span><span class="nx">Id</span><span class="p">]</span> <span class="o">=</span> <span class="nx">nextChildren</span><span class="p">;</span> <span class="k">if </span><span class="p">(</span><span class="nx">notFoundCounter</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="nx">notFoundCounter</span><span class="o">--</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="k">while </span><span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="nx">notFoundCounter</span> <span class="o">&lt;</span> <span class="nx">data</span><span class="p">.</span><span class="nx">length</span><span class="p">);</span> </code></pre> </div> <p>Und das wars auch schon 🙂<br> Da es sich bei der Map lediglich um Referenzen zu den Arrays mit den Kindknoten der jeweiligen Knoten handelt, ist der Overhead an Speicherverbrauch entsprechend gering. </p> <p>Wer's bequemer haben möchte, kann es natürlich auch noch in eine Funktion packen:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="kd">function</span> <span class="nf">unflat</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span><span class="p">(</span><span class="nx">data</span><span class="p">:</span> <span class="nx">T</span><span class="p">[],</span> <span class="nx">id</span><span class="p">:</span> <span class="p">(</span><span class="nx">o</span><span class="p">:</span> <span class="nx">T</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">(</span><span class="kr">string</span> <span class="o">|</span> <span class="kr">number</span><span class="p">),</span> <span class="nx">parentId</span><span class="p">:</span> <span class="p">(</span><span class="nx">o</span><span class="p">:</span> <span class="nx">T</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">(</span><span class="kr">string</span> <span class="o">|</span> <span class="kr">number</span><span class="p">),</span> <span class="nx">childrenPropertyName</span><span class="p">:</span> <span class="kr">string</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">Children</span><span class="dl">'</span><span class="p">,</span> <span class="p">):</span> <span class="p">(</span><span class="nx">T</span> <span class="o">&amp;</span> <span class="kr">any</span><span class="p">)[]</span> <span class="p">{</span> <span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">data</span> <span class="o">||</span> <span class="nx">data</span><span class="p">.</span><span class="nx">length</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="p">[];</span> <span class="p">}</span> <span class="kd">const</span> <span class="nx">tree</span> <span class="o">=</span> <span class="p">[];</span> <span class="kd">const</span> <span class="nx">childrenMap</span> <span class="o">=</span> <span class="p">{};</span> <span class="kd">let</span> <span class="nx">notFoundCounter</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">do</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">current</span> <span class="o">=</span> <span class="nx">data</span><span class="p">.</span><span class="nf">shift</span><span class="p">();</span> <span class="kd">const</span> <span class="nx">nextChildren</span> <span class="o">=</span> <span class="p">[];</span> <span class="kd">const</span> <span class="nx">currentParentId</span> <span class="o">=</span> <span class="nf">parentId</span><span class="p">(</span><span class="nx">current</span><span class="p">);</span> <span class="k">if </span><span class="p">(</span><span class="nx">currentParentId</span> <span class="o">==</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span> <span class="nx">tree</span><span class="p">.</span><span class="nf">push</span><span class="p">({...</span><span class="nx">current</span><span class="p">,</span> <span class="p">[</span><span class="nx">childrenPropertyName</span><span class="p">]:</span> <span class="nx">nextChildren</span><span class="p">});</span> <span class="p">}</span> <span class="k">else</span> <span class="k">if </span><span class="p">(</span><span class="nx">currentParentId</span> <span class="k">in</span> <span class="nx">childrenMap</span><span class="p">)</span> <span class="p">{</span> <span class="nx">childrenMap</span><span class="p">[</span><span class="nx">currentParentId</span><span class="p">].</span><span class="nf">push</span><span class="p">({...</span><span class="nx">current</span><span class="p">,</span> <span class="p">[</span><span class="nx">childrenPropertyName</span><span class="p">]:</span> <span class="nx">nextChildren</span><span class="p">});</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="nx">notFoundCounter</span><span class="o">++</span><span class="p">;</span> <span class="nx">data</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="nx">current</span><span class="p">);</span> <span class="k">continue</span><span class="p">;</span> <span class="p">}</span> <span class="nx">childrenMap</span><span class="p">[</span><span class="nf">id</span><span class="p">(</span><span class="nx">current</span><span class="p">)]</span> <span class="o">=</span> <span class="nx">nextChildren</span><span class="p">;</span> <span class="k">if </span><span class="p">(</span><span class="nx">notFoundCounter</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="nx">notFoundCounter</span><span class="o">--</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="k">while </span><span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="nx">notFoundCounter</span> <span class="o">&lt;</span> <span class="nx">data</span><span class="p">.</span><span class="nx">length</span><span class="p">);</span> <span class="k">return</span> <span class="nx">tree</span><span class="p">;</span> <span class="p">}</span> <span class="kd">const</span> <span class="nx">data</span><span class="p">:</span> <span class="nx">FlatNode</span><span class="p">[]</span> <span class="o">=</span> <span class="p">[</span> <span class="p">{</span><span class="na">Id</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="na">Name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">1</span><span class="dl">'</span><span class="p">,</span> <span class="na">ParentId</span><span class="p">:</span> <span class="kc">null</span><span class="p">},</span> <span class="p">{</span><span class="na">Id</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="na">Name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">1 - 1</span><span class="dl">'</span><span class="p">,</span> <span class="na">ParentId</span><span class="p">:</span> <span class="mi">1</span><span class="p">},</span> <span class="p">{</span><span class="na">Id</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span> <span class="na">Name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">1 - 2</span><span class="dl">'</span><span class="p">,</span> <span class="na">ParentId</span><span class="p">:</span> <span class="mi">1</span><span class="p">},</span> <span class="p">{</span><span class="na">Id</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span> <span class="na">Name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">1 - 3</span><span class="dl">'</span><span class="p">,</span> <span class="na">ParentId</span><span class="p">:</span> <span class="mi">1</span><span class="p">},</span> <span class="p">{</span><span class="na">Id</span><span class="p">:</span> <span class="mi">5</span><span class="p">,</span> <span class="na">Name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">1 - 2 - 1</span><span class="dl">'</span><span class="p">,</span> <span class="na">ParentId</span><span class="p">:</span> <span class="mi">3</span><span class="p">},</span> <span class="p">];</span> <span class="kd">const</span> <span class="nx">tree</span> <span class="o">=</span> <span class="nf">unflat</span><span class="p">(</span><span class="nx">data</span><span class="p">,</span> <span class="p">(</span><span class="nx">o</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">o</span><span class="p">.</span><span class="nx">Id</span><span class="p">,</span> <span class="p">(</span><span class="nx">o</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">o</span><span class="p">.</span><span class="nx">ParentId</span><span class="p">);</span> <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">tree</span><span class="p">);</span> </code></pre> </div> <p>Ich finde dieses Beispiel ist ein gutes Beispiel dafür, dass man nicht immer nur auf Algorithmen selber sondern auch auf die Datenhaltung schauen sollte, wenn man schnellen und zugleich verständlichen Code schreiben möchte.</p> <p>Was hältst du von dem Ansatz? Vorschläge? Alternativen? Ab in die Kommentare damit. </p> typescript javascript data german Eine Angular Library schreiben Julian Finkler Sun, 01 Dec 2019 12:40:12 +0000 https://dev.to/devtronic/eine-angular-library-schreiben-36ib https://dev.to/devtronic/eine-angular-library-schreiben-36ib <p>Es kommt vor, dass man Code für eine Angular Anwendung schreibt, den man eigentlich in mehreren Projekten benötigt.<br> Anstatt den Code zu kopieren und somit gegen das <a href="proxy.php?url=https://de.wikipedia.org/wiki/Don%E2%80%99t_repeat_yourself" rel="noopener noreferrer">DRY Prinzip</a> zu verstoßen, ist es sinnvoll, den Code zu isolieren und als eigene <a href="proxy.php?url=https://angular.io/guide/creating-libraries" rel="noopener noreferrer">Angular Library</a> zu veröffentlichen.</p> <h2> Neues Projekt anlegen </h2> <p>Um die Library entwickeln zu können, müssen wir zuerst ein neues Angular Projekt bzw. einen Workspace anlegen.</p> <p>Workspaces unterscheiden sich von "normalen" Angular Anwendungen darin, dass es kein einzelnes Projekt gibt, sondern einen Arbeitsbereich hat, in dem man mehrere Projekte hinzufügen kann.</p> <p>Wir brauchen somit:</p> <ol> <li>ein Library Projekt in welchem wir den Code, den wir isolieren wollen, schreiben und</li> <li>eine Angular Anwendung, mit welcher wir die Library testen können.</li> </ol> <p>Legen wir als erstes einmal den Workspace an. Ich möchte in meinem konkreten Fall einen Form Generator schreiben. Durch die Option <code>--create-application=false</code> wird ein Workspace anstatt einem Projekt erzeugt<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>ng new form-builder <span class="nt">--create-application</span><span class="o">=</span><span class="nb">false </span>Would you like to add Angular routing? No Which stylesheet format would you like to use? CSS CREATE form-builder/README.md <span class="o">(</span>1028 bytes<span class="o">)</span> CREATE form-builder/.editorconfig <span class="o">(</span>246 bytes<span class="o">)</span> CREATE form-builder/.gitignore <span class="o">(</span>629 bytes<span class="o">)</span> CREATE form-builder/angular.json <span class="o">(</span>135 bytes<span class="o">)</span> CREATE form-builder/package.json <span class="o">(</span>1263 bytes<span class="o">)</span> CREATE form-builder/tsconfig.json <span class="o">(</span>435 bytes<span class="o">)</span> CREATE form-builder/tslint.json <span class="o">(</span>1621 bytes<span class="o">)</span> // ... </code></pre> </div> <p>Als nächstes wechseln wir in das Workspace Verzeichnis und legen das Library Projekt und die Beispiel Anwendung an<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span><span class="nb">cd </span>form-builder <span class="nv">$ </span>ng g library form-builder <span class="nv">$ </span>ng g application form-builder-example </code></pre> </div> <p>Im Verzeichnis <code>projects</code> sollten bei dir nun 3 Projekte liegen. Einmal das Library Projekt, dann die Angular Anwendung und dann noch die e2e Tests der Angular Anwendung.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight plaintext"><code>projects/ |- form-builder |- form-builder-example/ |- form-builder-example-e2e/ </code></pre> </div> <p>Das Library Projekt hat einen Ordner <code>src/lib</code>. In diesem befindet sich der Code deiner Library. Außerdem existiert eine Datei <code>src/public-api.ts</code>. In dieser musst du alles hinterlegen, was du aus deiner Library für andere Anwendungen verfügbar machen möchtest. Das können z.B. Komponenten oder Services sein.</p> <h2> Build Script </h2> <p>Als nächstes musst du in der Workspace übergreifenden <code>package.json</code> zwei neue Scripts hinzufügen.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight json"><code><span class="p">{</span><span class="w"> </span><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">...</span><span class="w"> </span><span class="nl">"lib:build"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ng build form-builder"</span><span class="p">,</span><span class="w"> </span><span class="nl">"lib:dev"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ng build form-builder --watch"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre> </div> <p><code>lib:build</code> dient zum bauen der Library und <code>lib:dev</code> zum ausführen des build Prozess während der Entwicklungszeit.</p> <p>Führe zunächst einmal <code>npm run lib:build</code> aus, um die Library einmal initial zu bauen.</p> <h2> Library einbinden </h2> <p>Um nun deine Library in der Example Anwendung verwenden zu können, musst du in der Example Anwendung im AppModule (<code>projects/form-builder-example/src/app/app.module.ts</code>) im <code>@NgModule</code> Decorator unter imports die Library importieren.<br> Wichtig: wir wollen die gebaute Version verwenden, deshalb achte darauf, dass du die richtige Datei aus dem <code>dist/</code> Verzeichnis importierst.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="c1">// projects/form-builder-example/src/app/app.module.ts</span> <span class="c1">// ...</span> <span class="k">import</span> <span class="p">{</span><span class="nx">FormBuilderModule</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../../../../dist/form-builder</span><span class="dl">'</span><span class="p">;</span> <span class="c1">// &lt;--</span> <span class="p">@</span><span class="nd">NgModule</span><span class="p">({</span> <span class="c1">// ...</span> <span class="na">imports</span><span class="p">:</span> <span class="p">[</span> <span class="c1">// ...</span> <span class="nx">FormBuilderModule</span><span class="p">,</span> <span class="c1">// &lt;--</span> <span class="p">],</span> <span class="c1">// ...</span> <span class="p">})</span> <span class="k">export</span> <span class="kd">class</span> <span class="nc">AppModule</span> <span class="p">{</span> <span class="p">}</span> </code></pre> </div> <p>Nun kannst du in einer Kommandozeile <code>ng serve</code> und in einer zweiten <code>npm run lib:dev</code> ausführen.</p> <p>Unter <a href="proxy.php?url=http://localhost:4200" rel="noopener noreferrer">http://localhost:4200</a> kannst du deine Testanwendung aufrufen.</p> <h2> Library verwenden </h2> <p>Du kannst nun wie gewohnt im Verzeichnis deiner Example Anwendung den Code aus der Library verwenden. In meinem konkreten Fall habe ich z.B. im <code>app.component.html</code> einfach die <code>&lt;form-builder&gt;</code> Komponente eingebunden.</p> <h2> Zusammenfassung </h2> <p>Du weißt nun was ein Angular Workspace ist und kannst einen solchen anlegen. Außerdem weißt du nun, wie du ein Library Projekt anlegst und in einer example Anwendung einbindest und testen kannst.</p> <p>Zum veröffentlichen der Library im NPM Repository, kannst du dir am besten die <a href="proxy.php?url=https://docs.npmjs.com/packages-and-modules/contributing-packages-to-the-registry" rel="noopener noreferrer">Dokumentation</a> anschauen.</p> <p>Danke für's lesen und ich hoffe ich konnte alles verständlich rüberbringen.</p> <p>Fragen und Anregungen gerne in die Kommentare🙂</p> angular typescript npm german Clean Architecture in TypeScript Projekt: Angular als Plugin Julian Finkler Sat, 30 Nov 2019 22:25:00 +0000 https://dev.to/devtronic/german-clean-architecture-in-einem-typescript-projekt-2-2-28ng https://dev.to/devtronic/german-clean-architecture-in-einem-typescript-projekt-2-2-28ng <p>Im letzten Teil des Beitrags haben wir die Business Logik unseres Aufgaben Tools programmiert. In diesem Teil geht es darum, unsere Anwendung zum Leben zu erwecken.<br> Ich verwende hierzu Angular und ich gehe davon aus, dass du dich damit schon auskennst, weil ich nicht näher auf die Installation der CLI etc. eingehen werde.</p> <h2> Neues Angular Projekt </h2> <p>Als erstes starten wir, außerhalb des Verzeichnisses des letzten Beitrags, ein neues Angular Projekt.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>ng new todo-app Would you like to add Angular routing? No Which stylesheet format would you like to use? CSS CREATE todo-app/README.md <span class="o">(</span>1024 bytes<span class="o">)</span> CREATE todo-app/.editorconfig <span class="o">(</span>246 bytes<span class="o">)</span> CREATE todo-app/.gitignore <span class="o">(</span>629 bytes<span class="o">)</span> CREATE todo-app/angular.json <span class="o">(</span>3825 bytes<span class="o">)</span> CREATE todo-app/package.json <span class="o">(</span>1307 bytes<span class="o">)</span> CREATE todo-app/tsconfig.json <span class="o">(</span>435 bytes<span class="o">)</span> CREATE todo-app/tslint.json <span class="o">(</span>1621 bytes<span class="o">)</span> CREATE todo-app/src/favicon.ico <span class="o">(</span>5430 bytes<span class="o">)</span> CREATE todo-app/src/index.html <span class="o">(</span>294 bytes<span class="o">)</span> CREATE todo-app/src/main.ts <span class="o">(</span>372 bytes<span class="o">)</span> CREATE todo-app/src/polyfills.ts <span class="o">(</span>2841 bytes<span class="o">)</span> CREATE todo-app/src/styles.css <span class="o">(</span>80 bytes<span class="o">)</span> CREATE todo-app/src/test.ts <span class="o">(</span>642 bytes<span class="o">)</span> CREATE todo-app/src/browserslist <span class="o">(</span>388 bytes<span class="o">)</span> CREATE todo-app/src/karma.conf.js <span class="o">(</span>1021 bytes<span class="o">)</span> CREATE todo-app/src/tsconfig.app.json <span class="o">(</span>166 bytes<span class="o">)</span> CREATE todo-app/src/tsconfig.spec.json <span class="o">(</span>256 bytes<span class="o">)</span> CREATE todo-app/src/tslint.json <span class="o">(</span>244 bytes<span class="o">)</span> CREATE todo-app/src/assets/.gitkeep <span class="o">(</span>0 bytes<span class="o">)</span> CREATE todo-app/src/environments/environment.prod.ts <span class="o">(</span>51 bytes<span class="o">)</span> CREATE todo-app/src/environments/environment.ts <span class="o">(</span>662 bytes<span class="o">)</span> CREATE todo-app/src/app/app.module.ts <span class="o">(</span>314 bytes<span class="o">)</span> CREATE todo-app/src/app/app.component.css <span class="o">(</span>0 bytes<span class="o">)</span> CREATE todo-app/src/app/app.component.html <span class="o">(</span>1120 bytes<span class="o">)</span> CREATE todo-app/src/app/app.component.spec.ts <span class="o">(</span>984 bytes<span class="o">)</span> CREATE todo-app/src/app/app.component.ts <span class="o">(</span>212 bytes<span class="o">)</span> CREATE todo-app/e2e/protractor.conf.js <span class="o">(</span>752 bytes<span class="o">)</span> CREATE todo-app/e2e/tsconfig.e2e.json <span class="o">(</span>213 bytes<span class="o">)</span> CREATE todo-app/e2e/src/app.e2e-spec.ts <span class="o">(</span>637 bytes<span class="o">)</span> CREATE todo-app/e2e/src/app.po.ts <span class="o">(</span>251 bytes<span class="o">)</span> ... </code></pre> </div> <h2> Business Logik hinzufügen </h2> <p>Als nächstes kopierst du den Inhalt deiner Business Logik, aus dem letzten Beitrag, nach <code>todo-app/src/app</code>, damit folgende Struktur entsteht:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight plaintext"><code>todo-app/ |- src/ | |- app/ | | |- core/ | | | |- entity/ | | | |- repository/ | | | |- use-case/ | | |- data/ | | |- infrastructure/ | | |- presentation/ | | |- app.module.ts ... </code></pre> </div> <h2> Angular Core Module erzeugen </h2> <p><strong>Dieser Schritt entfällt da die UseCases mit</strong> <code>@Injectable({providedIn: 'root'})</code> <strong>dekoriert werden.</strong></p> <p>Weiter zum nächsten Schritt</p> <p><del>Als nächstes erzeugen wir ein Core Modul für Angular. Darin werden all unsere Use Cases und Services registriert.</del><br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>ng g m core </code></pre> </div> <p><del>Nun sollte eine Datei <code>src/app/core/core.module.ts</code> bei dir angelegt worden sein, welche du nun öffnest.<br> In dem <code>@NgModule</code> Decorator fügst du nun das Feld <code>providers: []</code> hinzu, in welchem du deine UseCases hinterlegst.</del><br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="c1">// src/app/core/core.module.ts</span> <span class="k">import</span> <span class="p">{</span><span class="nx">AddToDoUseCase</span><span class="p">,</span> <span class="nx">DeleteToDoUseCase</span><span class="p">,</span> <span class="nx">EditToDoUseCase</span><span class="p">,</span> <span class="nx">ShowToDoListUseCase</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./use-case</span><span class="dl">'</span><span class="p">;</span> <span class="p">@</span><span class="nd">NgModule</span><span class="p">({</span> <span class="c1">// ...</span> <span class="na">providers</span><span class="p">:</span> <span class="p">[</span> <span class="nx">AddToDoUseCase</span><span class="p">,</span> <span class="nx">DeleteToDoUseCase</span><span class="p">,</span> <span class="nx">EditToDoUseCase</span><span class="p">,</span> <span class="nx">ShowToDoListUseCase</span><span class="p">,</span> <span class="p">]</span> <span class="p">})</span> <span class="k">export</span> <span class="kd">class</span> <span class="nc">CoreModule</span> <span class="p">{</span> <span class="p">}</span> </code></pre> </div> <p><del>Nun musst du noch das <code>CoreModule</code> in dem <code>AppModule</code> importieren, damit in der Angular Anwendung auf die UseCases zugegriffen werden kann.</del><br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="c1">// src/app/app.module.ts</span> <span class="k">import</span> <span class="p">{</span><span class="nx">CoreModule</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./core/core.module</span><span class="dl">'</span><span class="p">;</span> <span class="p">@</span><span class="nd">NgModule</span><span class="p">({</span> <span class="na">imports</span><span class="p">:</span> <span class="p">[</span> <span class="nx">CoreModule</span><span class="p">,</span> <span class="c1">// &lt;--</span> <span class="p">],</span> <span class="p">})</span> <span class="k">export</span> <span class="kd">class</span> <span class="nc">AppModule</span> <span class="p">{</span> <span class="p">}</span> </code></pre> </div> <h2> Dependency Injection Fehler beheben </h2> <p>Wenn du jetzt mit <code>ng serve</code> die Anwendung 'servierst', wirst du beim Aufruf der Anwendung nichts sehen und in der Konsole einen Fehler wie <code>Can't resolve all parameters for AddToDoUseCase: (?, ?, ?).</code> sehen.</p> <p>Das liegt daran, dass Angular die Abhängigkeiten nicht automatisch injiziert. Hierfür gibt es nun zwei Lösungen.</p> <ol> <li>Du verdrahtest alles manuell im <code>CoreModule</code> (<code>{useClass: AddToDoUseCase, dependencies: [...]}</code>)</li> <li>Du fügst den UseCases die <code>@Injectable()</code> Decorator von Angular hinzu.</li> </ol> <p>Beide Lösungen haben Vor- und Nachteile. Bei der ersten Lösung hältst du deinen Code getrennt vom Angular Kram, du hast jedoch jede Menge zu tun um Services zu verdrahten.<br> Bei der zweiten Lösung koppelst du Angular Code an deine UseCases, sparst dir aber auf der anderen Seite viel Schreibarbeit.</p> <p>Und damit kommen wir zu dem 'Beweis' dass sich Architekturen in der Praxis selten 1:1 umsetzen lassen. Ich persönlich habe Variante 1 ausprobiert, diese hat mich jedoch extrem ausgebremst, weswegen ich mittlerweile Variante 2 nutze - auch wenn es nicht schön ist. Aber hey, es ist lediglich ein Decorator der mir die Arbeit sehr vereinfacht und die Fehlerrate reduziert (falsch verdrahtete Services etc.) 😅.<br> (Wer für Lösung 1 eine Alternative kennt, bitte kommentieren 🙂)</p> <p>Deine Aufgabe besteht nun darin, alle vier UseCases mit <code>@Injectable({providedIn: 'root'})</code> zu dekorieren.</p> <p>Nach dem du das erledigt hast, sollte die Anwendung ohne Probleme laden.</p> <h2> Presentation Layer </h2> <p>Als nächstes machen wir uns an die Liste der Aufgaben. Generiere hierzu einfach mit <code>ng g c presentation/todo-list</code> die Angular Komponente.</p> <p>Als nächstes ersetzt du noch den Inhalt der Datei <code>src/app/app.component.html</code> mit<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight html"><code><span class="nt">&lt;h1&gt;</span>Meine Aufgaben<span class="nt">&lt;/h1&gt;</span> <span class="nt">&lt;app-todo-list&gt;&lt;/app-todo-list&gt;</span> </code></pre> </div> <h3> ViewModel und Presenter anlegen </h3> <p>Wie du dich sicherlich erinnerst, verwenden unsere UseCases einen Presenter um Daten darzustellen. Presenter bereiten die Daten entsprechend für die View auf und schreiben die Daten in das ViewModel.</p> <p>Ich habe mir als Namenskonvention <code>[component].view-model.ts</code> und <code>[component].presenter.ts</code> angeeignet. Somit haben die Dateinamen im Verzeichnis der Komponente den gleichen Aufbau.</p> <h4> ViewModel </h4> <p>Da wir in unserer Anwendung die Aufgaben nicht wirklich für die View aufbereiten müssen, reicht es wenn wir einfach das <code>ToDo</code> Entity missbrauchen und dem ViewModel eine Property vom Typen <code>ToDo[]</code> geben.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="c1">// src/app/presentation/todo-list/todo-list.view-model.ts</span> <span class="k">import</span> <span class="p">{</span><span class="nx">ToDo</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../../core/entity</span><span class="dl">'</span><span class="p">;</span> <span class="k">export</span> <span class="kd">class</span> <span class="nc">TodoListViewModel</span> <span class="p">{</span> <span class="k">public</span> <span class="nx">toDos</span><span class="p">:</span> <span class="nx">ToDo</span><span class="p">[]</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span> <span class="p">}</span> </code></pre> </div> <h4> Presenter </h4> <p>Weiter geht es mit dem Presenter. Wenn du dir jetzt denkst, dass du da den <code>ShowToDoListPresenter</code> verwenden musst, liegst du goldrichtig!</p> <p>Unser <code>TodoListPresenter</code> erweitert einfach den <code>ShowToDoListPresenter&lt;T&gt;</code>. Als Typ-Argument geben wir das gerade angelegte <code>TodoListViewModel</code> an. Außerdem rufen wir im Konstruktor <code>super(TodoListViewModel);</code> auf.</p> <p>Jetzt musst du nur noch die <code>displayToDos</code> Methode implementieren, welche lediglich <code>toDos</code> in das ViewModel schreibt, und schon ist unser Presenter für die Aufgabenliste fertig.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="c1">// src/app/presentation/todo-list/todo-list.presenter.ts</span> <span class="k">import</span> <span class="p">{</span><span class="nx">ShowToDoListPresenter</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../../core/use-case</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span><span class="nx">TodoListViewModel</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./todo-list.view-model</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span><span class="nx">ToDo</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../../core/entity</span><span class="dl">'</span><span class="p">;</span> <span class="k">export</span> <span class="kd">class</span> <span class="nc">TodoListPresenter</span> <span class="kd">extends</span> <span class="nc">ShowToDoListPresenter</span><span class="o">&lt;</span><span class="nx">TodoListViewModel</span><span class="o">&gt;</span> <span class="p">{</span> <span class="nf">constructor</span><span class="p">()</span> <span class="p">{</span> <span class="k">super</span><span class="p">(</span><span class="nx">TodoListViewModel</span><span class="p">);</span> <span class="p">}</span> <span class="k">public</span> <span class="nf">displayToDos</span><span class="p">(</span><span class="na">toDos</span><span class="p">:</span> <span class="nx">ToDo</span><span class="p">[]):</span> <span class="k">void</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="nx">viewModel</span><span class="p">.</span><span class="nx">toDos</span> <span class="o">=</span> <span class="nx">toDos</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> </code></pre> </div> <h4> TodoListPresenter bekannt machen </h4> <p>Aktuell weiß Angular noch nicht, was für <code>ShowToDoListPresenter&lt;T&gt;</code> injected werden soll. Um das zu ändern, erzeugst du erst einmal das <code>PresentationModule</code><br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>ng g m presentation </code></pre> </div> <p>Anschließend referenzierst du dieses im <code>AppModule</code>. In diesem entfernst du zusätzlich die Verwendung der <code>TodoListComponent</code>.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="c1">// src/app/app.module.ts</span> <span class="k">import</span> <span class="p">{</span><span class="nx">BrowserModule</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@angular/platform-browser</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span><span class="nx">NgModule</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@angular/core</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span><span class="nx">AppComponent</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./app.component</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span><span class="nx">CoreModule</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./core/core.module</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span><span class="nx">PresentationModule</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./presentation/presentation.module</span><span class="dl">'</span><span class="p">;</span> <span class="p">@</span><span class="nd">NgModule</span><span class="p">({</span> <span class="na">declarations</span><span class="p">:</span> <span class="p">[</span> <span class="nx">AppComponent</span><span class="p">,</span> <span class="p">],</span> <span class="na">imports</span><span class="p">:</span> <span class="p">[</span> <span class="nx">BrowserModule</span><span class="p">,</span> <span class="nx">CoreModule</span><span class="p">,</span> <span class="nx">PresentationModule</span><span class="p">,</span> <span class="p">],</span> <span class="na">providers</span><span class="p">:</span> <span class="p">[],</span> <span class="na">bootstrap</span><span class="p">:</span> <span class="p">[</span><span class="nx">AppComponent</span><span class="p">]</span> <span class="p">})</span> <span class="k">export</span> <span class="kd">class</span> <span class="nc">AppModule</span> <span class="p">{</span> <span class="p">}</span> </code></pre> </div> <p>Im <code>PresentationModule</code> fügst du unter <code>declarations</code> und <code>exports</code> die <code>TodoListComponent</code> hinzu und verdrahtest unter <code>providers</code> die beiden Presenter.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="c1">// src/app/presentation/presentation.module.ts</span> <span class="k">import</span> <span class="p">{</span><span class="nx">NgModule</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@angular/core</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span><span class="nx">CommonModule</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@angular/common</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span><span class="nx">TodoListComponent</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./todo-list/todo-list.component</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span><span class="nx">ShowToDoListPresenter</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../core/use-case</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span><span class="nx">TodoListPresenter</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./todo-list/todo-list.presenter</span><span class="dl">'</span><span class="p">;</span> <span class="p">@</span><span class="nd">NgModule</span><span class="p">({</span> <span class="na">declarations</span><span class="p">:</span> <span class="p">[</span> <span class="nx">TodoListComponent</span><span class="p">,</span> <span class="c1">// &lt;--</span> <span class="p">],</span> <span class="na">imports</span><span class="p">:</span> <span class="p">[</span> <span class="nx">CommonModule</span><span class="p">,</span> <span class="p">],</span> <span class="na">exports</span><span class="p">:</span> <span class="p">[</span> <span class="nx">TodoListComponent</span><span class="p">,</span> <span class="c1">// &lt;--</span> <span class="p">],</span> <span class="na">providers</span><span class="p">:</span> <span class="p">[</span> <span class="p">{</span><span class="na">provide</span><span class="p">:</span> <span class="nx">ShowToDoListPresenter</span><span class="p">,</span> <span class="na">useClass</span><span class="p">:</span> <span class="nx">TodoListPresenter</span><span class="p">},</span> <span class="c1">// &lt;--</span> <span class="p">]</span> <span class="p">})</span> <span class="k">export</span> <span class="kd">class</span> <span class="nc">PresentationModule</span> <span class="p">{</span> <span class="p">}</span> </code></pre> </div> <h3> Component zum Leben erwecken </h3> <p>Nach wie vor macht unsere Anwendung nichts als</p> <blockquote> <h1> Meine Aufgaben </h1> <p>todo-list works!</p> </blockquote> <p>ausgeben. Das wollen wir jetzt ändern.</p> <p>Injecte zunächst den <code>ShowToDoListUseCase</code> als <code>private readonly</code> und den <code>ShowToDoListPresenter&lt;T&gt;</code> als <code>public readonly</code>.<br> Im body des Konstruktors rufst du dann <code>presenter.reset()</code> auf, wodurch das ViewModel neu initialisiert wird. Danach rufst du <code>useCase.execute()</code> auf, um die Business Logic unserer Aufgaben-Liste<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="c1">// src/app/presentation/todo-list/todo-list.component.ts</span> <span class="nf">constructor</span><span class="p">(</span><span class="k">private</span> <span class="k">readonly</span> <span class="nx">useCase</span><span class="p">:</span> <span class="nx">ShowToDoListUseCase</span><span class="p">,</span> <span class="k">public</span> <span class="k">readonly</span> <span class="nx">presenter</span><span class="p">:</span> <span class="nx">ShowToDoListPresenter</span><span class="o">&lt;</span><span class="nx">TodoListViewModel</span><span class="o">&gt;</span><span class="p">,</span> <span class="p">)</span> <span class="p">{</span> <span class="nx">presenter</span><span class="p">.</span><span class="nf">reset</span><span class="p">();</span> <span class="nx">useCase</span><span class="p">.</span><span class="nf">execute</span><span class="p">();</span> <span class="p">}</span> </code></pre> </div> <p>Wenn du die Seite jetzt wieder Aufrufst, erscheint wieder ein Fehler. <br> Die Ursache ist in Zeile 3 zu finden.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight plaintext"><code>AppComponent.ngfactory.js? [sm]:1 ERROR Error: StaticInjectorError(AppModule)[EditToDoUseCase -&gt; TodoRepository]: StaticInjectorError(Platform: core)[EditToDoUseCase -&gt; TodoRepository]: NullInjectorError: No provider for TodoRepository! ... </code></pre> </div> <p>Klar, wir haben ja noch gar keine Datenquelle für unsere Aufgaben.</p> <h2> Daten Layer </h2> <p>Als nächstes müssen wir das <code>TodoRepository</code> implementieren. Dieses wird sich im <code>DataModule</code> befinden, welches mit <code>ng g m data</code> angelegt wird.<br> Dieses musst du, wie auch schon die anderen Module im <code>AppModule</code> importieren.</p> <h3> TodoRepository implementieren </h3> <p>Das Repository ist nichts anderes als ein Angular Service. Diesen erzeugst du mit <code>ng g s data/repository/todo-repository</code>. Die Klasse selber muss natürlich vom <code>TodoRepository</code> aus dem Core extenden.</p> <p>Um das Beispiel einfach zu halten, werden die Aufgaben nur temporär gespeichert, also nach einem Neuladen der Seite ist die Aufgabenliste wieder leer.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="c1">// src/app/data/repository/todo-repository.service.ts</span> <span class="k">import</span> <span class="p">{</span><span class="nx">Injectable</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@angular/core</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span><span class="nx">TodoRepository</span><span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../../core/repository</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span><span class="nx">ToDo</span><span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../../core/entity</span><span class="dl">"</span><span class="p">;</span> <span class="p">@</span><span class="nd">Injectable</span><span class="p">({</span> <span class="na">providedIn</span><span class="p">:</span> <span class="dl">'</span><span class="s1">root</span><span class="dl">'</span> <span class="p">})</span> <span class="k">export</span> <span class="kd">class</span> <span class="nc">TodoRepositoryService</span> <span class="kd">extends</span> <span class="nc">TodoRepository</span> <span class="p">{</span> <span class="k">private</span> <span class="nx">toDos</span><span class="p">:</span> <span class="nx">ToDo</span><span class="p">[]</span> <span class="o">=</span> <span class="p">[];</span> <span class="k">public</span> <span class="k">async</span> <span class="nf">createToDo</span><span class="p">(</span><span class="nx">todo</span><span class="p">:</span> <span class="nx">ToDo</span><span class="p">):</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="nx">ToDo</span><span class="o">&gt;</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="nx">toDos</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="nx">todo</span><span class="p">);</span> <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">toDos</span><span class="p">[</span><span class="k">this</span><span class="p">.</span><span class="nx">toDos</span><span class="p">.</span><span class="nx">length</span> <span class="o">-</span> <span class="mi">1</span><span class="p">];</span> <span class="p">}</span> <span class="k">public</span> <span class="k">async</span> <span class="nf">deleteToDo</span><span class="p">(</span><span class="nx">id</span><span class="p">:</span> <span class="kr">number</span><span class="p">):</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="k">void</span><span class="o">&gt;</span> <span class="p">{</span> <span class="k">if </span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">toDos</span><span class="p">[</span><span class="nx">id</span><span class="p">]</span> <span class="o">===</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span> <span class="k">throw</span> <span class="k">new</span> <span class="nc">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Diese Aufgabe existiert nicht.</span><span class="dl">'</span><span class="p">);</span> <span class="p">}</span> <span class="k">this</span><span class="p">.</span><span class="nx">toDos</span><span class="p">.</span><span class="nf">splice</span><span class="p">(</span><span class="nx">id</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="k">async</span> <span class="nf">editToDo</span><span class="p">(</span><span class="nx">id</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span> <span class="nx">todo</span><span class="p">:</span> <span class="nx">ToDo</span><span class="p">):</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="nx">ToDo</span><span class="o">&gt;</span> <span class="p">{</span> <span class="k">if </span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">toDos</span><span class="p">[</span><span class="nx">id</span><span class="p">]</span> <span class="o">===</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span> <span class="k">throw</span> <span class="k">new</span> <span class="nc">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Diese Aufgabe existiert nicht.</span><span class="dl">'</span><span class="p">);</span> <span class="p">}</span> <span class="k">this</span><span class="p">.</span><span class="nx">toDos</span><span class="p">[</span><span class="nx">id</span><span class="p">]</span> <span class="o">=</span> <span class="nx">todo</span><span class="p">;</span> <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">toDos</span><span class="p">[</span><span class="nx">id</span><span class="p">];</span> <span class="p">}</span> <span class="k">public</span> <span class="k">async</span> <span class="nf">getAllToDos</span><span class="p">():</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="nx">ToDo</span><span class="p">[]</span><span class="o">&gt;</span> <span class="p">{</span> <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">toDos</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> </code></pre> </div> <p>Auch in diesem Fall muss der Service im <code>DataModule</code> bereitgestellt werden<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="c1">// src/app/data/data.module.ts</span> <span class="nx">providers</span><span class="p">:</span> <span class="p">[</span> <span class="p">{</span><span class="na">provide</span><span class="p">:</span> <span class="nx">TodoRepository</span><span class="p">,</span> <span class="na">useClass</span><span class="p">:</span> <span class="nx">TodoRepositoryService</span><span class="p">}</span> <span class="p">]</span> </code></pre> </div> <p>Wenn du die Seite jetzt neu lädst sollte der Fehler verschwinden.</p> <h2> Liste + Aktionsbuttons rendern </h2> <p>Na, schon aus der Puste?🙂 Ist nicht mehr viel, versprochen.<br> Als nächstes soll die Liste mit den Aufgaben sowie Buttons zum anlegen, bearbeiten und löschen angezeigt werden.</p> <p>Wechsle dafür erst einmal in das Template der <code>TodoListComponent</code>.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight html"><code><span class="c">&lt;!-- src/app/presentation/todo-list/todo-list.component.ts --&gt;</span> <span class="nt">&lt;button</span> <span class="na">(click)=</span><span class="s">"addToDo()"</span><span class="nt">&gt;</span>Aufgabe hinzufügen<span class="nt">&lt;/button&gt;</span> <span class="nt">&lt;ul&gt;</span> <span class="nt">&lt;li</span> <span class="na">*ngFor=</span><span class="s">"let todo of presenter.viewModel.toDos; index as id"</span><span class="nt">&gt;</span> <span class="nt">&lt;label&gt;</span> <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"checkbox"</span> <span class="na">[checked]=</span><span class="s">"todo.isDone"</span> <span class="na">(change)=</span><span class="s">"setToDoState(id, todo)"</span><span class="nt">&gt;</span> {{ todo.description }} <span class="nt">&lt;/label&gt;</span> <span class="nt">&lt;button</span> <span class="na">(click)=</span><span class="s">"editToDo(id, todo)"</span><span class="nt">&gt;</span>Bearbeiten<span class="nt">&lt;/button&gt;</span> <span class="nt">&lt;button</span> <span class="na">(click)=</span><span class="s">"deleteToDo(id)"</span><span class="nt">&gt;</span>Löschen<span class="nt">&lt;/button&gt;</span> <span class="nt">&lt;/li&gt;</span> <span class="nt">&lt;/ul&gt;</span> </code></pre> </div> <p>Sollte soweit verständlich sein. Der erste Button ist zum hinzufügen von Aufgaben, die ungeordnete Liste zeigt die Aufgaben an, wobei die Checkbox den Status der Aufgabe anzeigt. Mit den beiden Buttons lässt sich die Aufgabe bearbeiten bzw. löschen.</p> <p>Als nächstes müssen wir den Code für die Buttons schreiben. Wechsle also in den TypeScript Teil der Komponente.<br> Dort fügst du als erstes einmal die anderen UseCases als <code>private readonly</code> im Konstruktor hinzu.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="c1">// src/app/presentation/todo-list/todo-list.component.ts</span> <span class="nf">constructor</span><span class="p">(</span><span class="k">private</span> <span class="k">readonly</span> <span class="nx">useCase</span><span class="p">:</span> <span class="nx">ShowToDoListUseCase</span><span class="p">,</span> <span class="k">public</span> <span class="k">readonly</span> <span class="nx">presenter</span><span class="p">:</span> <span class="nx">ShowToDoListPresenter</span><span class="o">&lt;</span><span class="nx">TodoListViewModel</span><span class="o">&gt;</span><span class="p">,</span> <span class="k">private</span> <span class="k">readonly</span> <span class="nx">addToDoUseCase</span><span class="p">:</span> <span class="nx">AddToDoUseCase</span><span class="p">,</span> <span class="c1">// &lt;--</span> <span class="k">private</span> <span class="k">readonly</span> <span class="nx">editToDoUseCase</span><span class="p">:</span> <span class="nx">EditToDoUseCase</span><span class="p">,</span> <span class="c1">// &lt;--</span> <span class="k">private</span> <span class="k">readonly</span> <span class="nx">deleteToDoUseCase</span><span class="p">:</span> <span class="nx">DeleteToDoUseCase</span><span class="p">,</span> <span class="c1">// &lt;--</span> <span class="p">)</span> <span class="p">{</span> <span class="nx">presenter</span><span class="p">.</span><span class="nf">reset</span><span class="p">();</span> <span class="nx">useCase</span><span class="p">.</span><span class="nf">execute</span><span class="p">();</span> <span class="p">}</span> </code></pre> </div> <p>Anschließend fügst du noch die Funktionen hinzu, welche die Buttons im Template aufrufen.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="c1">// src/app/presentation/todo-list/todo-list.component.ts</span> <span class="k">public</span> <span class="nf">addToDo</span><span class="p">()</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="nx">addToDoUseCase</span><span class="p">.</span><span class="nf">execute</span><span class="p">();</span> <span class="p">}</span> <span class="k">public</span> <span class="nf">setToDoState</span><span class="p">(</span><span class="nx">id</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span> <span class="nx">todo</span><span class="p">:</span> <span class="nx">ToDo</span><span class="p">)</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="nx">editToDoUseCase</span><span class="p">.</span><span class="nf">execute</span><span class="p">({</span><span class="nx">id</span><span class="p">,</span> <span class="nx">todo</span><span class="p">,</span> <span class="na">onlyToggleDone</span><span class="p">:</span> <span class="kc">true</span><span class="p">})</span> <span class="p">}</span> <span class="k">public</span> <span class="nf">editToDo</span><span class="p">(</span><span class="nx">id</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span> <span class="nx">todo</span><span class="p">:</span> <span class="nx">ToDo</span><span class="p">)</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="nx">editToDoUseCase</span><span class="p">.</span><span class="nf">execute</span><span class="p">({</span><span class="nx">id</span><span class="p">,</span> <span class="nx">todo</span><span class="p">,</span> <span class="na">onlyToggleDone</span><span class="p">:</span> <span class="kc">false</span><span class="p">})</span> <span class="p">}</span> <span class="k">public</span> <span class="nf">deleteToDo</span><span class="p">(</span><span class="nx">id</span><span class="p">:</span> <span class="kr">number</span><span class="p">)</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="nx">deleteToDoUseCase</span><span class="p">.</span><span class="nf">execute</span><span class="p">(</span><span class="nx">id</span><span class="p">);</span> <span class="p">}</span> </code></pre> </div> <p>Wenn du jetzt die Seite lädst, dann sollte... es wieder ein Fehler geben 😉<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight plaintext"><code>AppComponent.ngfactory.js? [sm]:1 ERROR Error: StaticInjectorError(AppModule)[EditToDoUseCase -&gt; InteractionService]: StaticInjectorError(Platform: core)[EditToDoUseCase -&gt; InteractionService]: NullInjectorError: No provider for InteractionService! </code></pre> </div> <p>Stimmt, unser Interaction Service fehlt noch.</p> <h2> Infrastruktur Modul und InteractionService </h2> <p>Als letzten Schritt, muss noch das Infrastruktur Modul sowie der InteractionService erzeugt werden. (Modul nicht vergessen zu importieren)<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">$ </span>ng g m infrastructure <span class="nv">$ </span>ng g s infrastructure/service/interaction </code></pre> </div> <p>Im <code>InfrastructureModule</code> verlinkst du wieder den Service aus dem Core:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="c1">// src/app/infrastructure/infrastructure.module.ts</span> <span class="k">import</span> <span class="o">*</span> <span class="k">as</span> <span class="nx">CoreService</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../core/service</span><span class="dl">"</span><span class="p">;</span> <span class="c1">// ...</span> <span class="nl">providers</span><span class="p">:</span> <span class="p">[</span> <span class="p">{</span><span class="na">provide</span><span class="p">:</span> <span class="nx">CoreService</span><span class="p">.</span><span class="nx">InteractionService</span><span class="p">,</span> <span class="na">useClass</span><span class="p">:</span> <span class="nx">InteractionService</span><span class="p">},</span> <span class="p">]</span> </code></pre> </div> <p>Die Implementierung des <code>InteractionService</code> ist auch überschaubar, hier verwenden wir einfach die Standard JS Funktionen <code>confirm</code> und <code>prompt</code>.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="k">import</span> <span class="p">{</span><span class="nx">Injectable</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@angular/core</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="o">*</span> <span class="k">as</span> <span class="nx">CoreService</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../../core/service</span><span class="dl">"</span><span class="p">;</span> <span class="p">@</span><span class="nd">Injectable</span><span class="p">({</span> <span class="na">providedIn</span><span class="p">:</span> <span class="dl">'</span><span class="s1">root</span><span class="dl">'</span> <span class="p">})</span> <span class="k">export</span> <span class="kd">class</span> <span class="nc">InteractionService</span> <span class="k">implements</span> <span class="nx">CoreService</span><span class="p">.</span><span class="nx">InteractionService</span> <span class="p">{</span> <span class="k">public</span> <span class="k">async</span> <span class="nf">confirm</span><span class="p">(</span><span class="nx">message</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="nx">boolean</span><span class="o">&gt;</span> <span class="p">{</span> <span class="k">return</span> <span class="nf">confirm</span><span class="p">(</span><span class="nx">message</span><span class="p">);</span> <span class="p">}</span> <span class="k">public</span> <span class="k">async</span> <span class="nf">enterString</span><span class="p">(</span><span class="nx">currentValue</span><span class="p">?:</span> <span class="kr">string</span><span class="p">):</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="kr">string</span><span class="o">&gt;</span> <span class="p">{</span> <span class="k">return</span> <span class="nf">prompt</span><span class="p">(</span><span class="dl">"</span><span class="s2">Eingabe:</span><span class="dl">"</span><span class="p">,</span> <span class="nx">currentValue</span><span class="p">);</span> <span class="p">}</span> <span class="p">}</span> </code></pre> </div> <p>Und damit bist du fertig🙂.</p> <p>Falls es bei dir Probleme gibt, schau mal bei <a href="proxy.php?url=https://github.com/devtronic/clean-architecture-typescript" rel="noopener noreferrer">GitHub</a> vorbei. Dort findest du den kompletten Code. </p> <h2> Zusammenfassung </h2> <p>Du hast heute die Application Logic für die Business Logic aus dem vorherigen Beitrag implementiert. Aus abstrakten Services wurden konkrete Implementationen. Service Abhängigkeiten werden per Dependency Injection injiziert.<br> Schau dir einmal den tatsächlich geschriebenen Code im Data / Infrastructure / Presentation Layer an. <br> Ich persönlich war anfangs ziemlich erstaunt, wie wenig das ist.</p> <p>Außerdem ist die Kopplung so lose, dass du den TodoRepositoryService problemlos durch einen Service ersetzen könntest, der mit einer API kommuniziert und Daten permanent speichert.</p> <p>Fragen / Anregungen gerne in die Kommentare 🙂</p> typescript architecture angular german JavaScript: Map an array of objects to a dictionary Julian Finkler Sat, 23 Nov 2019 20:45:39 +0000 https://dev.to/devtronic/javascript-map-an-array-of-objects-to-a-dictionary-3f42 https://dev.to/devtronic/javascript-map-an-array-of-objects-to-a-dictionary-3f42 <p>The easiest way for converting an array of objects to a dictionary in JavaScript / TypeScript:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="kd">let</span> <span class="nx">data</span> <span class="o">=</span> <span class="p">[</span> <span class="p">{</span><span class="na">id</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="na">country</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Germany</span><span class="dl">'</span><span class="p">,</span> <span class="na">population</span><span class="p">:</span> <span class="mi">83623528</span><span class="p">},</span> <span class="p">{</span><span class="na">id</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="na">country</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Austria</span><span class="dl">'</span><span class="p">,</span> <span class="na">population</span><span class="p">:</span> <span class="mi">8975552</span><span class="p">},</span> <span class="p">{</span><span class="na">id</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span> <span class="na">country</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Switzerland</span><span class="dl">'</span><span class="p">,</span> <span class="na">population</span><span class="p">:</span> <span class="mi">8616571</span><span class="p">}</span> <span class="p">];</span> <span class="kd">let</span> <span class="nx">dictionary</span> <span class="o">=</span> <span class="nb">Object</span><span class="p">.</span><span class="nf">assign</span><span class="p">({},</span> <span class="p">...</span><span class="nx">data</span><span class="p">.</span><span class="nf">map</span><span class="p">((</span><span class="nx">x</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">({[</span><span class="nx">x</span><span class="p">.</span><span class="nx">id</span><span class="p">]:</span> <span class="nx">x</span><span class="p">.</span><span class="nx">country</span><span class="p">})));</span> <span class="c1">// {1: "Germany", 2: "Austria", 3: "Switzerland"}</span> </code></pre> </div> javascript typescript data Clean Architecture in TypeScript Projekt: Geschäftslogik Julian Finkler Fri, 22 Nov 2019 08:42:06 +0000 https://dev.to/devtronic/german-clean-architecture-in-einem-typescript-projekt-1-2-3lna https://dev.to/devtronic/german-clean-architecture-in-einem-typescript-projekt-1-2-3lna <p>Vorweg: In diesem Beitrag geht es <em>nicht</em> darum Clean Architecture zu erklären, sondern wie meine Erfahrung in einem TypeScript Projekt waren und wie man die Architektur in einem TypeScript bzw. später Angular Projekt implementieren kann.</p> <p>title: "Clean Architecture in einem TypeScript Projekt"</p> <h2> series: Clean Architecture in Typescript </h2> <p>Dieses Jahr habe ich mich ziemlich viel mit Architekturen und Design Patterns auseinander gesetzt.<br> Irgendwann Mitte des Jahres bin ich auf <a href="proxy.php?url=https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html" rel="noopener noreferrer">Clean Architecture</a> von Robert C. Martin gestoßen und war über den Lösungsansatz ziemlich begeistert.</p> <h2> Alles easy going... nicht </h2> <p>Als begeisterter Programmierer wollte ich natürlich das ganze Konzept nun in meine Projekte einbauen. Somit fing ich an, mir das ganze einmal näher anzuschauen. Alles schien auf den ersten Blick im Diagramm ganz logisch und einfach umsetzbar. Es gibt verschiedene Schichten (wie bei der Zwiebel-Architektur), wobei die jeweilige Schicht nur Code in der eigenen Schicht und den inneren Schichten kennt, jedoch nichts äußeres - auch keine Referenzen, Imports etc.. Grundsätzlich lässt sich das mit dem <a href="proxy.php?url=https://de.wikipedia.org/wiki/Dependency-Inversion-Prinzip" rel="noopener noreferrer">DIP</a> lösen.</p> <p>Und dann ging es langsam mit den Fragen wie "Wer erzeugt die Instanzen meiner Klassen?" oder "Wie strukturiere ich meinen Code am besten?" los.</p> <p>Tatsächlich brauchte es 4-6 Wochen bis ich das ganze Konzept hinter Clean Architecture verstanden habe und noch mal zwei weitere Wochen um was halbwegs lauffähiges, was sich an die Architektur hält, zu programmieren.</p> <h2> Eine Architektur lässt sich in der Praxis nie zu 100% umsetzen... </h2> <p>...wenn man Produktiv bleiben möchte. 🙂</p> <p>Ich habe versucht, die Architektur haargenau in einem Angular Projekt umzusetzen. Das Ergebnis war ernüchternd. Die ganzen Vorteile die geschaffen wurden, wurden durch einen Slowdown überdeckt. Viele neue Dateien, welche zum Teil nur Datenstrukturen abbilden und keine vernünftige Ordnerstruktur (Screaming Architecture; ist meiner Meinung nach in Kombination mit Clean Architecture der Obergau) machten ein produktives arbeiten im Projekt unmöglich. <br> Also hieß es <code>git reset --hard origin/master</code> </p> <h2> Grundstruktur definieren </h2> <p>Durch die Erkenntnis, dass es viele neue Dateien geben wird, wollte ich nun zuerst einmal die Ordnerstruktur festlegen. Diese verwende ich mittlerweile überwiegend, da sie für mich gut funktioniert.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight plaintext"><code>Root |- core/ | |- entity/ | |- repository/ | |- use-case/ |- data/ |- infrastructure/ |- presentation/ </code></pre> </div> <p><code>core</code> beinhaltet alles an Business Logic. Dieser Ordner repräsentiert also die beiden innersten Layer. Ich könnte problemlos den Code vom core Verzeichnis von einem Angular Projekt in ein Vue Projekt kopieren und müsste darin nichts ändern um die Businesslogik zu übernehmen.</p> <p>In <code>data</code> werden Datenmodelle, welche z.B. für den Austausch mit einer API benötigt werden, gespeichert. Außerdem befinden sich in diesem Ordner auch die Implementierungen der abstrakten Repositories aus dem Core Verzeichnis.</p> <p>Im Verzeichnis <code>infrastructure</code> sind z.B. Implementierungen der abstrakten Services, welche im Core benötigt werden, zu finden. Hierbei fällt z.B. ein TranslationService oder ein InteractionService der mit dem Benutzer interagiert.</p> <p>Ich denke, die Bedeutung vom Verzeichnis <code>presentation</code> ist klar. Hier findet sich alles, was mit UI zu tun hat.</p> <h2> Grundpfeiler: UseCase und Presenter </h2> <p>Ich habe im Verzeichnis <code>core</code> noch einen Ordner <code>arch</code> angelegt. In diesem befinden sich mit den folgenden drei Dateien, der "Basiscode" für die Architektur:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="c1">// core/arch/index.ts</span> <span class="k">export</span> <span class="o">*</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./use-case</span><span class="dl">'</span><span class="p">;</span> <span class="k">export</span> <span class="o">*</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./presenter</span><span class="dl">'</span><span class="p">;</span> </code></pre> </div> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="c1">// core/arch/presenter.ts</span> <span class="k">export</span> <span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">Presenter</span><span class="o">&lt;</span><span class="nx">TView</span><span class="o">&gt;</span> <span class="p">{</span> <span class="k">public</span> <span class="na">viewModel</span><span class="p">:</span> <span class="nx">TView</span><span class="p">;</span> <span class="nf">constructor</span><span class="p">(</span><span class="k">private</span> <span class="na">template</span><span class="p">:</span> <span class="k">new</span><span class="p">()</span> <span class="o">=&gt;</span> <span class="nx">TView</span><span class="p">,</span> <span class="p">)</span> <span class="p">{</span> <span class="p">}</span> <span class="k">public</span> <span class="nf">reset</span><span class="p">():</span> <span class="k">void</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">model</span> <span class="o">=</span> <span class="k">new</span> <span class="k">this</span><span class="p">.</span><span class="nf">template</span><span class="p">();</span> <span class="k">if </span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">viewModel</span> <span class="o">==</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="nx">viewModel</span> <span class="o">=</span> <span class="nx">model</span><span class="p">;</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="nb">Object</span><span class="p">.</span><span class="nf">assign</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">viewModel</span><span class="p">,</span> <span class="nx">model</span><span class="p">);</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> </code></pre> </div> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="c1">// core/arch/use-case.ts</span> <span class="k">export</span> <span class="kr">interface</span> <span class="nx">IUseCase</span><span class="o">&lt;</span><span class="nx">TRequest</span><span class="p">,</span> <span class="nx">TPresenter</span><span class="o">&gt;</span> <span class="p">{</span> <span class="k">readonly</span> <span class="na">presenter</span><span class="p">:</span> <span class="nx">TPresenter</span><span class="p">;</span> <span class="nf">execute</span><span class="p">(</span><span class="na">request</span><span class="p">:</span> <span class="nx">TRequest</span><span class="p">):</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="k">void</span><span class="o">&gt;</span><span class="p">;</span> <span class="p">}</span> </code></pre> </div> <h2> Eine "To Do App" soll es sein </h2> <p>Bei SPAs ist eine Aufgaben App das "Hallo, Welt!" schlecht hin. Also los geht's. (Den Code gibt's auf <a href="proxy.php?url=https://github.com/devtronic/clean-architecture-typescript" rel="noopener noreferrer">GitHub</a>)<br> Zunächst schreiben wir nur Business Logic, später wird diese in einer Angular Anwendung verwendet.</p> <h3> Erst einmal Use-Cases definieren </h3> <p>Die Use-Cases einer To Do App sind relativ überschaubar:</p> <ul> <li>Liste der Aufgaben anzeigen</li> <li>Aufgabe hinzufügen</li> <li>Aufgabe bearbeiten</li> <li>Aufgabe löschen</li> <li>Aufgabe abhaken</li> <li>Aufgabe abhaken rückgängig machen</li> </ul> <p>Da wir Informatiker "bequem" sind, reichen uns auch vier Use-Cases</p> <ul> <li>Liste der Aufgaben anzeigen</li> <li>Aufgabe hinzufügen</li> <li>Aufgabe bearbeiten (abgehakt ist eine Boolesche Variable 🤓)</li> <li>Aufgabe löschen</li> </ul> <h3> Entity erstellen </h3> <p>Weiter geht's mit dem Model für eine Aufgabe. Um es einfach zu halten, gibt es nur eine Beschreibung und ein "Erledigt" Flag:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="c1">// core/entity/to-do.ts</span> <span class="k">export</span> <span class="kd">class</span> <span class="nc">ToDo</span> <span class="p">{</span> <span class="nf">constructor</span><span class="p">(</span><span class="k">public</span> <span class="nx">description</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="k">public</span> <span class="nx">isDone</span><span class="p">:</span> <span class="nx">boolean</span> <span class="o">=</span> <span class="kc">false</span><span class="p">,</span> <span class="p">)</span> <span class="p">{</span> <span class="p">}</span> <span class="p">}</span> </code></pre> </div> <h3> Repository anlegen </h3> <p>Damit Aufgaben geladen, gespeichert und gelöscht werden können, wird ein Repository benötigt. Da wir uns zunächst im Core bewegen und im nächsten Schritt erst unsere Use Cases implementieren, reicht es eine abstrakte Klasse mit vier abstrakten Methoden anzulegen. Warum bei editToDo und deleteToDo eine id benötigt wird und woher die kommt, erkläre ich später.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="c1">// core/repository/todo.repository.ts</span> <span class="k">import</span> <span class="p">{</span><span class="nx">ToDo</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../entity</span><span class="dl">'</span><span class="p">;</span> <span class="k">export</span> <span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">TodoRepository</span> <span class="p">{</span> <span class="k">public</span> <span class="kd">abstract</span> <span class="nf">getAllToDos</span><span class="p">():</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="nx">ToDo</span><span class="p">[]</span><span class="o">&gt;</span><span class="p">;</span> <span class="k">public</span> <span class="kd">abstract</span> <span class="nf">createToDo</span><span class="p">(</span><span class="nx">todo</span><span class="p">:</span> <span class="nx">ToDo</span><span class="p">):</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="nx">ToDo</span><span class="o">&gt;</span><span class="p">;</span> <span class="k">public</span> <span class="kd">abstract</span> <span class="nf">editToDo</span><span class="p">(</span><span class="nx">id</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span> <span class="nx">todo</span><span class="p">:</span> <span class="nx">ToDo</span><span class="p">):</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="nx">ToDo</span><span class="o">&gt;</span><span class="p">;</span> <span class="k">public</span> <span class="kd">abstract</span> <span class="nf">deleteToDo</span><span class="p">(</span><span class="nx">id</span><span class="p">:</span> <span class="kr">number</span><span class="p">):</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="k">void</span><span class="o">&gt;</span><span class="p">;</span> <span class="p">}</span> </code></pre> </div> <h3> Services anlegen </h3> <p>Damit der User nicht aus versehen eine Aufgabe löscht, muss nach dem Klick auf den löschen Button das löschen zuerst bestätigen. Um das einheitlich abzubilden schreiben wir einen abstrakten <code>interaction.service.ts</code> unter <code>core/service</code> (Verzeichnis anlegen😉). Diese Klasse enthält eine abstrakte Methode <code>confirm</code>, welche einen string als Input bekommt (Die Nachricht die bestätigt werden soll) und ein <code>Promise&lt;boolean&gt;</code> mit dem Ergebnis zurück gibt.<br> Außerdem enthält die Klasse noch eine abstrakte Methode <code>enterString</code>. Du kannst dir sicherlich denken wofür. Richtig, zum Bearbeiten der Aufgaben Beschreibung.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="c1">// core/service/interaction.service.ts</span> <span class="k">export</span> <span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">InteractionService</span> <span class="p">{</span> <span class="k">public</span> <span class="kd">abstract</span> <span class="nf">confirm</span><span class="p">(</span><span class="nx">message</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="nx">boolean</span><span class="o">&gt;</span><span class="p">;</span> <span class="k">public</span> <span class="kd">abstract</span> <span class="nf">enterString</span><span class="p">(</span><span class="nx">currentValue</span><span class="p">?:</span> <span class="kr">string</span><span class="p">):</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="kr">string</span><span class="o">&gt;</span><span class="p">;</span> <span class="p">}</span> </code></pre> </div> <h3> Use Cases </h3> <p>Nun geht's ans Eingemachte. Im Verzeichnis <code>core/use-case</code> wird nun die Datei <code>show-to-do-list.use-case.ts</code> angelegt. In dieser wird der UseCase zum Darstellen der Liste mit den bestehenden Aufgaben programmiert. Der UseCase selber lädt die Aufgaben aus dem <code>TodoRepository</code> und stellt diese mithilfe des Presenters dar.</p> <p>Starten wir mit dem (abstrakten) List Presenter, dieser leitet von <code>Presenter&lt;T&gt;</code> ab, wobei das Typ Argument 1:1 durchgereicht wird. Es wird als nächstes noch eine abstrakte Methode benötigt, welche die geladenen Aufgaben entgegen nimmt.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="c1">// core/use-case/show-to-do-list.use-case.ts</span> <span class="k">import</span> <span class="p">{</span><span class="nx">IUseCase</span><span class="p">,</span> <span class="nx">Presenter</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../arch</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span><span class="nx">ToDo</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../entity</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span><span class="nx">TodoRepository</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../repository</span><span class="dl">'</span><span class="p">;</span> <span class="k">export</span> <span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">ShowToDoListPresenter</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span> <span class="kd">extends</span> <span class="nx">Presenter</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span> <span class="p">{</span> <span class="k">public</span> <span class="kd">abstract</span> <span class="nf">displayToDos</span><span class="p">(</span><span class="na">toDos</span><span class="p">:</span> <span class="nx">ToDo</span><span class="p">[]):</span> <span class="k">void</span><span class="p">;</span> <span class="p">}</span> </code></pre> </div> <p>Als nächstes wird der eigentliche UseCase geschrieben. Dieser leitet vom <code>IUseCase&lt;T1, T1&gt;</code> ab. Wobei als T1 <code>void</code> übergeben wird, weil es ja keine request im eigentlichen Sinne gibt und als T2 der <code>ShowListPresenter&lt;any&gt;</code> übergeben wird.</p> <p>Im <code>constructor</code> wird später der konkrete Presenter und das konkrete TodoRepository injected.</p> <p>Die <code>execute</code> Methode ist relativ einfach gestrickt. Hier werden zuerst alle Aufgaben aus dem repository geladen und anschließend an den Presenter weitergegeben.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="c1">// core/use-case/show-to-do-list.use-case.ts</span> <span class="k">export</span> <span class="kd">class</span> <span class="nc">ShowToDoListUseCase</span> <span class="k">implements</span> <span class="nx">IUseCase</span><span class="o">&lt;</span><span class="k">void</span><span class="p">,</span> <span class="nx">ShowToDoListPresenter</span><span class="o">&lt;</span><span class="kr">any</span><span class="o">&gt;&gt;</span> <span class="p">{</span> <span class="nf">constructor</span><span class="p">(</span><span class="k">public</span> <span class="k">readonly</span> <span class="na">presenter</span><span class="p">:</span> <span class="nx">ShowToDoListPresenter</span><span class="o">&lt;</span><span class="kr">any</span><span class="o">&gt;</span><span class="p">,</span> <span class="k">private</span> <span class="k">readonly</span> <span class="na">repository</span><span class="p">:</span> <span class="nx">TodoRepository</span><span class="p">,</span> <span class="p">)</span> <span class="p">{</span> <span class="p">}</span> <span class="k">public</span> <span class="k">async</span> <span class="nf">execute</span><span class="p">(</span><span class="na">request</span><span class="p">:</span> <span class="k">void</span><span class="p">):</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="k">void</span><span class="o">&gt;</span> <span class="p">{</span> <span class="k">try</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">allToDos</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">repository</span><span class="p">.</span><span class="nf">getAllToDos</span><span class="p">();</span> <span class="k">this</span><span class="p">.</span><span class="nx">presenter</span><span class="p">.</span><span class="nf">displayToDos</span><span class="p">(</span><span class="nx">allToDos</span><span class="p">);</span> <span class="p">}</span> <span class="k">catch </span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Failed to load or present to dos: %o</span><span class="dl">'</span><span class="p">,</span> <span class="nx">e</span><span class="p">);</span> <span class="k">throw</span> <span class="nx">e</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> </code></pre> </div> <h4> Neue Aufgabe anlegen </h4> <p>Der nächste UseCase der implementiert werden soll, ist <code>add-to-do.use-case.ts</code>. Auch hier implementieren wir wieder den <code>IUseCase</code> jedoch mit zwei mal <code>void</code> als Typ, da wir keine Request und auch keinen Presenter haben. Stattdessen injecten wir einfach den <code>ShowToDoListUseCase</code> und führen diesen aus, nachdem die Aufgabe hinzugefügt wurde<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="c1">// core/use-case/add-to-do.use-case.ts</span> <span class="k">import</span> <span class="p">{</span><span class="nx">IUseCase</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../arch</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span><span class="nx">ShowToDoListUseCase</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./show-to-do-list-use.case</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span><span class="nx">InteractionService</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../service</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span><span class="nx">TodoRepository</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../repository</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span><span class="nx">ToDo</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../entity</span><span class="dl">'</span><span class="p">;</span> <span class="k">export</span> <span class="kd">class</span> <span class="nc">AddToDoUseCase</span> <span class="k">implements</span> <span class="nx">IUseCase</span><span class="o">&lt;</span><span class="k">void</span><span class="p">,</span> <span class="k">void</span><span class="o">&gt;</span> <span class="p">{</span> <span class="k">public</span> <span class="k">readonly</span> <span class="na">presenter</span><span class="p">:</span> <span class="k">void</span><span class="p">;</span> <span class="nf">constructor</span><span class="p">(</span><span class="k">private</span> <span class="k">readonly</span> <span class="na">interaction</span><span class="p">:</span> <span class="nx">InteractionService</span><span class="p">,</span> <span class="k">private</span> <span class="k">readonly</span> <span class="na">repository</span><span class="p">:</span> <span class="nx">TodoRepository</span><span class="p">,</span> <span class="k">private</span> <span class="k">readonly</span> <span class="na">listUseCase</span><span class="p">:</span> <span class="nx">ShowToDoListUseCase</span><span class="p">,</span> <span class="p">)</span> <span class="p">{</span> <span class="p">}</span> <span class="k">public</span> <span class="k">async</span> <span class="nf">execute</span><span class="p">(</span><span class="na">request</span><span class="p">:</span> <span class="k">void</span><span class="p">):</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="k">void</span><span class="o">&gt;</span> <span class="p">{</span> <span class="k">try</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">description</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">interaction</span><span class="p">.</span><span class="nf">enterString</span><span class="p">();</span> <span class="k">if </span><span class="p">(</span><span class="nx">description</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span> <span class="nx">description</span><span class="p">.</span><span class="nf">trim</span><span class="p">()</span> <span class="o">===</span> <span class="dl">''</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span> <span class="kd">const</span> <span class="nx">todo</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ToDo</span><span class="p">(</span><span class="nx">description</span><span class="p">);</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">repository</span><span class="p">.</span><span class="nf">createToDo</span><span class="p">(</span><span class="nx">todo</span><span class="p">);</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">listUseCase</span><span class="p">.</span><span class="nf">execute</span><span class="p">();</span> <span class="p">}</span> <span class="k">catch </span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Failed to create a todo: %o</span><span class="dl">'</span><span class="p">,</span> <span class="nx">e</span><span class="p">);</span> <span class="k">throw</span> <span class="nx">e</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> </code></pre> </div> <h4> Aufgabe bearbeiten </h4> <p>Zum bearbeiten von Aufgaben erstellst du den <code>edit-to-do.use-case.ts</code>.<br> In diesem Fall leitet der UseCase wieder von <code>IUseCase</code> ab. Als Request Typen wird <code>EditToDoRequest</code> und als Presenter Typen <code>void</code> übergeben.</p> <p>Die Request Klasse hat drei Felder, die <code>id</code> der Aufgabe, das <code>todo</code> selber und ein Flag <code>onlyToggleDone</code> welches angibt, ob die Aufgabe lediglich als erledigt / nicht erledigt markiert werden soll.</p> <p>In den UseCase selber wird wieder der <code>InteractionService</code>, das <code>TodoRepository</code> und der <code>ShowToDoListUseCase</code> injected.</p> <p>Im execute wird zunächst geprüft, ob lediglich der Status der Aufgabe geändert werden soll. Ist das der Fall wird das <code>isDone</code> Flag der Aufgabe umgekehrt. Falls nicht, wird solange nach einer neuen Aufgaben Beschreibung gefragt, bis der User die Eingabe abbricht (<code>null</code>) oder mindestens ein nicht Whitespace Zeichen eingibt. </p> <p>Anschließend wird in beiden Fällen die Aufgabe mit dem Repository gespeichert und der <code>ShowToDoListUseCase</code> ausgeführt, um die Liste zu aktualisieren.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="c1">// core/use-case/edit-to-do.use-case.ts</span> <span class="k">import</span> <span class="p">{</span><span class="nx">IUseCase</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../arch</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span><span class="nx">ToDo</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../entity</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span><span class="nx">InteractionService</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../service</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span><span class="nx">TodoRepository</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../repository</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span><span class="nx">ShowToDoListUseCase</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./show-to-do-list.use-case</span><span class="dl">'</span><span class="p">;</span> <span class="k">export</span> <span class="kd">class</span> <span class="nc">EditToDoRequest</span> <span class="p">{</span> <span class="nf">constructor</span><span class="p">(</span><span class="k">public</span> <span class="k">readonly</span> <span class="nx">id</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span> <span class="k">public</span> <span class="k">readonly</span> <span class="nx">todo</span><span class="p">:</span> <span class="nx">ToDo</span><span class="p">,</span> <span class="k">public</span> <span class="k">readonly</span> <span class="nx">onlyToggleDone</span><span class="p">:</span> <span class="nx">boolean</span> <span class="o">=</span> <span class="kc">false</span><span class="p">,</span> <span class="p">)</span> <span class="p">{</span> <span class="p">}</span> <span class="p">}</span> <span class="k">export</span> <span class="kd">class</span> <span class="nc">EditToDoUseCase</span> <span class="k">implements</span> <span class="nx">IUseCase</span><span class="o">&lt;</span><span class="nx">EditToDoRequest</span><span class="p">,</span> <span class="k">void</span><span class="o">&gt;</span> <span class="p">{</span> <span class="k">readonly</span> <span class="na">presenter</span><span class="p">:</span> <span class="k">void</span><span class="p">;</span> <span class="nf">constructor</span><span class="p">(</span><span class="k">private</span> <span class="k">readonly</span> <span class="na">interaction</span><span class="p">:</span> <span class="nx">InteractionService</span><span class="p">,</span> <span class="k">private</span> <span class="k">readonly</span> <span class="na">repository</span><span class="p">:</span> <span class="nx">TodoRepository</span><span class="p">,</span> <span class="k">private</span> <span class="k">readonly</span> <span class="na">listUseCase</span><span class="p">:</span> <span class="nx">ShowToDoListUseCase</span><span class="p">,</span> <span class="p">)</span> <span class="p">{</span> <span class="p">}</span> <span class="k">public</span> <span class="k">async</span> <span class="nf">execute</span><span class="p">(</span><span class="na">request</span><span class="p">:</span> <span class="nx">EditToDoRequest</span><span class="p">):</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="k">void</span><span class="o">&gt;</span> <span class="p">{</span> <span class="k">try</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">todo</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ToDo</span><span class="p">(</span><span class="nx">request</span><span class="p">.</span><span class="nx">todo</span><span class="p">.</span><span class="nx">description</span><span class="p">,</span> <span class="nx">request</span><span class="p">.</span><span class="nx">todo</span><span class="p">.</span><span class="nx">isDone</span><span class="p">);</span> <span class="k">if </span><span class="p">(</span><span class="nx">request</span><span class="p">.</span><span class="nx">onlyToggleDone</span><span class="p">)</span> <span class="p">{</span> <span class="nx">todo</span><span class="p">.</span><span class="nx">isDone</span> <span class="o">=</span> <span class="o">!</span><span class="nx">todo</span><span class="p">.</span><span class="nx">isDone</span><span class="p">;</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="k">do</span> <span class="p">{</span> <span class="nx">todo</span><span class="p">.</span><span class="nx">description</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">interaction</span><span class="p">.</span><span class="nf">enterString</span><span class="p">(</span><span class="nx">todo</span><span class="p">.</span><span class="nx">description</span><span class="p">);</span> <span class="k">if </span><span class="p">(</span><span class="nx">todo</span><span class="p">.</span><span class="nx">description</span> <span class="o">==</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="k">while </span><span class="p">(</span><span class="nx">todo</span><span class="p">.</span><span class="nx">description</span><span class="p">.</span><span class="nf">trim</span><span class="p">()</span> <span class="o">==</span> <span class="dl">''</span><span class="p">)</span> <span class="p">}</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">repository</span><span class="p">.</span><span class="nf">editToDo</span><span class="p">(</span><span class="nx">request</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span> <span class="nx">todo</span><span class="p">);</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">listUseCase</span><span class="p">.</span><span class="nf">execute</span><span class="p">();</span> <span class="p">}</span> <span class="k">catch </span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Failed to edit a todo: %o</span><span class="dl">'</span><span class="p">,</span> <span class="nx">e</span><span class="p">);</span> <span class="k">throw</span> <span class="nx">e</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> </code></pre> </div> <h4> Aufgabe löschen </h4> <p>Zuletzt fehlt nun noch der Use Case zum löschen einer Aufgabe.<br> In diesem UseCase wird als Request Typ eine Zahl, nämlich die ID der Aufgabe übergeben.<br> Der Konstruktor sollte mittlerweile klar sein.</p> <p>Beim <code>execute</code> wird der Nutzer zunächst gefragt, ob die Aufgabe gelöscht werden soll. Beantwortet der diese Frage mit <code>false</code>, wird die Ausführung des UseCase abgebrochen.<br> Andernfalls wird die Aufgabe mithilfe des <code>TodoRepository</code> gelöscht und die Liste der Aufgaben mit dem <code>ShowToDoListUseCase</code> aktualisiert.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="c1">// core/use-case/delete-to-do.use-case.ts</span> <span class="k">import</span> <span class="p">{</span><span class="nx">IUseCase</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../arch</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span><span class="nx">InteractionService</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../service</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span><span class="nx">TodoRepository</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../repository</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span><span class="nx">ShowToDoListUseCase</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./show-to-do-list.use-case</span><span class="dl">'</span><span class="p">;</span> <span class="k">export</span> <span class="kd">class</span> <span class="nc">DeleteToDoUseCase</span> <span class="k">implements</span> <span class="nx">IUseCase</span><span class="o">&lt;</span><span class="kr">number</span><span class="p">,</span> <span class="k">void</span><span class="o">&gt;</span> <span class="p">{</span> <span class="k">readonly</span> <span class="na">presenter</span><span class="p">:</span> <span class="k">void</span><span class="p">;</span> <span class="nf">constructor</span><span class="p">(</span><span class="k">private</span> <span class="k">readonly</span> <span class="na">interaction</span><span class="p">:</span> <span class="nx">InteractionService</span><span class="p">,</span> <span class="k">private</span> <span class="k">readonly</span> <span class="na">repository</span><span class="p">:</span> <span class="nx">TodoRepository</span><span class="p">,</span> <span class="k">private</span> <span class="k">readonly</span> <span class="na">listUseCase</span><span class="p">:</span> <span class="nx">ShowToDoListUseCase</span><span class="p">,</span> <span class="p">)</span> <span class="p">{</span> <span class="p">}</span> <span class="k">public</span> <span class="k">async</span> <span class="nf">execute</span><span class="p">(</span><span class="na">id</span><span class="p">:</span> <span class="kr">number</span><span class="p">):</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="k">void</span><span class="o">&gt;</span> <span class="p">{</span> <span class="k">try</span> <span class="p">{</span> <span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">interaction</span><span class="p">.</span><span class="nf">confirm</span><span class="p">(</span><span class="dl">'</span><span class="s1">Soll die Aufgabe gelöscht werden?</span><span class="dl">'</span><span class="p">))</span> <span class="p">{</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">repository</span><span class="p">.</span><span class="nf">deleteToDo</span><span class="p">(</span><span class="nx">id</span><span class="p">);</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">listUseCase</span><span class="p">.</span><span class="nf">execute</span><span class="p">();</span> <span class="p">}</span> <span class="k">catch </span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Failed to delete a todo: %o</span><span class="dl">'</span><span class="p">,</span> <span class="nx">e</span><span class="p">);</span> <span class="k">throw</span> <span class="nx">e</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> </code></pre> </div> <h2> Zusammenfassung </h2> <p>Du hast nun die komplette Business Logik unserer Aufgaben App programmiert.<br> Wie du sicherlich festgestellt hast, wurde noch keinen Gedanke an die Datenhaltung oder an das UI verschwendet, und das ist gut so.<br> Der Code ist so allgemein wie möglich gehalten, dass es sich hierbei um eine Konsolen-Anwendung oder eine Browser Anwendung handeln könnte.<br> Erst mit der Implementierung der Services und der Controller wird definiert, wo die Anwendung später laufen wird, wobei alles "drum-herum" als "Plugins" für deine Anwendung zu verstehen ist. Die Datenbank ist ein Plugin, das UI ist ein Plugin und auch ein Framework ist lediglich ein Plugin. Programmiere nicht nach einem Framework sondern passe das Framework an, sodass es mit deiner Anwendung funktioniert 😉 (z.B. per Wrapper Klassen).</p> <p>Danke für's lesen, ich hoffe es war verständlich bis hier hin.</p> <p>Im zweiten Teil des Beitrags geht es darum, die Services zu implementieren und mit Angular die Anwendung zum laufen zu bringen.</p> typescript architecture angular german