<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Trung Vo</title>
    <description>The latest articles on DEV Community by Trung Vo (@trungk18).</description>
    <link>https://dev.to/trungk18</link>
    <image>
      <url>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%2F420743%2Fd5e9e5e4-9883-4615-8854-92bf9683924f.png</url>
      <title>DEV Community: Trung Vo</title>
      <link>https://dev.to/trungk18</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/trungk18"/>
    <language>en</language>
    <item>
      <title>Built a Spotify client with Angular 11, Nx, ngrx, TailwindCSS and ng-zorro</title>
      <dc:creator>Trung Vo</dc:creator>
      <pubDate>Fri, 02 Apr 2021 07:38:59 +0000</pubDate>
      <link>https://dev.to/trungk18/built-a-spotify-client-with-angular-11-nx-ngrx-tailwindcss-and-ng-zorro-3lm7</link>
      <guid>https://dev.to/trungk18/built-a-spotify-client-with-angular-11-nx-ngrx-tailwindcss-and-ng-zorro-3lm7</guid>
      <description>&lt;h1&gt;
  
  
  Angular Spotify
&lt;/h1&gt;

&lt;p&gt;A simple Spotify client built with Angular 11, Nx workspace, ngrx, TailwindCSS and ng-zorro.&lt;/p&gt;

&lt;h2&gt;
  
  
  Source code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/trungvose" rel="noopener noreferrer"&gt;
        trungvose
      &lt;/a&gt; / &lt;a href="https://github.com/trungvose/angular-spotify" rel="noopener noreferrer"&gt;
        angular-spotify
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Spotify client built with Angular 15, Nx Workspace, ngrx, TailwindCSS and ng-zorro
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Working application
&lt;/h2&gt;

&lt;p&gt;Check out the &lt;strong&gt;live application&lt;/strong&gt; -&amp;gt; &lt;a href="https://spotify.trungk18.com" rel="noopener noreferrer"&gt;https://spotify.trungk18.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Spotify premium&lt;/strong&gt; is required for the Web Playback SDK to play music. If you are using a free account, you can still browse the app, but it couldn't play the music. Sorry about that 🤣&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Ftrungk18%2Fangular-spotify%2Fraw%2Fmain%2Fapps%2Fangular-spotify%2Fsrc%2Fassets%2Freadme%2Fangular-spotify-demo-short.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Ftrungk18%2Fangular-spotify%2Fraw%2Fmain%2Fapps%2Fangular-spotify%2Fsrc%2Fassets%2Freadme%2Fangular-spotify-demo-short.gif" alt="Angular Spotify Demo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Ftrungk18%2Fangular-spotify%2Fraw%2Fmain%2Fapps%2Fangular-spotify%2Fsrc%2Fassets%2Freadme%2Fangular-spotify-visualization.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Ftrungk18%2Fangular-spotify%2Fraw%2Fmain%2Fapps%2Fangular-spotify%2Fsrc%2Fassets%2Freadme%2Fangular-spotify-visualization.gif" alt="Angular Spotify Visualizer"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Ftrungk18%2Fangular-spotify%2Fraw%2Fmain%2Fapps%2Fangular-spotify%2Fsrc%2Fassets%2Freadme%2Fangular-spotify-album-art.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Ftrungk18%2Fangular-spotify%2Fraw%2Fmain%2Fapps%2Fangular-spotify%2Fsrc%2Fassets%2Freadme%2Fangular-spotify-album-art.gif" alt="Angular Spotify Web Player"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Ftrungk18%2Fangular-spotify%2Fraw%2Fmain%2Fapps%2Fangular-spotify%2Fsrc%2Fassets%2Freadme%2Fangular-spotify-web-player.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Ftrungk18%2Fangular-spotify%2Fraw%2Fmain%2Fapps%2Fangular-spotify%2Fsrc%2Fassets%2Freadme%2Fangular-spotify-web-player.png" alt="Angular Spotify Web Player"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Support
&lt;/h2&gt;

&lt;p&gt;If you like my work, feel free to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⭐ &lt;a href="https://github.com/trungk18/angular-spotify" rel="noopener noreferrer"&gt;this repository&lt;/a&gt;. And we will be happy together :)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://twitter.com/intent/tweet?url=https://github.com/trungk18/angular-spotify&amp;amp;text=A%20cool%20Spotify%20client%20made%20with%20Angular%2011,%20Nx%20workspace,%20ngrx,%20TailwindCSS%20and%20ng-zorro&amp;amp;hashtags=angularspotify,angular,nx,ngrx,ngzorro,typescript" rel="noopener noreferrer"&gt;Tweet&lt;/a&gt; about Angular Spotify&lt;/li&gt;
&lt;li&gt;Get me a &lt;a href="https://www.buymeacoffee.com/tuantrungvo" rel="noopener noreferrer"&gt;coffee&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks a bunch for stopping by and supporting me!&lt;/p&gt;

&lt;h2&gt;
  
  
  Who is it for 🤷‍♀️
&lt;/h2&gt;

&lt;p&gt;I still remember Window Media Player on windows has the visualization when you start to play the music, and I wanted to have the same experience when listening to Spotify. That is the reason I started this project.&lt;/p&gt;

&lt;p&gt;I found that there weren't many resources on building a proper real-world scale application, and that's my focus for sharing. I made a &lt;a href="https://jira.trungk18.com/" rel="noopener noreferrer"&gt;Jira clone application&lt;/a&gt; as the first one for that purpose. &lt;a href="https://nx.dev/" rel="noopener noreferrer"&gt;Nx workspace&lt;/a&gt; is getting more and more attention from the Angular community, but people are always confused about how to architect and set up a Nx project. I hope Angular Spotify will give you more insight on that despite the fact that it is my first project using Nx 🤣&lt;/p&gt;




&lt;p&gt;You know I am one of the moderators of &lt;a href="https://twitter.com/ngvnofficial" rel="noopener noreferrer"&gt;Angular Vietnam&lt;/a&gt;. Recently, I also started &lt;a href="https://twitter.com/angularsg" rel="noopener noreferrer"&gt;Angular Singapore&lt;/a&gt;. This piece of work is my another long-term plan to bring Angular knowledge to more people. I desire to advocate and grow the Angular developer community in Singapore and Vietnam :)&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech stack
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Ftrungk18%2Fangular-spotify%2Fraw%2Fmain%2Fapps%2Fangular-spotify%2Fsrc%2Fassets%2Freadme%2Fangular-spotify-tech-stack.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Ftrungk18%2Fangular-spotify%2Fraw%2Fmain%2Fapps%2Fangular-spotify%2Fsrc%2Fassets%2Freadme%2Fangular-spotify-tech-stack.png" alt="Tech logos"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://angular.io/" rel="noopener noreferrer"&gt;Angular 11&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nx.dev/" rel="noopener noreferrer"&gt;Nx Workspace&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/ngneat" rel="noopener noreferrer"&gt;ngneat&lt;/a&gt; packages includes: &lt;code&gt;ngneat/svg-icon&lt;/code&gt; and &lt;code&gt;ngneat/until-destroy&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ngrx.io/" rel="noopener noreferrer"&gt;ngrx&lt;/a&gt; and &lt;a href="https://ngrx.io/guide/component-store" rel="noopener noreferrer"&gt;ngrx/component-store&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ng.ant.design/docs/introduce/en" rel="noopener noreferrer"&gt;ng-zorro&lt;/a&gt; UI component: &lt;code&gt;tooltip&lt;/code&gt;, &lt;code&gt;modal&lt;/code&gt;, &lt;code&gt;slider&lt;/code&gt;, &lt;code&gt;switch&lt;/code&gt; and more.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;TailwindCSS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://netlify.com/" rel="noopener noreferrer"&gt;Netlify&lt;/a&gt; for deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I experimented with the ngrx/component store for &lt;code&gt;AuthStore&lt;/code&gt; and &lt;code&gt;UIStore&lt;/code&gt;. It might not be a best practice and I will refactor it very soon. Just FYI 🤣&lt;/p&gt;

&lt;h2&gt;
  
  
  High-level design
&lt;/h2&gt;

&lt;p&gt;See my original notes on &lt;a href="https://gist.github.com/trungk18/7ef8766cafc05bc8fd87be22de6c5b12" rel="noopener noreferrer"&gt;Nx workspace structure for NestJS and Angular&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Principles
&lt;/h3&gt;

&lt;p&gt;All components are following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OnPush Change Detection and async pipes: all components use observable and async pipe for rendering data without any single manual subscribe. Only some places are calling subscribe for dispatching an action, which I will have a refactor live stream session with my friend &lt;a href="https://github.com/nartc" rel="noopener noreferrer"&gt;@nartc&lt;/a&gt; to use the component store for a fully subscribe-less application.&lt;/li&gt;
&lt;li&gt;SCAMs (single component Angular modules) for tree-shakable components, meaning each component will have a respective module. For example, a RegisterComponent will have a corresponding RegisterModule. We won't declare RegisterComponent as part of AuthModule, for instance.&lt;/li&gt;
&lt;li&gt;Mostly, everything will stay in the &lt;code&gt;libs&lt;/code&gt; folder. New modules, new models, new configurations, new components etc... are in libs. libs should be separated into different directories based on existing apps. We won't put them inside the apps folder. For example in an Angular, contains the &lt;code&gt;main.ts&lt;/code&gt;, &lt;code&gt;app.component.ts&lt;/code&gt; and &lt;code&gt;app.module.ts&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Structure
&lt;/h3&gt;

&lt;p&gt;I followed the structure recommended by my friend &lt;a href="https://github.com/nartc" rel="noopener noreferrer"&gt;@nartc&lt;/a&gt;. Below is the simplified version of the application structure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
└── root
    ├── apps
    │   └── angular-spotify
    └── libs
        └── web (dir)
            ├── shell (dir)
            │   ├── feature (angular:lib) - for configure any forRoot modules
            │   └── ui
            │       └── layout (angular:lib)
            ├── playlist (dir)
            │   ├── data-access (angular:lib, service, state management)
            │   ├── features
            │   │   ├── list (angular:lib PlaylistsComponent)
            │   │   └── detail (angular:lib PlaylistDetailComopnent)
            │   └── ui (dir)
            │       └── playlist-track (angular:lib, SCAM for Component)
            ├── visualizer (dir)
            │   ├── data-access (angular:lib)
            │   └── feature
            ├── home (dir)
            │   ├── data-access (angular:lib)
            │   ├── feature (angular:lib)
            │   └── ui (dir)
            │       ├── featured-playlists (angular:lib, SCAM for Component)
            │       ├── greeting (angular:lib, SCAM for Component)
            │       └── recent-played (angular:lib, SCAM for Component)
            └── shared (dir)
                ├── app-config (injection token for environment)
                ├── data-access (angular:lib, API call, Service or State management to share across the Client app)
                ├── ui (dir)
                ├── pipes (dir)
                ├── directives (dir)
                └── utils (angular:lib, usually shared Guards, Interceptors, Validators...)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Authentication Flow
&lt;/h3&gt;

&lt;p&gt;I follow &lt;code&gt;Implicit Grant Flow&lt;/code&gt; that Spotify recommended for client-side only application and does not involve secret keys. The access tokens that are issued are short-lived, and there are no refresh tokens to extend them when they expire.&lt;/p&gt;

&lt;p&gt;View the &lt;a href="https://developer.spotify.com/documentation/general/guides/authorization-guide/" rel="noopener noreferrer"&gt;Spotify Authorization Guide&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Upon opening Angular Spotify, It will redirect you to Spotify to get access to your data. Angular Spotify only uses the data purely for displaying on the UI. We won't store your information anywhere else.&lt;/li&gt;
&lt;li&gt;Angular Spotify only keeps the access token in the browser memory without even storing it into browser local storage. If you do a hard refresh on the browser, It will ask for a new access token from Spotify. One access token is only valid for &lt;strong&gt;one hour&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;After having the token, I'll try to connect to the Web Playback SDK with a new player - &lt;code&gt;Angular Spotify Web Player&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Ftrungk18%2Fangular-spotify%2Fraw%2Fmain%2Fapps%2Fangular-spotify%2Fsrc%2Fassets%2Freadme%2Fsdk-flow.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Ftrungk18%2Fangular-spotify%2Fraw%2Fmain%2Fapps%2Fangular-spotify%2Fsrc%2Fassets%2Freadme%2Fsdk-flow.png" alt="Angular Spotify Web Playback SDK flow"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Dependency Graph
&lt;/h3&gt;

&lt;p&gt;Nx provides an &lt;a href="https://nx.dev/latest/angular/structure/dependency-graph" rel="noopener noreferrer"&gt;dependency graph&lt;/a&gt; out of the box. To view it on your browser, clone my repository and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dep-graph
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A simplified graph looks like the following. It gives you insightful information for your mono repo and especially helpful when multiple projects are depending on each other.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Ftrungk18%2Fangular-spotify%2Fraw%2Fmain%2Fapps%2Fangular-spotify%2Fsrc%2Fassets%2Freadme%2Fdep-graph.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Ftrungk18%2Fangular-spotify%2Fraw%2Fmain%2Fapps%2Fangular-spotify%2Fsrc%2Fassets%2Freadme%2Fdep-graph.png" alt="Angular Spotify Dependency Graph"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Nx Computation Cache
&lt;/h3&gt;

&lt;p&gt;Having Nx Cloud configured shorten the deployment time quite a lot.&lt;/p&gt;

&lt;p&gt;Nx Cloud pairs with Nx in order to enable you to build and test code more rapidly, by up to 10 times. Even teams that are new to Nx can connect to Nx Cloud and start saving time instantly. Visit &lt;a href="https://nx.app/" rel="noopener noreferrer"&gt;Nx Cloud&lt;/a&gt; to learn more.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Ftrungk18%2Fangular-spotify%2Fraw%2Fmain%2Fapps%2Fangular-spotify%2Fsrc%2Fassets%2Freadme%2Fnx-cloud.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Ftrungk18%2Fangular-spotify%2Fraw%2Fmain%2Fapps%2Fangular-spotify%2Fsrc%2Fassets%2Freadme%2Fnx-cloud.png" alt="Nx cloud"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Features and Roadmap
&lt;/h2&gt;

&lt;p&gt;I set the tentative deadline for motivating myself to finish the work on time. Otherwise, It will take forever to complete :)&lt;/p&gt;

&lt;h3&gt;
  
  
  1.0 - Simple Spotify client
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;March 01 - 28, 2021&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;[x] Proven, scalable, and easy to understand structure with Nx workspace&lt;/li&gt;
&lt;li&gt;[x] Play music using Spotify SDK&lt;/li&gt;
&lt;li&gt;[x] Load a maximum of 50 save playlists and top 100 songs per playlist.&lt;/li&gt;
&lt;li&gt;[x] Cool visualization&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Live stream
&lt;/h2&gt;

&lt;p&gt;Let work on it together!&lt;/p&gt;

&lt;p&gt;I scheduled a few live stream sessions to show you how I continue developing Angular Spotify. Follow &lt;a href="https://twitter.com/tuantrungvo" rel="noopener noreferrer"&gt;my twitter&lt;/a&gt; for the latest updates. See the scheduled events.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;Description/Link&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Sat, 3rd April 2021, 10AM&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.youtube.com/watch?v=9njo6MZWBN0" rel="noopener noreferrer"&gt;Structure your Angular application with Nx workspace&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Sat, 10th April 2021, 10AM&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.youtube.com/watch?v=vEIxjcrXcDc" rel="noopener noreferrer"&gt;Build the album list and detail pages&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Sat, 17th April 2021, 10AM&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.youtube.com/watch?v=8P3pB40JF2w" rel="noopener noreferrer"&gt;Build the artist list and detail pages&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Sat, 24th April 2021, 10AM&lt;/td&gt;
&lt;td&gt;&lt;a href="https://youtu.be/Oj4yomnxfj4" rel="noopener noreferrer"&gt;Build the track list page&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I will also do some refactoring with &lt;a href="https://github.com/nartc" rel="noopener noreferrer"&gt;@nartc&lt;/a&gt; for Angular Vietnam Office Hours. More detail is coming soon.&lt;/p&gt;

&lt;h2&gt;
  
  
  Time spending
&lt;/h2&gt;

&lt;p&gt;It is a side project that I only spent time outside of the office hours to work on. I initially planned to complete the project within two weeks, but the first two weekends were not very productive, maybe because of the holiday mood from Lunar New Year :) But once the lego blocks are getting together, I feel the rhythm, and I know it has to be finished by the end of March.&lt;/p&gt;

&lt;p&gt;I couldn't get the full-time report from waka time because it only shows me the latest two weeks. 🤣&lt;/p&gt;

&lt;p&gt;I have spent approximately 50 hours working on this project, which is almost the same amount that I worked on the first version of &lt;a href="https://jira.trungk18.com/" rel="noopener noreferrer"&gt;jira.trungk18.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The visualizer was the most exciting feature, and I decided to start this project because of that single component.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Ftrungk18%2Fangular-spotify%2Fraw%2Fmain%2Fapps%2Fangular-spotify%2Fsrc%2Fassets%2Freadme%2Ftime-spending.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Ftrungk18%2Fangular-spotify%2Fraw%2Fmain%2Fapps%2Fangular-spotify%2Fsrc%2Fassets%2Freadme%2Ftime-spending.png" alt="Angular Spotify - Time spending"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Accessibility ♿
&lt;/h3&gt;

&lt;p&gt;Not all components have properly defined &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA" rel="noopener noreferrer"&gt;aria attributes&lt;/a&gt;, visual focus indicators, etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the development environment 🛠
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;git clone https://github.com/trungk18/angular-spotify.git&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cd angular-spotify&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;npm start&lt;/code&gt; for starting Angular web application&lt;/li&gt;
&lt;li&gt;The app should run on &lt;code&gt;http://localhost:4200/&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Unit/Integration tests 🧪
&lt;/h3&gt;

&lt;p&gt;I skipped writing test for this project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compatibility
&lt;/h2&gt;

&lt;p&gt;Web Playback SDK provided supports for Chrome, Firefox, Edge, IE 11, or above running on Mac/Windows/Linux.&lt;/p&gt;

&lt;p&gt;It &lt;strong&gt;doesn't support&lt;/strong&gt; Safari or any mobile browser on &lt;strong&gt;Android&lt;/strong&gt; or &lt;strong&gt;iOS&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;View &lt;a href="https://developer.spotify.com/documentation/web-playback-sdk/#supported-browsers" rel="noopener noreferrer"&gt;completed list of supported browsers&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Author: Trung Vo ✍️
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A seasoned front-end engineer with seven years of passion in creating experience-driven products. I am proficient in Angular, React and TypeScript development.&lt;/li&gt;
&lt;li&gt;Personal blog: &lt;a href="https://trungk18.com/" rel="noopener noreferrer"&gt;https://trungk18.com/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Say hello: trungk18 [et] gmail [dot] com&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Contributing
&lt;/h2&gt;

&lt;p&gt;If you have any ideas, just &lt;a href="https://github.com/trungk18/angular-spotify/issues/new" rel="noopener noreferrer"&gt;open an issue&lt;/a&gt; and tell me what you think.&lt;/p&gt;

&lt;p&gt;If you'd like to contribute, please fork the repository and make changes as you'd like. &lt;a href="https://github.com/trungk18/angular-spotify/compare" rel="noopener noreferrer"&gt;Pull requests&lt;/a&gt; are warmly welcome.&lt;/p&gt;

&lt;h2&gt;
  
  
  Credits and reference
&lt;/h2&gt;

&lt;p&gt;Special thanks to my friend &lt;a href="https://github.com/nartc" rel="noopener noreferrer"&gt;@nartc&lt;/a&gt;, who helped me review the code early.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Resource&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github1s.com/koel/core/blob/master/js/utils/visualizer.ts" rel="noopener noreferrer"&gt;@koel/koel&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;A cool player made by &lt;a href="https://twitter.com/notphanan" rel="noopener noreferrer"&gt;@phanan&lt;/a&gt;, I reused the visualizer code from this repo with my additional customization&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/beeman/component-store-playground" rel="noopener noreferrer"&gt;beeman/component-store-playground&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;A nice example of using Nx with ngrx/component-store, I refer to the project structure from this repo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://timdeschryver.dev/blog/start-using-ngrx-effects-for-this" rel="noopener noreferrer"&gt;Start using ngrx/effects for this&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;An excellent write up by &lt;a href="https://twitter.com/tim_deschryver" rel="noopener noreferrer"&gt;Tim Deschryver&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  License
&lt;/h2&gt;

&lt;p&gt;Feel free to use my code on your project. Please put a reference to this repository.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://opensource.org/licenses/MIT" rel="noopener noreferrer"&gt;MIT&lt;/a&gt;&lt;/p&gt;

</description>
      <category>angular</category>
      <category>typescript</category>
      <category>tailwindcss</category>
    </item>
    <item>
      <title>Build an Angular component to display snow ❄️ effect</title>
      <dc:creator>Trung Vo</dc:creator>
      <pubDate>Wed, 23 Dec 2020 00:39:19 +0000</pubDate>
      <link>https://dev.to/trungk18/build-an-angular-component-to-display-snow-effect-78n</link>
      <guid>https://dev.to/trungk18/build-an-angular-component-to-display-snow-effect-78n</guid>
      <description>&lt;p&gt;2020 is about to an end, and the holiday session is coming. It was such a particular year for you and me. I have been staying in Singapore for more than eight months without traveling anywhere else. Probably, I will not be able to come back home for our upcoming Tet holiday. But tough times will make us stronger, I believe so :)&lt;/p&gt;

&lt;h2&gt;
  
  
  Jira clone snow theme
&lt;/h2&gt;

&lt;p&gt;I did a quick snow theme for Jira Clone based on an awesome &lt;a href="https://codepen.io/alphardex/pen/dyPorwJ" rel="noopener noreferrer"&gt;codepen&lt;/a&gt;, written purely with CSS.&lt;/p&gt;

&lt;p&gt;That's my result -&amp;gt; &lt;a href="https://jira.trungk18.com/project/issue/2020" rel="noopener noreferrer"&gt;https://jira.trungk18.com/project/issue/2020&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Ftrungk18%2Fjira-clone-angular%2Fraw%2Fmaster%2Ffrontend%2Fsrc%2Fassets%2Fimg%2Fmerry-christmas-2020.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Ftrungk18%2Fjira-clone-angular%2Fraw%2Fmaster%2Ffrontend%2Fsrc%2Fassets%2Fimg%2Fmerry-christmas-2020.gif" alt="Build a component to display snow ❄️ effect with Angular"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Snow component
&lt;/h2&gt;

&lt;p&gt;So let go ahead and create a new &lt;code&gt;SnowComponent&lt;/code&gt;. We don't need to do anything with that component. The heavy lifting part is the template and styling.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-snow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;templateUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./snow.component.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;styleUrls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./snow.component.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SnowComponent&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the scss file, and paste the below code. Basically, each snowflake will have a random position, opacity, and delay. And we applied it by generating different &lt;code&gt;keyframe&lt;/code&gt; animation.&lt;/p&gt;

&lt;p&gt;The code looks pretty short, but the CSS compiled version could be huge. 😂&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="k"&gt;@function&lt;/span&gt; &lt;span class="nf"&gt;random_range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$min&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$max&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;$rand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nv"&gt;$random_range&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$min&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$rand&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;$max&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;$min&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="k"&gt;@return&lt;/span&gt; &lt;span class="nv"&gt;$random_range&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.snow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;$total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;pointer-events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;#a3b1bc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;@for&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="ow"&gt;from&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="ow"&gt;through&lt;/span&gt; &lt;span class="nv"&gt;$total&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$random-x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1000000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="mi"&gt;.0001vw&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$random-offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;random_range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;-100000&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="m"&gt;100000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="mi"&gt;.0001vw&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$random-x-end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$random-x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;$random-offset&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$random-x-end-yoyo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$random-x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$random-offset&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$random-yoyo-time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;random_range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;30000&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="m"&gt;80000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="m"&gt;100000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$random-yoyo-y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$random-yoyo-time&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;100vh&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$random-scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="mi"&gt;.0001&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$fall-duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;random_range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;1s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$fall-delay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;-1s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nd"&gt;:nth-child&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;8000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="mi"&gt;.0001&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$random-x&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="m"&gt;-10px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$random-scale&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;fall-&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="nv"&gt;$fall-duration&lt;/span&gt; &lt;span class="nv"&gt;$fall-delay&lt;/span&gt; &lt;span class="n"&gt;linear&lt;/span&gt; &lt;span class="n"&gt;infinite&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="nt"&gt;fall-&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nf"&gt;percentage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$random-yoyo-time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$random-x-end&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$random-yoyo-y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$random-scale&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="nt"&gt;to&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$random-x-end-yoyo&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="m"&gt;100vh&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$random-scale&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And last, the snow template. It is just a bunch of &lt;code&gt;&amp;lt;div class="snow"&amp;gt;&lt;/code&gt;, the exact number of &lt;code&gt;div&lt;/code&gt; should be equal to the &lt;code&gt;$total&lt;/code&gt; variable we defined on the styling.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"snow"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;❅&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"snow"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;❅&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"snow"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;❅&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"snow"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;❅&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"snow"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;❅&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- code remove for brevity --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now, you can apply the snow component into your component. That's all. See the result below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fes14c90h3djq5edqtnm3.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fes14c90h3djq5edqtnm3.gif" alt="Build a component to display snow ❄️ effect with Angular"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Set overflow hidden for body
&lt;/h2&gt;

&lt;p&gt;Notice on the above result, there was both vertical and horizontal scrollbar. We don't want that to happens.&lt;/p&gt;

&lt;p&gt;To fix, add &lt;code&gt;overflow: hidden&lt;/code&gt; for &lt;code&gt;body&lt;/code&gt; to style.css.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We're done! See the final source code and output below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Source code
&lt;/h2&gt;

&lt;p&gt;&lt;iframe src="https://stackblitz.com/edit/angular-ivy-snow?view=preview" width="100%" height="500"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>angular</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Build a rich text editor in Angular with ngx-quill</title>
      <dc:creator>Trung Vo</dc:creator>
      <pubDate>Wed, 04 Nov 2020 11:51:29 +0000</pubDate>
      <link>https://dev.to/trungk18/build-a-rich-text-editor-in-angular-with-ngx-quill-4i6d</link>
      <guid>https://dev.to/trungk18/build-a-rich-text-editor-in-angular-with-ngx-quill-4i6d</guid>
      <description>&lt;p&gt;If you notice, the current &lt;a href="https://jira.trungk18.com/" rel="noopener noreferrer"&gt;jira.trungk18.com&lt;/a&gt; is using a rich text HTML editor. This tutorial will help you to create one using &lt;code&gt;ngx-quill&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That's how a rich text editor looks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fih9rkjbdt2461xryuqt8.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fih9rkjbdt2461xryuqt8.gif" alt="Angular Jira Clone Part 07 - Build a rich text editor"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;See &lt;a href="https://trungk18.com/tags/jira-clone/" rel="noopener noreferrer"&gt;all tutorials for Jira clone&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Source code and demo
&lt;/h2&gt;

&lt;p&gt;&lt;iframe src="https://stackblitz.com/edit/angular-markdown-editor-jira?view=preview" width="100%" height="500"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Rich Editor Module
&lt;/h2&gt;

&lt;p&gt;Like a markdown text editor, I will reuse a rich text editor in many places on a web application. So that I will create a brand new module, &lt;code&gt;RichTextEditorModule&lt;/code&gt;, for that purpose. At the moment, it will have only one component, &lt;code&gt;RichTextEditorComponent&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fnc23ksicqfj2wwj16xvs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fnc23ksicqfj2wwj16xvs.png" alt="Angular Jira Clone Part 07 - Build a rich text editor"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is not much code inside its module and component.&lt;/p&gt;

&lt;p&gt;rich-text-editor.component.ts&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rich-text-editor&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;templateUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./rich-text-editor.component.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;styleUrls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./rich-text-editor.component.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RichTextEditorComponent&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;OnInit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
  &lt;span class="nf"&gt;ngOnInit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;rich-text-editor.module.ts&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;NgModule&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;CommonModule&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;RichTextEditorComponent&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;declarations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;RichTextEditorComponent&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MarkdownEditorModule&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;No worry, we will add more code to the component. 😆&lt;/p&gt;

&lt;h2&gt;
  
  
  ngx-quill
&lt;/h2&gt;

&lt;p&gt;To build a rich text editor from scratch could take me the same time to make the whole Jira clone application. That's why I am utilizing &lt;a href="https://github.com/KillerCodeMonkey/ngx-quill" rel="noopener noreferrer"&gt;ngx-quill&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;ngx-quill is an angular module for the &lt;a href="https://quilljs.com/" rel="noopener noreferrer"&gt;Quill Rich Text Editor&lt;/a&gt; containing all components you need.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

npm &lt;span class="nb"&gt;install &lt;/span&gt;ngx-quill


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;For projects using Angular &amp;lt; v5.0.0, please run.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

npm &lt;span class="nb"&gt;install &lt;/span&gt;ngx-quill@1.6.0


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Basic Usage
&lt;/h3&gt;
&lt;h4&gt;
  
  
  1. Import &lt;code&gt;QuillModule&lt;/code&gt; into your &lt;code&gt;AppModule&lt;/code&gt;
&lt;/h4&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;NgModule&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;...,&lt;/span&gt;

    &lt;span class="nx"&gt;QuillModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forRoot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppModule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h4&gt;
  
  
  2. Import &lt;code&gt;QuillModule&lt;/code&gt; into &lt;code&gt;RichTextEditorModule&lt;/code&gt;
&lt;/h4&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CommonModule&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/common&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NgModule&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RichTextEditorComponent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./rich-text-editor.component&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;QuillModule&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ngx-quill&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;NgModule&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;CommonModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;QuillModule&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;declarations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;RichTextEditorComponent&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;RichTextEditorComponent&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RichTextEditorModule&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h4&gt;
  
  
  3. Import quill themes CSS into &lt;code&gt;styles.scss&lt;/code&gt;
&lt;/h4&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;

&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s2"&gt;'~quill/dist/quill.core.css'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s2"&gt;'~quill/dist/quill.snow.css'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Build our customize rich text editor component
&lt;/h3&gt;

&lt;p&gt;I can now use  in the &lt;code&gt;RichTextEditorComponent&lt;/code&gt;. I will go ahead and place that HTML in my component template. I set a class name &lt;code&gt;content-editor&lt;/code&gt; so that I can style it later.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;quill-editor&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"content-editor"&lt;/span&gt; &lt;span class="na"&gt;[placeholder]=&lt;/span&gt;&lt;span class="s"&gt;"''"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;&amp;lt;/quill-editor&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;See the result. Because quill is a compelling library, the rendered component has a textbox and most of the default toolbar button available for us.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ftw7lvsixtec75q6z3q6n.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ftw7lvsixtec75q6z3q6n.gif" alt="Angular Jira Clone Part 07 - Build a rich text editor"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My job now is pretty simple to customize the component with only the button that I need and some CSS styling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Toolbar configuration
&lt;/h3&gt;

&lt;p&gt;Below is the current configuration that I use for one toolbar row with some basic commands.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;QuillConfiguration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;toolbar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bold&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;italic&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;underline&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;strike&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blockquote&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;code-block&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ordered&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bullet&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;header&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;link&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;clean&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And then I passed it down to &lt;code&gt;modules&lt;/code&gt; input of the &lt;code&gt;quill-editor&lt;/code&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;quill-editor&lt;/span&gt;
  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"content-editor"&lt;/span&gt;
  &lt;span class="na"&gt;[placeholder]=&lt;/span&gt;&lt;span class="s"&gt;"''"&lt;/span&gt;
  &lt;span class="na"&gt;[modules]=&lt;/span&gt;&lt;span class="s"&gt;"quillConfiguration"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/quill-editor&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;That's the result with lesser command.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Femwlkcx4gbvmvpijpbwh.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Femwlkcx4gbvmvpijpbwh.gif" alt="Angular Jira Clone Part 07 - Build a rich text editor"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Noted that by default, &lt;code&gt;ngx-quill&lt;/code&gt; will render a short textarea, and it will automatically expand to fill the height as you type. You might want to set a default &lt;code&gt;min-height&lt;/code&gt;. I did set default &lt;code&gt;120px&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;quill-editor&lt;/span&gt;
  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"content-editor"&lt;/span&gt;
  &lt;span class="na"&gt;[placeholder]=&lt;/span&gt;&lt;span class="s"&gt;"''"&lt;/span&gt;
  &lt;span class="na"&gt;[modules]=&lt;/span&gt;&lt;span class="s"&gt;"quillConfiguration"&lt;/span&gt;
  &lt;span class="na"&gt;[styles]=&lt;/span&gt;&lt;span class="s"&gt;"{'min-height': '120px'}"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/quill-editor&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;I guess it looks right now. The leftover part is to connect it with a form :)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fw79ilnos2x3sa6kk0lyd.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fw79ilnos2x3sa6kk0lyd.gif" alt="Angular Jira Clone Part 07 - Build a rich text editor"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Connect RichTextEditorComponent to a form
&lt;/h3&gt;

&lt;p&gt;ngx-quill provided support for both &lt;code&gt;ReactiveForms&lt;/code&gt; and &lt;code&gt;TemplateForm&lt;/code&gt;. I shifted only to use ReactiveForms. That's why I will follow a similar approach as the Markdown component to take a &lt;code&gt;FormControl&lt;/code&gt; as an &lt;code&gt;Input&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RichTextEditorComponent&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;OnInit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;quillConfiguration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;QuillConfiguration&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;control&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FormControl&lt;/span&gt;

  &lt;span class="nf"&gt;ngOnInit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;control&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;control&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormControl&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;quill-editor&lt;/span&gt;
  &lt;span class="na"&gt;[formControl]=&lt;/span&gt;&lt;span class="s"&gt;"control"&lt;/span&gt;
  &lt;span class="na"&gt;[placeholder]=&lt;/span&gt;&lt;span class="s"&gt;"''"&lt;/span&gt;
  &lt;span class="na"&gt;[modules]=&lt;/span&gt;&lt;span class="s"&gt;"quillConfiguration"&lt;/span&gt;
  &lt;span class="na"&gt;[styles]=&lt;/span&gt;&lt;span class="s"&gt;"{'min-height': '120px'}"&lt;/span&gt;
  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"content-editor"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/quill-editor&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;See the result when I pair it inside a form. Work perfectly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F5x4fz20as6wuik45xp1k.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F5x4fz20as6wuik45xp1k.gif" alt="Angular Jira Clone Part 07 - Build a rich text editor"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Homework
&lt;/h3&gt;

&lt;p&gt;There is some small improvement that I leave it to you.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set a border when focusing into the rich text editor&lt;/li&gt;
&lt;li&gt;Implement &lt;a href="https://angular.io/api/forms/ControlValueAccessor" rel="noopener noreferrer"&gt;ControlValueAccessor&lt;/a&gt; for the &lt;code&gt;RichTextEditorComponent&lt;/code&gt; so that you can use both &lt;code&gt;[ngModel]&lt;/code&gt; and &lt;code&gt;formControl&lt;/code&gt; in a form :)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's all for a rich text editor with Angular. Any questions, you can leave it in the comment box below or reach me on Twitter. Thanks for stopping by!&lt;/p&gt;

</description>
      <category>angular</category>
      <category>typescript</category>
      <category>css</category>
      <category>jiraclone</category>
    </item>
    <item>
      <title>A childhood memory Tetris game built with Angular 10 and Akita</title>
      <dc:creator>Trung Vo</dc:creator>
      <pubDate>Tue, 29 Sep 2020 22:52:17 +0000</pubDate>
      <link>https://dev.to/trungk18/a-childhood-memory-tetris-game-built-with-angular-10-and-akita-2ap4</link>
      <guid>https://dev.to/trungk18/a-childhood-memory-tetris-game-built-with-angular-10-and-akita-2ap4</guid>
      <description>&lt;h2&gt;
  
  
  Working Game
&lt;/h2&gt;

&lt;p&gt;Check out the &lt;strong&gt;working game&lt;/strong&gt; -&amp;gt; &lt;a href="https://tetris.trungk18.com"&gt;https://tetris.trungk18.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The game has sounds, wear your 🎧 or turn on your 🔊 for a better experience.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8E_mhyym--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/j1tc0mphgoyfawzlqs1t.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8E_mhyym--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/j1tc0mphgoyfawzlqs1t.gif" alt="A childhood memory Tetris game built with Angular 10 and Akita" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Please tweet and tag me @tuantrungvo for any issues that you are currently facing!&lt;br&gt;
Thanks for your understanding. Stay tuned!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qzoRVRzH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/oncmh4doytj4qedc5wtj.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qzoRVRzH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/oncmh4doytj4qedc5wtj.gif" alt="A childhood memory Tetris game built with Angular 10 and Akita" width="300" height="580"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Source Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/trungk18"&gt;
        trungk18
      &lt;/a&gt; / &lt;a href="https://github.com/trungk18/angular-tetris"&gt;
        angular-tetris
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Tetris game built with Angular and Akita 🎮
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;If you like my work, feel free to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⭐ this repository. And we will be happy together :)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://twitter.com/intent/tweet?url=https%3A%2F%2Fgithub.com%2Ftrungk18%2Fangular-tetris&amp;amp;text=Awesome%20Tetris%20game%20built%20with%20Angular%2010%20and%20Akita%2C%20can%20you%20get%20999999%20points%3F&amp;amp;hashtags=angular,angulartetris,akita,typescript"&gt;Tweet&lt;/a&gt; about Angular Tetris&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks a bunch for stopping by and supporting me!&lt;/p&gt;

&lt;h2&gt;
  
  
  Why?
&lt;/h2&gt;

&lt;p&gt;Tetris was the first game that my dad bought for me and It cost about 1$ US at that time. It didn't sound a lot today. But 20 years ago, 1$ can feed my family for at least a few days. Put it that way, with 1\$ you can buy two dozens eggs.&lt;br&gt;
This is the only gaming "machine" that I ever had until my first computer arrived. I have never had a SNES or PS1 at home.&lt;/p&gt;

&lt;p&gt;My Tetris was exactly in the same yellow color and it was so big, running on 2 AA battery. It is how it looks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cGjmFK6P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://github.com/trungk18/angular-tetris/raw/master/src/assets/readme/retro-tetris.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cGjmFK6P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://github.com/trungk18/angular-tetris/raw/master/src/assets/readme/retro-tetris.jpg" alt="Retro Tetris" width="300" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After showing my wife the &lt;a href="https://github.com/Binaryify/vue-tetris"&gt;Tetris game built with Vue&lt;/a&gt;. She asked me why I didn't build the same &lt;u&gt;Tetris with Angular&lt;/u&gt;? And here you go.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The game can hold up to a maximum score of 999999 (one million minus one 😂) and I have never reached that very end.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Who is this for?
&lt;/h2&gt;

&lt;p&gt;I built this game dedicated to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For anyone that grew up with Tetris as a part of your memory. It was my childhood memory and I hope you enjoy the game as well.&lt;/li&gt;
&lt;li&gt;For the Angular developer community, I have never really seen a game that built with Angular and that's my answer. Using Akita as the underlying state management helps me to see all of the data flow, it is great for debugging. I wanted to see more Angular game from you guys 💪💪💪&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  How to play
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Before playing
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You can use both keyboard and mouse to play. But prefer to use &lt;u&gt;keyboard&lt;/u&gt;
&lt;/li&gt;
&lt;li&gt;Press arrow left and right to change the speed of the game &lt;strong&gt;(1 - 6)&lt;/strong&gt;. The higher the number, the faster the piece will fall&lt;/li&gt;
&lt;li&gt;Press arrow up and down to change how many of lines have been filled before starting the game &lt;strong&gt;(1 - 10)&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;Space&lt;/code&gt; to start the game&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;P&lt;/code&gt; for pause/resume game&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;R&lt;/code&gt; for resetting the game&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;S&lt;/code&gt; for the turn on/off the sounds&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Playing game
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Press &lt;code&gt;Space&lt;/code&gt; make the piece drop quickly&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;Arrow left&lt;/code&gt; and &lt;code&gt;right&lt;/code&gt; for moving left and right&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;Arrow up&lt;/code&gt; to rotate the piece&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;Arrow down&lt;/code&gt; to move a piece faster&lt;/li&gt;
&lt;li&gt;When clearing lines, you will receive a point - 100 points for 1 line, 300 points for 2 lines, 700 points for 3 lines, 1500 points for 4 lines&lt;/li&gt;
&lt;li&gt;The drop speed of the pieces increases with the number of rows eliminated (one level up for every 20 lines cleared)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Techstack
&lt;/h2&gt;

&lt;p&gt;I built it barely with Angular and Akita, no additional UI framework/library was required.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_aiPvPWK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://github.com/trungk18/angular-tetris/raw/master/src/assets/readme/tech-stack.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_aiPvPWK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://github.com/trungk18/angular-tetris/raw/master/src/assets/readme/tech-stack.png" alt="Angular Tetris" width="572" height="253"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Development Challenge
&lt;/h2&gt;

&lt;p&gt;I got the inspiration from the same but different &lt;a href="https://github.com/Binaryify/vue-tetris"&gt;Tetris game built with Vue&lt;/a&gt;. To not reinvented the wheel, I started to look at Vue code and thought it would be very identical to Angular. But later one, I realized a few catches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Vue source code was written a few years ago with pure JS. I could find several problems that the compiler didn't tell you. Such as giving &lt;code&gt;parseInt&lt;/code&gt; a number. It is still working though, but I don't like it.&lt;/li&gt;
&lt;li&gt;There was extensive use of &lt;code&gt;setTimeout&lt;/code&gt; and &lt;code&gt;setInterval&lt;/code&gt; for making animations. I rewrote all of the animation logic using RxJS. You will see the detail below.&lt;/li&gt;
&lt;li&gt;The brain of the game also used &lt;code&gt;setTimeout&lt;/code&gt; for the game loop. It was not a problem, but I was having a &lt;u&gt;hard time&lt;/u&gt; understanding the code on some essential elements: how to render the piece to the UI, how the calculation makes sense with XY axis. In the end, I changed all of the logic to a proper OOP way using TypeScript class, based on &lt;a href="https://github.com/chrum/ngx-tetris"&gt;@chrum/ngx-tetris&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Tetris Core
&lt;/h3&gt;

&lt;p&gt;It is the most important part of the game. As I am following the Vue source code, It is getting harder to understand what was the developer's intention. The Vue version inspired me but I think I have to write the core Tetris differently.&lt;/p&gt;

&lt;p&gt;Take a look at the two blocks of code below which do the same rendering piece on the screen and you will understand what I am talking about. The left side was rewritten with Angular and TypeScript and the right side was the JS version.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jJrEfdCH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://github.com/trungk18/angular-tetris/raw/master/src/assets/readme/compare01.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jJrEfdCH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://github.com/trungk18/angular-tetris/raw/master/src/assets/readme/compare01.png" alt="Angular Tetris" width="800" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I always think that your code must be written as you talk to people, without explaining a word. Otherwise, when someone comes in and reads your code and maintains it, they will be struggling.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“ Code is like humor. When you have to explain it, it’s bad.” – Cory House&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And let me emphasize it again, I didn't write the brain of the game from scratch. I adapted the well-written source by &lt;a href="https://github.com/chrum/ngx-tetris"&gt;@chrum/ngx-tetris&lt;/a&gt; for Tetris core. I did refactor some parts to support Akita and wrote some new functionality as well.&lt;/p&gt;
&lt;h3&gt;
  
  
  Akita state management + dev tool support
&lt;/h3&gt;

&lt;p&gt;Although you don't dispatch any action, Akita will still do it undo the hood as the Update action. And you still can see the data with &lt;a href="https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=en"&gt;Redux DevTools&lt;/a&gt;. Remember to put that option into your &lt;code&gt;AppModule&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;production&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AkitaNgDevtools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forRoot&lt;/span&gt;&lt;span class="p"&gt;()];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I turn it on all the time on &lt;a href="https://tetris.trungk18.com"&gt;tetris.trungk18.com&lt;/a&gt;, you can open the DevTools and start seeing the data flow.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7Qw1sCgT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://github.com/trungk18/angular-tetris/raw/master/src/assets/readme/akita-devtool.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7Qw1sCgT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://github.com/trungk18/angular-tetris/raw/master/src/assets/readme/akita-devtool.gif" alt="Angular Tetris" width="800" height="347"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: opening the DevTools could reduce the performance of the game significantly. I recommended you turn it off when you want to archive a high score 🤓&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Customizing Piece
&lt;/h3&gt;

&lt;p&gt;I defined a base &lt;a href="https://github.com/trungk18/angular-tetris/blob/master/src/app/interface/piece/piece.ts"&gt;Piece class&lt;/a&gt; for a piece. And for each type of piece, it will extend from the same base class to inherit the same capability&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;Piece&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;rotation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;PieceRotation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Deg0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PieceTypes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Shape&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Shape&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;_shapes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Shapes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;_lastConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Partial&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Piece&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;Piece&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_lastConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;rotation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rotation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shape&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_newPiece&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;//code removed for brevity&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, I have a piece L. I create a new class name &lt;a href="https://github.com/trungk18/angular-tetris/blob/master/src/app/interface/piece/L.ts"&gt;PieceL&lt;/a&gt;. I will contain the shape of L in four different rotation so that I don't have to mess up with the math to do minus plus on the XY axis. And I think defining in that way makes the code self-express better. If you see 1, it means on the matrix it will be filled, 0 mean empty tile.&lt;/p&gt;

&lt;p&gt;If my team member needs to maintain the code, I hope he will understand what I was trying to write immediately. Or maybe not 🤣&lt;/p&gt;

&lt;p&gt;One import property of the Piece is the &lt;code&gt;next&lt;/code&gt; property to display the piece shape on the decoration box for the upcoming piece.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ShapesL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Shapes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="nx"&gt;ShapesL&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;PieceRotation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Deg0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="nx"&gt;ShapesL&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;PieceRotation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Deg90&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="c1"&gt;//code removed for brevity&lt;/span&gt;

&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;PieceL&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Piece&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;PieceTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;L&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setShapes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ShapesL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now is the interesting part, you create a custom piece by yourself. Simply create a new class that extends from &lt;code&gt;Piece&lt;/code&gt; with different rotations.&lt;/p&gt;

&lt;p&gt;For instance, I will define a new piece call F with class name &lt;a href="https://github.com/trungk18/angular-tetris/blob/feature/pieceF/src/app/interface/piece/F.ts"&gt;&lt;code&gt;PieceF&lt;/code&gt;&lt;/a&gt;. That is how it should look like.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ShapesF&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Shapes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="nx"&gt;ShapesF&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;PieceRotation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Deg0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;PieceF&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Piece&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;PieceTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;F&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setShapes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ShapesF&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the last step, go to &lt;a href="https://github.com/trungk18/angular-tetris/blob/master/src/app/factory/piece-factory.ts"&gt;PieceFactory&lt;/a&gt; to add the new PieceF into the available pieces.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;PieceFactory&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;_available&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;Piece&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;//code removed for brevity&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_available&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PieceF&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And you're all set, this is the result. See how easy it is to understand the code and add a custom piece that you like.&lt;/p&gt;

&lt;p&gt;The source code for that custom piece F, you can see at &lt;a href="https://github.com/trungk18/angular-tetris/tree/feature/pieceF"&gt;feature/pieceF&lt;/a&gt; branch.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YarfOJdG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://github.com/trungk18/angular-tetris/raw/master/src/assets/readme/piecef-demo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YarfOJdG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://github.com/trungk18/angular-tetris/raw/master/src/assets/readme/piecef-demo.gif" alt="Angular Tetris Piece F" width="645" height="596"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Animation
&lt;/h3&gt;

&lt;p&gt;I rewrote the animation with RxJS. See the comparison below for the simple dinosaurs running animation at the beginning of the game.&lt;/p&gt;

&lt;p&gt;You could do a lot of stuff if you know RxJS well enough :) I think I need to strengthen my RxJS knowledge soon enough as well. Super powerful.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DOBvv5eA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://github.com/trungk18/angular-tetris/raw/master/src/assets/readme/compare02.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DOBvv5eA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://github.com/trungk18/angular-tetris/raw/master/src/assets/readme/compare02.png" alt="Angular Tetris" width="800" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The actual result doesn't look very identical but it is good enough in my standard.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uzGqkHyf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://github.com/trungk18/angular-tetris/raw/master/src/assets/readme/compare02-result.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uzGqkHyf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://github.com/trungk18/angular-tetris/raw/master/src/assets/readme/compare02-result.gif" alt="Angular Tetris" width="800" height="593"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Web Audio API
&lt;/h3&gt;

&lt;p&gt;There are many sound effects in the game such as when you press space, or left, right. In reality, all of the sounds were a reference to a single file &lt;a href="https://github.com/trungk18/angular-tetris/blob/master/src/assets/tetris-sound.mp3"&gt;assets/tetris-sound.mp3&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I don't have much experience working with audio before but the Web Audio API looks very promising. You could do more with it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;See the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API"&gt;official documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;See how I load the mp3 file and store it in &lt;a href="https://github.com/trungk18/angular-tetris/blob/master/src/app/services/sound-manager.service.ts"&gt;sound-manager.service.ts&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/Guide/Audio_and_video_delivery/Web_Audio_API_cross_browser"&gt;Writing Web Audio API code that works in every browser&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Keyboard handling
&lt;/h3&gt;

&lt;p&gt;I planned to use &lt;a href="https://github.com/ngneat/hotkeys"&gt;@ngneat/hotkeys&lt;/a&gt; but I decided to use &lt;code&gt;@HostListener&lt;/code&gt; instead. A simple implementation could look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;HostListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;KeyDown&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;TetrisKeyboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Left&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;keyDownLeft&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_soundManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;move&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_keyboardService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setKeỵ&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hasCurrent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_tetrisService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;moveLeft&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_tetrisService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;decreaseLevel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See more at &lt;a href="https://github.com/trungk18/angular-tetris/blob/master/src/app/containers/angular-tetris/angular-tetris.component.ts"&gt;containers/angular-tetris/angular-tetris.component.ts&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Features and Roadmap
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Phase 1 - Angular Tetris basic functionality
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;July 10 - 23, 2020&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Proven, scalable, and easy to understand project structure&lt;/li&gt;
&lt;li&gt;Basic Tetris functionality&lt;/li&gt;
&lt;li&gt;Six levels&lt;/li&gt;
&lt;li&gt;Local storage high score&lt;/li&gt;
&lt;li&gt;Sounds effects&lt;/li&gt;
&lt;li&gt;Limited mobile support&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Phase 2 - Firebase high score, service worker, more sounds effect, more animation
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;TBD&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Fully mobile support&lt;/li&gt;
&lt;li&gt;Offline mode (play without internet connection)&lt;/li&gt;
&lt;li&gt;Firebase high score&lt;/li&gt;
&lt;li&gt;More sound effects&lt;/li&gt;
&lt;li&gt;More animations&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Time spending
&lt;/h2&gt;

&lt;p&gt;I was still working with &lt;a href="https://github.com/nartc"&gt;Chau Tran&lt;/a&gt; on phase two of &lt;a href="https://github.com/trungk18/jira-clone-angular"&gt;Angular Jira clone&lt;/a&gt; when I saw that Tetris game built with Vue. My wife wanted to have a version that I built so that I decided to finish the Angular Tetris first before completing Jira clone phase two.&lt;/p&gt;

&lt;p&gt;According to waka time report, I have spent about 30 hours working on this project. Which is equal to &lt;a href="https://www.strava.com/activities/2902245728"&gt;run a marathon five times&lt;/a&gt; at my current speed 😩&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FwWGUq31--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://github.com/trungk18/angular-tetris/raw/master/src/assets/readme/time-spending.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FwWGUq31--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://github.com/trungk18/angular-tetris/raw/master/src/assets/readme/time-spending.png" alt="Angular Tetris" width="792" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The flow was easy. I designed a simple &lt;a href="https://www.notion.so/trungk18/Phase-1-be1ae0fbbf2c4c2fb92887e2218413db"&gt;to do list&lt;/a&gt;, then start reading the code in Vue. And start working on the Angular at the same time. Halfway, I start to read &lt;a href="https://github.com/chrum/ngx-tetris"&gt;@chrum/ngx-tetris&lt;/a&gt; instead of the Vue source. And keep building until I have the final result. 30 hours was a good number. It would take me longer, or lesser. But I enjoyed the experience working on the first-ever game I have built.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up development environment 🛠
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;git clone https://github.com/trungk18/angular-tetris.git&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cd angular-tetris&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;npm start&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The app should run on &lt;code&gt;http://localhost:4200/&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Author: Trung Vo ✍️
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A young and passionate front-end engineer. Working with Angular and TypeScript. Like photography, running, cooking, and reading books.&lt;/li&gt;
&lt;li&gt;Author of Angular Jira clone -&amp;gt; &lt;a href="https://github.com/trungk18/jira-clone-angular"&gt;jira.trungk18.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Personal blog: &lt;a href="https://trungk18.com/"&gt;https://trungk18.com/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Say hello: trungk18 [et] gmail [dot] com&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Credits and references
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Resource&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/Binaryify/vue-tetris"&gt;@Binaryify/vue-tetris&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Vue Tetris, I reused part of HTML, CSS and static assets from that project&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/chrum/ngx-tetris"&gt;@chrum/ngx-tetris&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;A comprehensive core Tetris written with Angular, I reused part of that for the brain of the game.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://medium.com/angular-in-depth/game-development-tetris-in-angular-64ef96ce56f7"&gt;Game Development: Tetris in Angular&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;A detailed excellent article about how to build a complete Tetris game. I didn't check the code but I learned much more from that&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://tetris.fandom.com/wiki/SRS"&gt;Super Rotation System&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;A standard for how the piece behaves. I didn't follow everything but it is good to know as wells&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Contributing
&lt;/h2&gt;

&lt;p&gt;If you have any ideas, just &lt;a href="https://github.com/trungk18/angular-tetris/issues/new/choose"&gt;open an issue&lt;/a&gt; and tell me what you think.&lt;/p&gt;

&lt;p&gt;If you'd like to contribute, please fork the repository and make changes as you'd like. &lt;a href="https://github.com/trungk18/angular-tetris/pulls"&gt;Pull requests&lt;/a&gt; are warmly welcome.&lt;/p&gt;

&lt;h2&gt;
  
  
  License
&lt;/h2&gt;

&lt;p&gt;Feel free to use my code on your project. It would be great if you put a reference to this repository.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://opensource.org/licenses/MIT"&gt;MIT&lt;/a&gt;&lt;/p&gt;

</description>
      <category>angular</category>
      <category>typescript</category>
      <category>akita</category>
      <category>games</category>
    </item>
    <item>
      <title>A simplified Jira clone built with Angular 9, ng-zorro and Akita</title>
      <dc:creator>Trung Vo</dc:creator>
      <pubDate>Fri, 25 Sep 2020 01:34:39 +0000</pubDate>
      <link>https://dev.to/trungk18/a-simplified-jira-clone-built-with-angular-9-ng-zorro-and-akita-4ofp</link>
      <guid>https://dev.to/trungk18/a-simplified-jira-clone-built-with-angular-9-ng-zorro-and-akita-4ofp</guid>
      <description>&lt;p&gt;There have been a handful of cool Jira-cloned apps written in &lt;code&gt;React&lt;/code&gt;/&lt;code&gt;VueJS&lt;/code&gt;, which makes me wonder &lt;strong&gt;Why not Angular&lt;/strong&gt;? And here you go.&lt;/p&gt;

&lt;p&gt;This is not only a simplified Jira clone built with Angular 9, but also an example of a &lt;strong&gt;modern&lt;/strong&gt;, &lt;strong&gt;real-world&lt;/strong&gt; Angular codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  Working application
&lt;/h2&gt;

&lt;p&gt;Checkout the &lt;strong&gt;live demo&lt;/strong&gt; -&amp;gt; &lt;a href="https://jira.trungk18.com" rel="noopener noreferrer"&gt;https://jira.trungk18.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fbhe54vqe2pyzsgtgwspg.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fbhe54vqe2pyzsgtgwspg.gif" alt="Jira clone built with Angular 9 and Akita"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Source code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/trungvose" rel="noopener noreferrer"&gt;
        trungvose
      &lt;/a&gt; / &lt;a href="https://github.com/trungvose/jira-clone-angular" rel="noopener noreferrer"&gt;
        jira-clone-angular
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A simplified Jira clone built with Angular, ng-zorro and Akita
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;If you like my work, feel free to⭐ this repository. And we will be happy together :)&lt;br&gt;
Thanks a bunch for stopping by and supporting me!&lt;/p&gt;

&lt;h2&gt;
  
  
  Who is it for 🤷‍♀️
&lt;/h2&gt;

&lt;p&gt;I have been working with Angular for about four years. I built cool stuff at &lt;a href="https://www.zyllem.com/" rel="noopener noreferrer"&gt;Zyllem&lt;/a&gt; but almost all of them are internal apps which is difficult to show.&lt;/p&gt;

&lt;p&gt;This is a showcase application I've built in my spare time to experiment the new library that I wanted to try before: &lt;code&gt;Akita&lt;/code&gt;, &lt;code&gt;TailwindCSS&lt;/code&gt;, &lt;code&gt;ng-zorro&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There are many Angular examples on the web but most of them are way too simple. I like to think that this codebase contains enough complexity to offer valuable insights to &lt;strong&gt;Angular developers of all skill levels&lt;/strong&gt; while still being &lt;em&gt;relatively&lt;/em&gt; easy to understand.&lt;/p&gt;




&lt;p&gt;This piece of work is also part of our technical series &lt;a href="https://github.com/angular-vietnam/100-days-of-angular" rel="noopener noreferrer"&gt;angular-vietnam/100-days-of-angular&lt;/a&gt; which aims at enabling everyone, after 100 days of learning Angular with us, to &lt;strong&gt;self-build their application with the similar scale&lt;/strong&gt;. Our desire is to advocate and grow the Angular developer community in Vietnam.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech stack
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fjira.trungk18.com%2Fassets%2Fimg%2Fjira-clone-tech-stack.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fjira.trungk18.com%2Fassets%2Fimg%2Fjira-clone-tech-stack.png" alt="Tech logos"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cli.angular.io/" rel="noopener noreferrer"&gt;Angular CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://datorama.github.io/akita/" rel="noopener noreferrer"&gt;Akita&lt;/a&gt; state management&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nestjs.com/" rel="noopener noreferrer"&gt;NestJS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;UI modules:

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;TailwindCSS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Angular CDK &lt;a href="https://material.angular.io/cdk/drag-drop/overview" rel="noopener noreferrer"&gt;drag and drop&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ng.ant.design/docs/introduce/en" rel="noopener noreferrer"&gt;ng-zorro&lt;/a&gt; UI components&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/KillerCodeMonkey/ngx-quill" rel="noopener noreferrer"&gt;ngx-quill&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;a href="https://www.netlify.com/" rel="noopener noreferrer"&gt;Netlify&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href="https://www.heroku.com/" rel="noopener noreferrer"&gt;Heroku&lt;/a&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  High level design
&lt;/h2&gt;

&lt;p&gt;As requested by &lt;a href="https://www.reddit.com/r/Angular2/comments/hj4kxd/angular_jira_clone_application_built_akita_and/fwu1tbm/" rel="noopener noreferrer"&gt;@eric_cart&lt;/a&gt;, I draw a simple high-level design for the application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Application architecture
&lt;/h3&gt;

&lt;p&gt;I have an AppModule that will import:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fjira.trungk18.com%2Fassets%2Fimg%2Fdiagram%2Fapplication-architecture.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fjira.trungk18.com%2Fassets%2Fimg%2Fdiagram%2Fapplication-architecture.png" alt="Jira clone built with Angular 9 and Akita - Application architecture"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Angular needed modules such as &lt;code&gt;BrowserModule&lt;/code&gt; and any module that need to run &lt;code&gt;forRoot&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The application core modules such as &lt;code&gt;AuthModule&lt;/code&gt; that need to available on the whole platform.&lt;/li&gt;
&lt;li&gt;And I also configured the router to &lt;a href="https://angular.io/guide/lazy-loading-ngmodules" rel="noopener noreferrer"&gt;lazy load any modules&lt;/a&gt; only when I needed. Otherwise, everything will be loaded when I start the application.
For instance, &lt;code&gt;LoginModule&lt;/code&gt; when I open the URL at &lt;code&gt;/login&lt;/code&gt; and &lt;code&gt;ProjectModule&lt;/code&gt; when the URL is &lt;code&gt;/project&lt;/code&gt;. Inside each modules, I could import whatever modules that are required. Such as I need the &lt;code&gt;JiraControlModule&lt;/code&gt; for some custom UI components for the &lt;code&gt;ProjectModule&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Simple data interaction flow
&lt;/h3&gt;

&lt;p&gt;As I am using &lt;a href="https://datorama.github.io/akita/" rel="noopener noreferrer"&gt;Akita&lt;/a&gt; state management, I follow the Akita documentation for the data flow. I found it is simple to understand comparing with ngrx terms (&lt;code&gt;reducer&lt;/code&gt;, &lt;code&gt;selector&lt;/code&gt;, &lt;code&gt;effect&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fjira.trungk18.com%2Fassets%2Fimg%2Fdiagram%2Finteraction-data-flow.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fjira.trungk18.com%2Fassets%2Fimg%2Fdiagram%2Finteraction-data-flow.png" alt="Jira clone built with Angular 9 and Akita - Simple data interaction flow"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I set up a &lt;a href="https://github.com/trungk18/jira-clone-angular/blob/master/frontend/src/app/project/state/project/project.store.ts" rel="noopener noreferrer"&gt;project state with initial data&lt;/a&gt;. The main heavy lifting part I think is the &lt;a href="https://github.com/trungk18/jira-clone-angular/blob/master/frontend/src/app/project/state/project/project.service.ts" rel="noopener noreferrer"&gt;project service&lt;/a&gt;, it contains all the interacting with &lt;a href="https://github.com/trungk18/jira-clone-angular/blob/master/frontend/src/app/project/state/project/project.store.ts" rel="noopener noreferrer"&gt;project store&lt;/a&gt;. Such as after fetching the project successfully, I update the store immediately inside the service itself. The last lego block was to expose the data through &lt;a href="https://github.com/trungk18/jira-clone-angular/blob/master/frontend/src/app/project/state/project/project.query.ts" rel="noopener noreferrer"&gt;project query&lt;/a&gt;. Any components can start to inject &lt;a href="https://github.com/trungk18/jira-clone-angular/blob/master/frontend/src/app/project/state/project/project.query.ts" rel="noopener noreferrer"&gt;project query&lt;/a&gt; and consume data from there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Features and Roadmap
&lt;/h2&gt;

&lt;p&gt;I set the tentative deadline for motivating myself to finish the work on time. Otherwise, It will take forever to complete :)&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 1 - Angular application and simple Nest API
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;June 13 - 27, 2020&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Proven, scalable, and easy to understand project structure&lt;/li&gt;
&lt;li&gt;Simple drag and drop kanban board&lt;/li&gt;
&lt;li&gt;Add/update issue&lt;/li&gt;
&lt;li&gt;Search/filtering issues&lt;/li&gt;
&lt;li&gt;Add comments&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Noted: All of your interactions with data such as leave a comment or change the issue detail will not be saved to the persistent database. Currently, the application will serve a fixed structure of data every time you open the app. It means if you reload the browser, all of your changes will be gone.&lt;/p&gt;

&lt;p&gt;Phase 2 will bring you a proper API where you can log in and save your work.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;While working with this application, I have the opportunity to revisit some of the interesting topics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TailwindCSS configuration - that's awesome&lt;/li&gt;
&lt;li&gt;Scrollable layout with Flexbox&lt;/li&gt;
&lt;li&gt;Deploy Angular application to Netlify&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Phase 2
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;TBD&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Refactor the mono repo to use Nx Workspace&lt;/li&gt;
&lt;li&gt;GraphQL API and store data on the actual database&lt;/li&gt;
&lt;li&gt;Authentication&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;View the current &lt;a href="https://github.com/trungk18/jira-clone-angular/tree/feature/gql" rel="noopener noreferrer"&gt;work in progress branch&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Tutorial
&lt;/h2&gt;

&lt;p&gt;When I look at the application, it is huge. When the task is huge, you usually don't know where and how to start working with them. I started to break the big task into a &lt;a href="https://www.notion.so/trungk18/Tasks-636be5c5c0dd4d8cab30808e4e41facc" rel="noopener noreferrer"&gt;simple to-do list on notion&lt;/a&gt;. Once I know what needs to be done, what I need is to follow the plan. That's my approach.&lt;/p&gt;

&lt;p&gt;I learned a lot of stuff. I know you might also have a curiosity about the process of building the same scale app as well. That's why I am writing a tutorial series on how I built Angular Jira clone from scratch. I hope you guys will learn something from that too :)&lt;/p&gt;

&lt;p&gt;I will try to be as detailed as possible. Hopefully through the tutorial, you will get the idea and will start working on your own application soon. Please bear with me.&lt;/p&gt;

&lt;p&gt;Its series will also be published in Vietnamese as part of our &lt;a href="https://github.com/angular-vietnam/100-days-of-angular" rel="noopener noreferrer"&gt;angular-vietnam/100-days-of-angular&lt;/a&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;00&lt;/td&gt;
&lt;td&gt;&lt;a href="https://slides.com/tuantrungvo/behind-the-900-star-repository-jira-clone-angular" rel="noopener noreferrer"&gt;Behind the 900 stars repository - Slide&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Done&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;00&lt;/td&gt;
&lt;td&gt;&lt;a href="https://youtu.be/3dukbsRX0tc" rel="noopener noreferrer"&gt;Behind a thousand stars repository - Angular Air&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Done&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;00&lt;/td&gt;
&lt;td&gt;&lt;a href="https://trungk18.com/experience/angular-jira-clone-tutorial-00-prerequisites/" rel="noopener noreferrer"&gt;Prerequisites&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Done&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;01&lt;/td&gt;
&lt;td&gt;&lt;a href="https://trungk18.com/experience/angular-jira-clone-tutorial-01-planning-and-set-up/" rel="noopener noreferrer"&gt;Create a new repository and set up a new Angular application with CLI&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Done&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;02&lt;/td&gt;
&lt;td&gt;&lt;a href="https://trungk18.com/experience/angular-jira-clone-tutorial-02-application-layout-tailwindcss-flex/" rel="noopener noreferrer"&gt;Build the application layout with flex and TailwindCSS&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Done&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;03&lt;/td&gt;
&lt;td&gt;&lt;a href="https://trungk18.com/experience/angular-jira-clone-tutorial-03-akita-state-management/" rel="noopener noreferrer"&gt;Setup Akita state management&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Done&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;04&lt;/td&gt;
&lt;td&gt;&lt;a href="https://trungk18.com/experience/angular-jira-clone-tutorial-04-editable-textbox/" rel="noopener noreferrer"&gt;Build an editable textbox&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Done&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;05&lt;/td&gt;
&lt;td&gt;&lt;a href="https://trungk18.com/experience/angular-jira-clone-tutorial-05-interactive-drag-and-drop-board/" rel="noopener noreferrer"&gt;Build an interactive drag and drop board&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Done&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;06&lt;/td&gt;
&lt;td&gt;&lt;a href="https://trungk18.com/experience/angular-jira-clone-tutorial-06-angular-markdown-text-editor/" rel="noopener noreferrer"&gt;Build a markdown text editor&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Done&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;07&lt;/td&gt;
&lt;td&gt;&lt;a href="https://trungk18.com/experience/angular-jira-clone-tutorial-07-rich-text-editor/" rel="noopener noreferrer"&gt;Build a rich text HTML editor&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Done&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;08&lt;/td&gt;
&lt;td&gt;&lt;a href="https://trungk18.com/experience/angular-jira-clone-tutorial-08-angular-placeholder-loading/" rel="noopener noreferrer"&gt;Create placeholder loading (like Facebook's cards loading)&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Done&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Time spending
&lt;/h2&gt;

&lt;p&gt;It is a side project that I only spent time outside of the office hours to work on. One day, my team and I were fire fighting on PROD until 11 PM. After taking a shower, I continue with Angular Jira clone for another two hours...&lt;/p&gt;

&lt;p&gt;According to waka time report, I have spent about 45 hours working on this project. Which is equivalent to watch the &lt;a href="https://www.bingeclock.com/s/stranger-things/" rel="noopener noreferrer"&gt;whole Stranger Things series twice&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I really enjoyed working on this project. The interactive kanban board took me sometimes, it is challenging but exciting at the same time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fjira.trungk18.com%2Fassets%2Fimg%2Ftime-spending.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fjira.trungk18.com%2Fassets%2Fimg%2Ftime-spending.png" alt="Jira clone built with Angular 9 and Akita - Time spending"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What's currently missing?
&lt;/h2&gt;

&lt;p&gt;There are missing features from the live demo which should exist in a real product. All of them will be finished on Phase 2:&lt;/p&gt;

&lt;h3&gt;
  
  
  Proper backend API
&lt;/h3&gt;

&lt;p&gt;I built a very simple NestJS API to send a fixed data structure to the client. All of your interactivity with data will only be saved on the memory. If you refresh the page, it will be gone. Phase 2 will bring the application to live by saving the data into a database.&lt;/p&gt;

&lt;h3&gt;
  
  
  Proper authentication system 🔐
&lt;/h3&gt;

&lt;p&gt;I am currently sending the same email and a random password to the server without any check to get the current user back. Phase 2 will also bring a proper authentication system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accessibility ♿
&lt;/h3&gt;

&lt;p&gt;Not all components have properly defined &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA" rel="noopener noreferrer"&gt;aria attributes&lt;/a&gt;, visual focus indicators, etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up development environment 🛠
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;git clone https://github.com/trungk18/jira-clone-angular.git&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cd jira-clone-angular&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;npm run start:front&lt;/code&gt; for angular web application&lt;/li&gt;
&lt;li&gt;The app should run on &lt;code&gt;http://localhost:4200/&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Unit/Integration tests 🧪
&lt;/h3&gt;

&lt;p&gt;I skipped writing test for this project. I might do it for the proper backend GraphQL API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compatibility
&lt;/h2&gt;

&lt;p&gt;It was being tested on IE 11, Chrome and Firefox. For Safari, there are some minor alignment issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  Author: Trung Vo ✍️
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A young and passionate front-end engineer. Working with Angular and TypeScript. Like photography, running, cooking, and reading books.&lt;/li&gt;
&lt;li&gt;Personal blog: &lt;a href="https://trungk18.com/" rel="noopener noreferrer"&gt;https://trungk18.com/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Say hello: trungk18 [et] gmail [dot] com&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Contributing
&lt;/h2&gt;

&lt;p&gt;If you have any ideas, just &lt;a href="https://github.com/trungk18/jira-clone-angular/issues/new" rel="noopener noreferrer"&gt;open an issue&lt;/a&gt; and tell me what you think.&lt;/p&gt;

&lt;p&gt;If you'd like to contribute, please fork the repository and make changes as you'd like. &lt;a href="https://github.com/trungk18/jira-clone-angular/compare" rel="noopener noreferrer"&gt;Pull requests&lt;/a&gt; are warmly welcome.&lt;/p&gt;

&lt;h2&gt;
  
  
  Credits
&lt;/h2&gt;

&lt;p&gt;Inspired by &lt;a href="https://github.com/oldboyxx/jira_clone" rel="noopener noreferrer"&gt;oldboyxx/jira_clone&lt;/a&gt; and &lt;a href="https://github.com/Datlyfe/jira_clone" rel="noopener noreferrer"&gt;Datlyfe/jira_clone&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I reused part of the HTML and CSS code from these projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  License
&lt;/h2&gt;

&lt;p&gt;Feel free to use my code on your project. It would be great if you put a reference to this repository.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://opensource.org/licenses/MIT" rel="noopener noreferrer"&gt;MIT&lt;/a&gt;&lt;/p&gt;

</description>
      <category>angular</category>
      <category>typescript</category>
      <category>akita</category>
      <category>jiraclone</category>
    </item>
    <item>
      <title>Build a Markdown editor with Angular</title>
      <dc:creator>Trung Vo</dc:creator>
      <pubDate>Tue, 22 Sep 2020 03:42:19 +0000</pubDate>
      <link>https://dev.to/trungk18/build-a-markdown-editor-with-angular-14m0</link>
      <guid>https://dev.to/trungk18/build-a-markdown-editor-with-angular-14m0</guid>
      <description>&lt;p&gt;If you notice, the current &lt;a href="https://jira.trungk18.com/" rel="noopener noreferrer"&gt;jira.trungk18.com&lt;/a&gt; is using an HTML text editor. I am replacing it with a Markdown text editor for the upcoming features of #jiraclone. &lt;/p&gt;

&lt;p&gt;In this post, I will guide you through the process of building a Markdown editor with Angular.&lt;/p&gt;

&lt;p&gt;That's how a Markdown text editor looks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F3xjv7u0dr0wbfn8ow6wx.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F3xjv7u0dr0wbfn8ow6wx.gif" alt="Angular Jira Clone Part 06 - Build a markdown text editor"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;See &lt;a href="https://trungk18.com/tags/jira-clone/" rel="noopener noreferrer"&gt;all tutorials for Jira clone&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Source code and demo
&lt;/h2&gt;

&lt;p&gt;&lt;iframe src="https://stackblitz.com/edit/angular-markdown-editor-jira?view=preview" width="100%" height="500"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Markdown Editor Module
&lt;/h2&gt;

&lt;p&gt;A markdown text editor might be reused in many places on a web application. So that I will create a brand new module &lt;code&gt;MarkdownEditorModule&lt;/code&gt; for that purpose. At the moment, it will have only one component &lt;code&gt;MarkdownEditorComponent&lt;/code&gt; and it will be exported as well.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F4ws2ppe2uugzrjvnhw4h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F4ws2ppe2uugzrjvnhw4h.png" alt="Angular Jira Clone Part 06 - Build a markdown text editor"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is not much code inside its module and component.&lt;/p&gt;

&lt;p&gt;markdown-editor.component.ts&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;markdown-editor&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;templateUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./markdown-editor.component.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;styleUrls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./markdown-editor.component.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MarkdownEditorComponent&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;OnInit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;ngOnInit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;markdown-editor.module.ts&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;NgModule&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;CommonModule&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;MarkdownEditorComponent&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;declarations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;MarkdownEditorComponent&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MarkdownEditorModule&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No worry, we will add more code below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Github Markdown Toolbar
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Install @github/markdown-toolbar-element and use it inside our Angular component
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/nartc" rel="noopener noreferrer"&gt;@nartc&lt;/a&gt; suggested me to use that package to enable a markdown toolbar. I had a look and really like that tiny package, plus It came from Github itself 😊&lt;/p&gt;

&lt;p&gt;To add that to an Angular application, simply run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save&lt;/span&gt; @github/markdown-toolbar-element
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Second, you need to import &lt;code&gt;@github/markdown-toolbar-element&lt;/code&gt; into &lt;code&gt;MarkdownEditorComponent&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@github/markdown-toolbar-element&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you can paste the below code into &lt;code&gt;MarkdownEditorComponent&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;markdown-editor.component.html&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;markdown-toolbar&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"textarea_id"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;md-bold&amp;gt;&lt;/span&gt;bold&lt;span class="nt"&gt;&amp;lt;/md-bold&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;md-header&amp;gt;&lt;/span&gt;header&lt;span class="nt"&gt;&amp;lt;/md-header&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;md-italic&amp;gt;&lt;/span&gt;italic&lt;span class="nt"&gt;&amp;lt;/md-italic&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;md-quote&amp;gt;&lt;/span&gt;quote&lt;span class="nt"&gt;&amp;lt;/md-quote&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;md-code&amp;gt;&lt;/span&gt;code&lt;span class="nt"&gt;&amp;lt;/md-code&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;md-link&amp;gt;&lt;/span&gt;link&lt;span class="nt"&gt;&amp;lt;/md-link&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;md-image&amp;gt;&lt;/span&gt;image&lt;span class="nt"&gt;&amp;lt;/md-image&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;md-unordered-list&amp;gt;&lt;/span&gt;unordered-list&lt;span class="nt"&gt;&amp;lt;/md-unordered-list&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;md-ordered-list&amp;gt;&lt;/span&gt;ordered-list&lt;span class="nt"&gt;&amp;lt;/md-ordered-list&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;md-task-list&amp;gt;&lt;/span&gt;task-list&lt;span class="nt"&gt;&amp;lt;/md-task-list&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;md-mention&amp;gt;&lt;/span&gt;mention&lt;span class="nt"&gt;&amp;lt;/md-mention&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;md-ref&amp;gt;&lt;/span&gt;ref&lt;span class="nt"&gt;&amp;lt;/md-ref&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/markdown-toolbar&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;textarea&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"textarea_id"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/textarea&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because &lt;code&gt;markdown-toolbar&lt;/code&gt; is a custom web element tag and it looks like an Angular component selector. Angular couldn't find the declaration elsewhere, that's why you are seeing that error.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fwia287og0rt7t4bxb7hc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fwia287og0rt7t4bxb7hc.png" alt="Angular Jira Clone Part 06 - Build a markdown text editor"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To fix it, follow the error on the screen to add &lt;code&gt;CUSTOM_ELEMENTS_SCHEMA&lt;/code&gt; into the &lt;code&gt;MarkdownEditorModule&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;NgModule&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="c1"&gt;//code removed for brevity&lt;/span&gt;
  &lt;span class="na"&gt;schemas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;CUSTOM_ELEMENTS_SCHEMA&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now something is rendering on the UI and the textarea gets updated upon selection on the toolbar, but it hasn't looked good just yet.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F4sv4l675h8hlgoy345bu.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F4sv4l675h8hlgoy345bu.gif" alt="Angular Jira Clone Part 06 - Build a markdown text editor"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Styling the markdown toolbar
&lt;/h3&gt;

&lt;p&gt;To make styling easier, I set a button with class &lt;code&gt;.btn&lt;/code&gt; and wrap the text into a &lt;code&gt;button&lt;/code&gt;. I also use &lt;a href="https://icons.getbootstrap.com/" rel="noopener noreferrer"&gt;Boostrap Icon&lt;/a&gt; to make it look like a real toolbar. &lt;code&gt;markdown-editor.component.html&lt;/code&gt; is getting pretty long because all of the icons are SVG, I won't paste all of them here. Take a look at one bold icon and you will understand.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;markdown-toolbar&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"textarea_id"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;md-bold&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"1em"&lt;/span&gt;
          &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"1em"&lt;/span&gt;
          &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 16 16"&lt;/span&gt;
          &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"bi bi-type-bold"&lt;/span&gt;
          &lt;span class="na"&gt;fill=&lt;/span&gt;&lt;span class="s"&gt;"currentColor"&lt;/span&gt;
          &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2000/svg"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;path&lt;/span&gt; &lt;span class="na"&gt;d=&lt;/span&gt;&lt;span class="s"&gt;"M8.21 13c2.106 0 3.412-1.087 3.412-2.823 0-1.306-.984-2.283-2.324-2.386v-.055a2.176 2.176 0 0 0 1.852-2.14c0-1.51-1.162-2.46-3.014-2.46H3.843V13H8.21zM5.908 4.674h1.696c.963 0 1.517.451 1.517 1.244 0 .834-.629 1.32-1.73 1.32H5.908V4.673zm0 6.788V8.598h1.73c1.217 0 1.88.492 1.88 1.415 0 .943-.643 1.449-1.832 1.449H5.907z"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/md-bold&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- code removed for brevity --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/markdown-toolbar&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nt"&gt;hover-color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;#06&lt;/span&gt;&lt;span class="nt"&gt;c&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nt"&gt;markdown-toolbar&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="err"&gt;.btn&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inline-block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;24px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3px&lt;/span&gt; &lt;span class="m"&gt;5px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;28px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#222&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="err"&gt;i&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;hover-color&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="err"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After styling the textarea as below, you will see a quite satisfying result 😊&lt;/p&gt;

&lt;h3&gt;
  
  
  Styling the textarea
&lt;/h3&gt;

&lt;p&gt;I will do the styling for the textarea as well.&lt;/p&gt;

&lt;p&gt;First, I assign a class &lt;code&gt;text-editor&lt;/code&gt; to that textarea.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;textarea&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"textarea_id"&lt;/span&gt;
          &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text-editor"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/textarea&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the CSS, I wanted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No border for textarea&lt;/li&gt;
&lt;li&gt;Have border for the container around the markdown toolbar and textarea&lt;/li&gt;
&lt;li&gt;On hovering the textarea, set a different border color for the container&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I hope my CSS will express itself :) But if you have any questions about CSS, let me know in the comment box below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nt"&gt;border-color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;#d9d9d9&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;:host&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;border-color&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;border-color&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;outline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#fff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;flex-direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  

  &lt;span class="err"&gt;.text-editor&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;padding-left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;padding-right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;resize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;transparent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;overflow-y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="err"&gt;&amp;amp;:focus&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;outline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;transparent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="err"&gt;}&lt;/span&gt;

  &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nc"&gt;.focus&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;hover-color&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;hover-color&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I have a result now, looks pretty good. But the border color didn't change when I select the textarea.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fz4iiqnw70lpwdlj4ylc6.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fz4iiqnw70lpwdlj4ylc6.gif" alt="Angular Jira Clone Part 06 - Build a markdown text editor"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Why? Because we need to set an extra class to the &lt;strong&gt;parent of the textarea&lt;/strong&gt;. We need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Handle &lt;code&gt;focus&lt;/code&gt; event of the textarea to add a class named &lt;code&gt;.focus&lt;/code&gt; to the parent container.&lt;/li&gt;
&lt;li&gt;Also handle &lt;code&gt;blur&lt;/code&gt; event to remove this class from the parent container.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also added &lt;code&gt;cdkTextareaAutosize&lt;/code&gt; from &lt;a href="https://material.angular.io/cdk/text-field/api" rel="noopener noreferrer"&gt;@angular/cdk/text-field&lt;/a&gt; package to make the textarea auto-expand its height when the content is too long. By default, &lt;code&gt;textarea&lt;/code&gt; will have a scrollbar visible and won't auto expand. See more on my previous tutorial - &lt;a href="https://trungk18.com/experience/angular-jira-clone-tutorial-04-editable-textbox/" rel="noopener noreferrer"&gt;Build an editable textbox&lt;/a&gt;. I also set the &lt;code&gt;cdkAutosizeMinRows&lt;/code&gt; to 6 so that it will also have a certain minimum height.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;textarea&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text-editor"&lt;/span&gt;
          &lt;span class="na"&gt;(focus)=&lt;/span&gt;&lt;span class="s"&gt;"focus()"&lt;/span&gt;
          &lt;span class="na"&gt;(blur)=&lt;/span&gt;&lt;span class="s"&gt;"blur()"&lt;/span&gt;
          &lt;span class="na"&gt;[formControl]=&lt;/span&gt;&lt;span class="s"&gt;"control"&lt;/span&gt;
          &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"MarkdownInput"&lt;/span&gt;
          &lt;span class="na"&gt;cdkTextareaAutosize&lt;/span&gt;
          &lt;span class="na"&gt;[cdkAutosizeMinRows]=&lt;/span&gt;&lt;span class="s"&gt;"6"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/textarea&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MarkdownEditorComponent&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;OnInit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;HostBinding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;class.focus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;isFocus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isFocus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nf"&gt;blur&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isFocus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What the &lt;code&gt;HostBinding&lt;/code&gt; does is check if &lt;code&gt;isFocus&lt;/code&gt; is true, then Angular will add a class name &lt;code&gt;focus&lt;/code&gt; to the component selector. It look like &lt;code&gt;&amp;lt;markdown-editor class="focus&lt;/code&gt;. If the value is false, then remove this class then.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F6e66ktfhsedoed5hupdu.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F6e66ktfhsedoed5hupdu.gif" alt="Angular Jira Clone Part 06 - Build a markdown text editor"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I think we are almost there, looks excellent now. The last thing is to connect this component with a form.&lt;/p&gt;

&lt;h3&gt;
  
  
  Link the markdown editor component to a form
&lt;/h3&gt;

&lt;p&gt;Usually, the Markdown editor will be used into a form with some additional form input and you wanted to see its value in the form instance.&lt;/p&gt;

&lt;p&gt;To do it, simply set the &lt;code&gt;MarkdownEditorComponent&lt;/code&gt; to accept an input which is a &lt;code&gt;FormControl&lt;/code&gt;. So that the control can be passed into the component from the parent component form instance.&lt;/p&gt;

&lt;p&gt;The component will initial a default &lt;code&gt;FormControl&lt;/code&gt; if there is no input passed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MarkdownEditorComponent&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;OnInit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;control&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FormControl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;ngOnInit&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;control&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;control&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormControl&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And bind the control to the component HTML&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;textarea&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"textarea_id"&lt;/span&gt;
          &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text-editor"&lt;/span&gt;
          &lt;span class="na"&gt;[formControl]=&lt;/span&gt;&lt;span class="s"&gt;"control"&lt;/span&gt;
          &lt;span class="na"&gt;(focus)=&lt;/span&gt;&lt;span class="s"&gt;"focus()"&lt;/span&gt;
          &lt;span class="na"&gt;(blur)=&lt;/span&gt;&lt;span class="s"&gt;"blur()"&lt;/span&gt;
          &lt;span class="na"&gt;cdkTextareaAutosize&lt;/span&gt;
          &lt;span class="na"&gt;[cdkAutosizeMinRows]=&lt;/span&gt;&lt;span class="s"&gt;"6"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/textarea&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To be able to do that, you have to import &lt;code&gt;ReactiveFormsModule&lt;/code&gt; into &lt;code&gt;MarkdownEditorModule&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;NgModule&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;CommonModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;ReactiveFormsModule&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="c1"&gt;//code removed for brevity&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MarkdownEditorModule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To test it with a form, I will create a simple form with two inputs by &lt;code&gt;FormBuilder&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Title as the normal textbox&lt;/li&gt;
&lt;li&gt;Description as the markdown editor
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppComponent&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;OnInit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;form&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FormGroup&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;_fb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FormBuilder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="nf"&gt;ngOnInit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_fb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello, I am Trung&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Validators&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;This is a markdown text editor for - http://jira.trungk18.com/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;descriptionControl&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;controls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;FormControl&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also get the description control from my form and then send it to the &lt;code&gt;MarkdownEditorComponent&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;[formGroup]=&lt;/span&gt;&lt;span class="s"&gt;"form"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form-group"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"Title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Title&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;formControlName=&lt;/span&gt;&lt;span class="s"&gt;"title"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form-control"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"Title"&lt;/span&gt; &lt;span class="na"&gt;aria-describedby=&lt;/span&gt;&lt;span class="s"&gt;"Title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form-group"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;Description&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;markdown-editor&lt;/span&gt; &lt;span class="na"&gt;[control]=&lt;/span&gt;&lt;span class="s"&gt;"descriptionControl"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/markdown-editor&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"alert alert-info"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  {{ form.value | json }}
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sweet, everything seems working as expected.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fz2rnqq021ir0nqcf0zce.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fz2rnqq021ir0nqcf0zce.gif" alt="Angular Jira Clone Part 06 - Build a markdown text editor"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Accessibility
&lt;/h3&gt;

&lt;p&gt;Last but not least, remember to add the &lt;code&gt;aria-label&lt;/code&gt; and &lt;code&gt;title&lt;/code&gt; for all of the icons. Otherwise, If users are not familiar with the text edit icon, they might find it difficult to understand the meaning. The &lt;code&gt;aria-label&lt;/code&gt; is for people with disability can have an easy navigation through your website :)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;markdown-toolbar&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"textarea_id"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;md-bold&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn"&lt;/span&gt; &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"Bold"&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Bold"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"1em"&lt;/span&gt;
          &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"1em"&lt;/span&gt;
          &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 16 16"&lt;/span&gt;
          &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"bi bi-type-bold"&lt;/span&gt;
          &lt;span class="na"&gt;fill=&lt;/span&gt;&lt;span class="s"&gt;"currentColor"&lt;/span&gt;
          &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2000/svg"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;path&lt;/span&gt; &lt;span class="na"&gt;d=&lt;/span&gt;&lt;span class="s"&gt;"M8.21 13c2.106 0 3.412-1.087 3.412-2.823 0-1.306-.984-2.283-2.324-2.386v-.055a2.176 2.176 0 0 0 1.852-2.14c0-1.51-1.162-2.46-3.014-2.46H3.843V13H8.21zM5.908 4.674h1.696c.963 0 1.517.451 1.517 1.244 0 .834-.629 1.32-1.73 1.32H5.908V4.673zm0 6.788V8.598h1.73c1.217 0 1.88.492 1.88 1.415 0 .943-.643 1.449-1.832 1.449H5.907z"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/md-bold&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- code removed for brevity --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/markdown-toolbar&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when you hover over the icon for sometimes, the browser will display the title.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fddba7y8plkgavpuh2snr.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fddba7y8plkgavpuh2snr.gif" alt="Angular Jira Clone Part 06 - Build a markdown text editor"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's all for building a Markdown Editor with Angular. Any questions, you can leave it on the comment box below or reach me on Twitter. Thanks for stopping by!&lt;/p&gt;

</description>
      <category>angular</category>
      <category>typescript</category>
      <category>css</category>
      <category>jiraclone</category>
    </item>
  </channel>
</rss>
