<?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: Elanna Grossman</title>
    <description>The latest articles on DEV Community by Elanna Grossman (@karvel).</description>
    <link>https://dev.to/karvel</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%2F605617%2F92cdc551-9932-44df-a811-f167168fc236.jpeg</url>
      <title>DEV Community: Elanna Grossman</title>
      <link>https://dev.to/karvel</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/karvel"/>
    <language>en</language>
    <item>
      <title>How I Switched My Website to Analog</title>
      <dc:creator>Elanna Grossman</dc:creator>
      <pubDate>Sat, 23 Dec 2023 02:08:59 +0000</pubDate>
      <link>https://dev.to/karvel/how-i-switched-my-website-to-analog-468k</link>
      <guid>https://dev.to/karvel/how-i-switched-my-website-to-analog-468k</guid>
      <description>&lt;p&gt;&lt;em&gt;Updated Sep 09, 2024&lt;/em&gt; - added information about more recently implemented features, like search, skeleton loader, and tag cloud.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;As I switched my website to Analog, I had to make some decisions. I wanted to migrate every post, and most of the other text content. Analog adds some very nice quality of life features to Angular, which is already a powerful batteries-included front end framework. My previous portfolio, however, is built in WordPress, which provides a lot of template options for making sites like portfolios or blogs. Analog does not have a large template ecosystem at the moment, so that meant that I would need to build a lot of blog and portfolio functionality from scratch. I still wanted to use Analog, because the markdown and file routing support promise minimal friction if I just want to write a new post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Planning
&lt;/h2&gt;

&lt;p&gt;Once I knew what content I wanted to bring over and that I would have to build a lot of the site functionality myself, I looked at how my WordPress site is structured for inspiration and made lists for what features I wanted to keep vs discard:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep

&lt;ul&gt;
&lt;li&gt;Homepage&lt;/li&gt;
&lt;li&gt;About page&lt;/li&gt;
&lt;li&gt;Blog&lt;/li&gt;
&lt;li&gt;Blog index page&lt;/li&gt;
&lt;li&gt;Blog post page&lt;/li&gt;
&lt;li&gt;Categories&lt;/li&gt;
&lt;li&gt;Tags&lt;/li&gt;
&lt;li&gt;Previous/next post links&lt;/li&gt;
&lt;li&gt;Archives&lt;/li&gt;
&lt;li&gt;Filtering posts and route by year and year/month&lt;/li&gt;
&lt;li&gt;RSS&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Discard

&lt;ul&gt;
&lt;li&gt;Accounts&lt;/li&gt;
&lt;li&gt;Comments&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Nice to haves for later

&lt;ul&gt;
&lt;li&gt;Search (decided to implement a simple client side only version)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;This site is the canonical source for posts, but I currently repost them to &lt;a href="https://dev.to/karvel" rel="noopener"&gt;dev.to&lt;/a&gt;, which has account and comment support.&lt;/p&gt;

&lt;h2&gt;
  
  
  New Features
&lt;/h2&gt;

&lt;p&gt;In addition, I thought of new features I would want to have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Markdown support&lt;/li&gt;
&lt;li&gt;Photos page

&lt;ul&gt;
&lt;li&gt;Simple service that reaches out to the Flickr API to get some of my photos&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Talks page

&lt;ul&gt;
&lt;li&gt;Place to list any talks I have given&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Light/dark mode

&lt;ul&gt;
&lt;li&gt;Set by system preference initially&lt;/li&gt;
&lt;li&gt;User toggle for light/dark mode&lt;/li&gt;
&lt;li&gt;Mode updates syntax highlighing theme also&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Styling
&lt;/h2&gt;

&lt;p&gt;I decided to use Tailwind CSS, both to become more familiar with it and because it seemed like a good fit. The Analog &lt;code&gt;create analog@latest&lt;/code&gt; package template includes an option to generate a new project with Tailwind support from the start. It has been easy to build the site responsively with Tailwind, as it is designed around mobile-first layouts. I added the &lt;a href="https://tailwindcss.com/docs/typography-plugin" rel="noopener noreferrer"&gt;&lt;code&gt;@tailwindcss/typography&lt;/code&gt;&lt;/a&gt; plugin for some decent text styling defaults. The other default styles were useful, but I did customize the tailwind configuration slightly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Added a &lt;a href="https://github.com/Karvel/analogjs-blog/blob/develop/tailwind.config.js#L8" rel="noopener noreferrer"&gt;text shadow effect&lt;/a&gt;, which I adapted from this &lt;a href="https://daily-dev-tips.com/posts/tailwind-css-drop-shadow-effect-for-png-images/" rel="noopener noreferrer"&gt;article&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Removed &lt;a href="https://github.com/tailwindlabs/tailwindcss-typography/issues/18#issuecomment-1280797041" rel="noopener noreferrer"&gt;showing the back ticks when styling text with a monospace font&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tailwind is opinionated, but it isn't a full component framework. This site is simple enough that I didn't see the need to add any other CSS frameworks. I made UI elements from scratch, like the popover, which is built with content projection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data Handling
&lt;/h2&gt;

&lt;p&gt;I decided early on that this site would only have a client. The main reasons to have a back-end would be to support things like accounts, which I don't think I need. Since the majority of the routes interact with markdown files in some manner, I decided to use &lt;a href="https://jekyllrb.com/docs/front-matter/" rel="noopener noreferrer"&gt;front matter&lt;/a&gt; as a pseudo-api. The front matter on a given markdown file has the title, route slug, and date. &lt;a href="https://analogjs.org/docs/features/routing/content#using-the-content-files-list" rel="noopener noreferrer"&gt;&lt;code&gt;injectContentFiles()&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://analogjs.org/docs/features/routing/content#using-the-analog-markdown-component" rel="noopener noreferrer"&gt;&lt;code&gt;injectContent()&lt;/code&gt;&lt;/a&gt;  effectively became my &lt;code&gt;get()&lt;/code&gt; and &lt;code&gt;getById()&lt;/code&gt; methods. I made the following interface to interact with the front matter:&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="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;BlogPost&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;author&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;category&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;cover_image&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;cover_image_author&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;cover_image_source&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;cover_image_title&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;date&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;description&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;last_updated&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;published&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="nl"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;title&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&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 did make a &lt;a href="https://github.com/Karvel/analogjs-blog/blob/develop/src/app/services/api/flickr.service.ts" rel="noopener noreferrer"&gt;small API service&lt;/a&gt; for Flickr, where I host my photos. Flickr does have a public API for this purpose. If I could no longer rely on Flickr, I would need to come up with an alternate solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other Priorities
&lt;/h2&gt;

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

&lt;p&gt;Accessibility is really important, and like a lot of things in web development, it is much easier to implement from the beginning rather than with a complete project. Aside from the usual considerations, I made sure that the site is fully navigable from the keyboard. While styling the site, I also spent time considering the contrast for the light and dark themes, and the syntax theme for Prism. I found a &lt;a href="https://github.com/ericwbailey/a11y-syntax-highlighting" rel="noopener noreferrer"&gt;set of prism themes&lt;/a&gt; that are a11y compliant. I also tweaked the wrapping on the code blocks after &lt;a href="https://whitep4nth3r.com/blog/how-to-make-your-code-blocks-accessible-on-your-website/" rel="noopener noreferrer"&gt;following this guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;While the best accessibility testing comes using tools like screen readers directly, I have found these plug-ins very helpful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://wave.webaim.org/" rel="noopener noreferrer"&gt;WAVE Evaluation Tool&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Provides a simple audit of individual pages for accessibility concerns. Needs to be reloaded after navigating to a new route within a SPA. Available for Chrome, Firefox, and Edge.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://colourcontrast.cc/" rel="noopener noreferrer"&gt;Color Contrast Checker&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Shows if color contrast is sufficient, at different standard sizes. It can help developers have a consistent flow if checking contrast in multiple browsers.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  SEO
&lt;/h3&gt;

&lt;p&gt;I don't care a lot about SEO for this site, but I wanted to at least set meta tags including OG tags and have a sitemap. I made a &lt;a href="https://github.com/Karvel/analogjs-blog/blob/develop/vite.prerender.utils.ts" rel="noopener noreferrer"&gt;set of utility methods&lt;/a&gt; to dynamically build sitemap links for things like posts, categories, and tags.&lt;/p&gt;

&lt;p&gt;The official &lt;a href="https://www.opengraph.xyz/" rel="noopener noreferrer"&gt;Open Graph tool&lt;/a&gt; is very useful to confirm that the OG tags are working as expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the Site
&lt;/h2&gt;

&lt;p&gt;Since this is a personal project, I was okay with starting simple and seeing what my needs were. I don't think I'll need to implement NgRx in this project, for instance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Routing
&lt;/h3&gt;

&lt;p&gt;My WordPress site had a blog route structure of &lt;code&gt;/:year/:month/:slug&lt;/code&gt;. For this site, I decided to use &lt;a href="https://github.com/Karvel/analogjs-blog/blob/develop/src/app/pages/blog/%5Byear%5D.%5Bmonth%5D.%5Bslug%5D.page.ts" rel="noopener noreferrer"&gt;&lt;code&gt;/blog/:year/:month/:slug&lt;/code&gt;&lt;/a&gt;. I wanted to be able to navigate to either &lt;a href="https://github.com/Karvel/analogjs-blog/blob/develop/src/app/pages/blog/%5Byear%5D.page.ts" rel="noopener noreferrer"&gt;&lt;code&gt;/blog/:year&lt;/code&gt;&lt;/a&gt; or &lt;a href="https://github.com/Karvel/analogjs-blog/blob/develop/src/app/pages/blog/%5Byear%5D.%5Bmonth%5D.page.ts" rel="noopener noreferrer"&gt;&lt;code&gt;/blog/:year/:month&lt;/code&gt;&lt;/a&gt; and show posts that matched those parameters. The year and month are consumed from the &lt;code&gt;date&lt;/code&gt; property in the front matter, so that the posts could control the routing programmatically. Under the hood, slug and file name are still the real unique identifiers, so I couldn't have two files with the same slug and different dates without additional work. I would implement that for a client project, but I think that will be fine for this site.&lt;/p&gt;

&lt;h3&gt;
  
  
  Archive Component
&lt;/h3&gt;

&lt;p&gt;I created a &lt;a href="https://github.com/Karvel/analogjs-blog/blob/develop/src/app/components/archive/archive.component.ts" rel="noopener noreferrer"&gt;component&lt;/a&gt; that traverses the provided list of posts and &lt;a href="https://github.com/Karvel/analogjs-blog/blob/develop/src/app/utils/get-archive-links.ts" rel="noopener noreferrer"&gt;creates an array&lt;/a&gt; of labels and routes for years and months that have posts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pills Component
&lt;/h3&gt;

&lt;p&gt;The site supports both categories and tags for organizing content. I decided to create a &lt;a href="https://github.com/Karvel/analogjs-blog/blob/develop/src/app/components/pill/pill.component.ts" rel="noopener noreferrer"&gt;generic pills component&lt;/a&gt; for both. The tags are saved as a comma separated string, so I &lt;a href="https://github.com/Karvel/analogjs-blog/blob/develop/src/app/utils/split-tag-string-into-array.ts" rel="noopener noreferrer"&gt;split the string into an array&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Search
&lt;/h3&gt;

&lt;p&gt;I added a &lt;a href="https://github.com/Karvel/analogjs-blog/blob/develop/src/app/services/search.service.ts" rel="noopener noreferrer"&gt;simple search feature&lt;/a&gt; to the header. It is client-side only for now. If I write enough articles that it does not scale well, I can create a back-end to host posts and perform searches. At the moment, it only searches the post front matter.&lt;/p&gt;

&lt;h3&gt;
  
  
  Images
&lt;/h3&gt;

&lt;p&gt;Between wanting to support cover images for blog posts, and showcase some of my photos, I knew I would need to support a lot of functionality for images.&lt;/p&gt;

&lt;h4&gt;
  
  
  Cover Images
&lt;/h4&gt;

&lt;p&gt;In order to support cover images for posts, I added a number of properties to the front matter:&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;cover_image&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nl"&gt;cover_image_author&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nl"&gt;cover_image_source&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nl"&gt;cover_image_title&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some of these apply to the cover image directly, and I use some for the popover.&lt;/p&gt;

&lt;p&gt;I used a version of this for the recent photo album component that I added to the home page.&lt;/p&gt;

&lt;h4&gt;
  
  
  Popover
&lt;/h4&gt;

&lt;p&gt;For the post cover images, I wanted to provide an easy way to show the photo name, author, and link. I decided to use a popover for this. Since I needed to make one by hand, I decided to use content projection. I made a &lt;a href="https://github.com/Karvel/analogjs-blog/blob/develop/src/app/components/popover/popover.component.ts" rel="noopener noreferrer"&gt;component with the responsibility of having the popover icon&lt;/a&gt;, that would open a passed in &lt;a href="https://github.com/Karvel/analogjs-blog/blob/develop/src/app/components/popover/image-info-popover-content.component.ts" rel="noopener noreferrer"&gt;additional component as projected content&lt;/a&gt;. I set the tab index on the popover icon so keyboards could navigate to it. This worked out nicely, and so I added it to the photos in the masonry grid also.&lt;/p&gt;

&lt;h4&gt;
  
  
  Skeleton Loader
&lt;/h4&gt;

&lt;p&gt;After the site had been live for a little while, I wanted to improve the loading experience by adding skeleton loaders. With the way that the site is constructed, image skeleton loaders made the most sense. I made a &lt;a href="https://github.com/Karvel/analogjs-blog/blob/develop/src/app/components/skeleton-card/skeleton-card.component.ts" rel="noopener noreferrer"&gt;skeleton card component&lt;/a&gt; that accepts &lt;code&gt;height&lt;/code&gt;, &lt;code&gt;maxWidth&lt;/code&gt;, and &lt;code&gt;width&lt;/code&gt; as bindings. I also added a &lt;a href="https://github.com/Karvel/analogjs-blog/blob/develop/src/app/services/screen-size.service.ts" rel="noopener noreferrer"&gt;screen size service&lt;/a&gt; so I could listen to resize events to make the skeleton cards responsive.&lt;/p&gt;

&lt;h4&gt;
  
  
  Masonry Grid
&lt;/h4&gt;

&lt;p&gt;I have used Flickr for many years, and I have always liked the way that masonry grids look. I wanted to put some of my favorite photos on a page, where they would randomly load from a list into a masonry grid. My initial approach involved a directive, which felt too heavy for what I needed. I looked around for a CSS only solution, and found &lt;a href="https://blog.logrocket.com/responsive-image-gallery-css-flexbox/" rel="noopener noreferrer"&gt;this very useful article&lt;/a&gt;. I tweaked the styling and ended up with &lt;a href="https://github.com/Karvel/analogjs-blog/blob/develop/src/app/components/masonry-grid/masonry-grid.component.scss" rel="noopener noreferrer"&gt;these rules&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Object Fit
&lt;/h4&gt;

&lt;p&gt;After implementing the masonry grid, I styled the post and talk thumbnail images with &lt;code&gt;object-fit: cover&lt;/code&gt; to &lt;a href="https://github.com/Karvel/analogjs-blog/pull/92/files" rel="noopener noreferrer"&gt;improve the flow of the cards&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Broken Image Directive
&lt;/h4&gt;

&lt;p&gt;I wanted a solution to help prevent image link rot. I added a &lt;a href="https://github.com/Karvel/analogjs-blog/blob/develop/src/app/directives/replace-broken-image.directive.ts" rel="noopener noreferrer"&gt;directive&lt;/a&gt; that listens to the &lt;code&gt;onerror&lt;/code&gt; event for the target element, which in this case is &lt;code&gt;img&lt;/code&gt;. If it fires the &lt;code&gt;onerror&lt;/code&gt; event, it replaces the broken image a fallback instead, which I host locally.&lt;/p&gt;

&lt;h3&gt;
  
  
  Draft Functionality
&lt;/h3&gt;

&lt;p&gt;I wanted to be able to support draft functionality, where a post would be hidden from routing if it didn't have &lt;code&gt;published: true&lt;/code&gt;. Draft posts still support direct navigation. To support this, I had to add filters to the lists of posts, tags and categories, and the post navigation buttons. I also added a banner to a draft post to make it easier to distinguish.&lt;/p&gt;

&lt;h3&gt;
  
  
  RSS
&lt;/h3&gt;

&lt;p&gt;One of the nice features that Analog supports is RSS. I wanted to continue to provide an RSS feed, which is something that comes with WordPress sites by default. I personally prefer making the entire post text available and not just a blurb. The implementation can be found here: &lt;a href="https://github.com/Karvel/analogjs-blog/blob/develop/src/server/routes/feed.xml.ts" rel="noopener noreferrer"&gt;&lt;code&gt;feed.xml.ts&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tag Cloud
&lt;/h3&gt;

&lt;p&gt;Initially, I implemented a tag list page that aggregates all of the tags I use. Later on, I added tag cloud functionality, where I &lt;a href="https://github.com/Karvel/analogjs-blog/blob/develop/src/app/utils/aggregate-and-weigh-tags.ts" rel="noopener noreferrer"&gt;assign weights &lt;/a&gt; based on how many times I use a tag and resize the tag text so that more frequently used tags are larger.&lt;/p&gt;

&lt;h2&gt;
  
  
  Static Site
&lt;/h2&gt;

&lt;p&gt;As the same became usable, I started leaning toward building and deploying it as a static site. Static sites are very portable, which is important to me. I don't want to be stuck with a hosting service that I don't like because I can't easily switch. After evaluating the available options and my needs, I chose GitHub Pages for hosting. It is free for small scale sites, which this is. I was able to adapt the &lt;a href="https://nitro.unjs.io/deploy/providers/github-pages/" rel="noopener noreferrer"&gt;Nitro Github Action template&lt;/a&gt; to build and deploy. I configured it to deploy when I push to the &lt;code&gt;main&lt;/code&gt; branch.&lt;/p&gt;

&lt;p&gt;There are limitations to static sites and hosting on GitHub Pages. If I wanted to have a back end, I would need to host it somewhere else. In order to make changes to the site, whether programmatic changes or adding a new post, I would need to commit to the GitHub repository. I don't have any kind of admin panel I could use to make changes from any web browser. I also need to build the site in order to deploy it. The build times are quick, but not instant.&lt;/p&gt;

&lt;p&gt;For this site, none of these are deal breakers, but I wouldn't choose static sites for all use cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotchas
&lt;/h2&gt;

&lt;p&gt;Initially, I used the standard &lt;code&gt;/blog/:slug&lt;/code&gt; route structure for individual posts. However, after I decided I wanted to preserve the &lt;code&gt;/blog/:year/:month/:slug&lt;/code&gt; structure, I ran into an issue with how Angular deals with rerendering a component if only a route parameter value changes. If I navigated from one route structure to another, like &lt;code&gt;/blog&lt;/code&gt; to &lt;code&gt;/blog/:year/:month/:slug&lt;/code&gt;, the routing and rerendering worked as expected. However, if I only changed the &lt;code&gt;/blog/:year/:month/:slug&lt;/code&gt; router params, like moving from one post to another using the navigation buttons, the route would update but the page would not rerender. This is intentional behavior in Angular. In order to get the behavior I wanted, I needed to &lt;a href="https://github.com/Karvel/analogjs-blog/pull/88/files" rel="noopener noreferrer"&gt;add listeners to the route parameters&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Recreating my site in Analog took me a while because I had less free time to work on it than I liked and kept finding things to add, but I am very happy with the results so far. I still have some polish to apply, and some new features I want to implement, but I now have a site where it's easy for me to just publish a new post. The site is easy to move to new hosting if I need to, and it doesn't require a lot of dependencies or resources to build.&lt;/p&gt;

&lt;p&gt;The post &lt;a href="https://elanna.me/blog/2023/12/how-i-switched-my-website-to-analog" rel="noopener noreferrer"&gt;How I Switched My Website to Analog&lt;/a&gt; appeared first on &lt;a href="https://elanna.me" rel="noopener noreferrer"&gt;Hapax Legomenon&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>development</category>
      <category>angular</category>
      <category>analogjs</category>
      <category>wordpress</category>
    </item>
    <item>
      <title>Time for a Change: Switching to Analog</title>
      <dc:creator>Elanna Grossman</dc:creator>
      <pubDate>Sat, 25 Nov 2023 21:07:53 +0000</pubDate>
      <link>https://dev.to/karvel/time-for-a-change-switching-to-analog-4119</link>
      <guid>https://dev.to/karvel/time-for-a-change-switching-to-analog-4119</guid>
      <description>&lt;h2&gt;
  
  
  Hapax Legomenon 3.0
&lt;/h2&gt;

&lt;p&gt;This is the third version of this site. I started the original site in 2014, built in WordPress. I revamped the content and theme and started learning the WordPress ecosystem better in 2015. The second version was good enough to suit my needs for years. However, my end-of-life WordPress theme recently broke. That was enough of a motivator to redo this site. What follows is an exploration of why I decided that switching to Analog was the right choice.&lt;/p&gt;

&lt;p&gt;I had been thinking about redoing my website in something other than WordPress for a while. WordPress was definitely a good choice for this site originally. I was new to web development and I appreciated the batteries-included nature. However, I don't develop much in PHP or WordPress, and haven't kept up with the ecosystem much recently. I did not enjoy using Gutenberg block system for blog posts, which is the primary focus of my site.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Use Case
&lt;/h2&gt;

&lt;p&gt;I had looked at various Jamstack and SSG tools, especially ones built around writing content in Markdown. I don't find myself using full word processors much anymore, and Markdown is a sweet spot between simplicity and versatility. For this site, I don't need a back-end. I don't need to have user accounts or comments (&lt;a href="https://dev.to/karvel"&gt;dev.to&lt;/a&gt; works for that), and I host the majority of the images I link on my &lt;a href="https://www.flickr.com/photos/jadeilyn/" rel="noopener noreferrer"&gt;flickr&lt;/a&gt; (I'd figure out a contingency if flicker ever goes down). As a result, I wanted to make a simple site that I could deploy statically to something like GitHub Pages. I also wanted to retain RSS functionality, as one of the vanishing few who still use it.&lt;/p&gt;

&lt;p&gt;I value portability in websites because I don't want to be stuck in a particular ecosystem. I haven't liked the direction that Medium has taken in the past few years. While I do re-post articles from my site my dev.to, I maintain my site as the canonical source. I had only recently heard of Analog when my WordPress site started having problems, so I decided to check it out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter Analog
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://analogjs.org/" rel="noopener noreferrer"&gt;Analog&lt;/a&gt; is a meta framework for Angular, like Next.js is for React and Nuxt is for Vue. This means that while most of tooling is pure Angular, it adds some extra features like markdown rendering, file-based routing, SSG support, RSS support, what seems to be more reliable a SSR implementation (my historical experience with the official Angular Universal was a mixed bag), along with some other benefits. It also uses Vite instead of Webpack, and Vitest instead of Jasmine or Jest.&lt;/p&gt;

&lt;p&gt;It's a new framework, and not fully finished, so I spent some time making a sandbox site to see if it would suit my needs. I am not using all of its features, but I have been enjoying it a lot so far. I am using it for this site, which is a personal site and blog. I'm not sure if I would use it for a client project until it's more mature, but I haven't run into any real issues yet. The biggest issue I've found is that the app sometimes caches while developing locally, but reloading in a private window fixes that.&lt;/p&gt;

&lt;p&gt;While I wish that the main support infrastructure used something like a forum instead of Discord (since Discord is a closed ecosystem and can't be searched externally), I find the community very welcoming and helpful. As Analog is quite new, it doesn't have a large template or plugin ecosystem yet.&lt;/p&gt;

&lt;p&gt;I wrote a follow-up post on &lt;a href="https://elanna.me/blog/2023/12/how-i-switched-my-website-to-analog" rel="noopener noreferrer"&gt;how I made the switch&lt;/a&gt;, but I wanted to write a more general overview of my experience with switching to Analog so far and why I chose it. I'm looking forward to seeing how it evolves.&lt;/p&gt;

&lt;p&gt;The post &lt;a href="https://elanna.me/blog/2023/11/time-for-a-change-switching-to-analog" rel="noopener noreferrer"&gt;Time for a Change: Switching to Analog&lt;/a&gt; appeared first on &lt;a href="https://elanna.me" rel="noopener noreferrer"&gt;Hapax Legomenon&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>development</category>
      <category>angular</category>
      <category>analogjs</category>
      <category>wordpress</category>
    </item>
    <item>
      <title>Recommended Development Tools in Linux, MacOS, and Windows</title>
      <dc:creator>Elanna Grossman</dc:creator>
      <pubDate>Sat, 18 Feb 2023 02:39:42 +0000</pubDate>
      <link>https://dev.to/karvel/recommended-development-tools-in-linux-macos-and-windows-4i9l</link>
      <guid>https://dev.to/karvel/recommended-development-tools-in-linux-macos-and-windows-4i9l</guid>
      <description>&lt;p&gt;I use Linux, MacOS, and Windows, and I like all three operating systems. There might be a tool I like more in one than the others, but I can write code and spin up environments regardless of which OS I’m using. In this article I have put together a list of my recommended development tools.&lt;/p&gt;

&lt;p&gt;I have a work-provided Macbook Pro that runs MacOS. It is based on Apple Silicon and I have found that the ecosystem is a lot better than it was in 2021 when I first switched. Most applications I need are now native or universal and even NodeJS runs decently well on the native architecture.&lt;/p&gt;

&lt;p&gt;My personal computer is a desktop that runs Windows 10 and &lt;a href="https://manjaro.org/" rel="noopener noreferrer"&gt;Manjaro&lt;/a&gt; (Manjaro is an arch-derivative rolling release distro. I use the &lt;a href="https://manjaro.org/download/#cinnamon" rel="noopener noreferrer"&gt;Cinnamon DE&lt;/a&gt; because I can keep my window-snapping muscle memory). Since the desktop is my personal machine, I have found it useful to do most of my programming there on Linux. This helps me separate coding time mentally. I deliberately did not install anything like games in Linux so it could be a focused environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Development Tools
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Package Manager
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/Jguer/yay" rel="noopener noreferrer"&gt;yay&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Linux&lt;/li&gt;
&lt;li&gt;yay wraps the built-in &lt;a href="https://wiki.archlinux.org/title/pacman" rel="noopener noreferrer"&gt;pacman&lt;/a&gt;. I use it to install all of the Linux software.&lt;/li&gt;
&lt;li&gt;This is only available for arch and arch-derivative distros.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://brew.sh/" rel="noopener noreferrer"&gt;homebrew&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;MacOS&lt;/li&gt;
&lt;li&gt;Install most software available on homebrew with it.&lt;/li&gt;
&lt;li&gt;Since I’m on Apple Silicon, I check if the homebrew packages are native or universal, and if they still point to Darwin, I install manually. In the past year, this situation has gotten a lot better.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;I don’t use a package manager on Windows. I have been watching the development of &lt;a href="https://github.com/microsoft/winget-cli/" rel="noopener noreferrer"&gt;winget&lt;/a&gt; and I plan to try it out now that it is out of preview.&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Terminal
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://terminator-gtk3.readthedocs.io/en/latest/" rel="noopener noreferrer"&gt;Terminator&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Linux&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://iterm2.com/" rel="noopener noreferrer"&gt;iTerm&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;MacOS&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://github.com/microsoft/terminal" rel="noopener noreferrer"&gt;Windows Terminal&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Windows&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  IDE
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://visualstudio.microsoft.com/downloads/" rel="noopener noreferrer"&gt;Visual Studio&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Windows&lt;/li&gt;
&lt;li&gt;I like to use Visual Studio in Windows for .NET projects , but I think that for .NET Core API projects Visual Studio Code works very well. I do not use Visual Studio for front end projects.&lt;/li&gt;
&lt;li&gt;I also don’t use Visual Studio on MacOS unless I am working with Xamarin. It is not the same product under the hood as the original Visual Studio on Windows and I use Visual Studio Code instead.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Code Editor
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://code.visualstudio.com/" rel="noopener noreferrer"&gt;Visual Studio Code&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Linux&lt;/li&gt;
&lt;li&gt;MacOS&lt;/li&gt;
&lt;li&gt;Windows&lt;/li&gt;
&lt;li&gt;I listed my recommended extensions &lt;a href="https://elanna.me/blog/2023/01/recommended-vs-code-extensions/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Git Client
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.gitkraken.com/" rel="noopener noreferrer"&gt;GitKraken&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Linux&lt;/li&gt;
&lt;li&gt;MacOS&lt;/li&gt;
&lt;li&gt;Windows&lt;/li&gt;
&lt;li&gt;I like using Git GUI clients because I have a very visual memory. GitKraken helps me commit only final code and not debug stuff. I also like to use it to commit specific lines which helps me do atomic commits.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Database Tools
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dbeaver.io/" rel="noopener noreferrer"&gt;DBeaver&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Linux&lt;/li&gt;
&lt;li&gt;MacOS&lt;/li&gt;
&lt;li&gt;Windows&lt;/li&gt;
&lt;li&gt;I most frequently use Postgres and MSSQL. DBeaver does not work as well for non-relational databases. &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://docs.microsoft.com/en-us/sql/ssms/download-sql-server-management-studio-ssms?view=sql-server-ver15" rel="noopener noreferrer"&gt;SQL Server Management Studio (SSMS)&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Windows&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  HTTP
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.postman.com/" rel="noopener noreferrer"&gt;Postman&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Linux&lt;/li&gt;
&lt;li&gt;MacOS&lt;/li&gt;
&lt;li&gt;Windows&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Notes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/BoostIO/Boostnote" rel="noopener noreferrer"&gt;Boost Note&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Linux&lt;/li&gt;
&lt;li&gt;MacOS&lt;/li&gt;
&lt;li&gt;Windows&lt;/li&gt;
&lt;li&gt;There is an older and a newer version of Boost Note. I still use the older version because I prefer the feature set. The older version is in maintenance mode and might be an acquired taste at this point, but I really like it.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Sync
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.dropbox.com" rel="noopener noreferrer"&gt;Dropbox&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Linux&lt;/li&gt;
&lt;li&gt;MacOS&lt;/li&gt;
&lt;li&gt;Windows&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;If I find any other recommended development tools, I will add them here.&lt;/p&gt;

&lt;p&gt;The post &lt;a href="https://elanna.me/blog/2023/02/recommended-development-tools-in-linux-macos-and-windows/" rel="noopener noreferrer"&gt;Recommended Development Tools in Linux, MacOS, and Windows&lt;/a&gt; appeared first on &lt;a href="https://elanna.me" rel="noopener noreferrer"&gt;Hapax Legomenon&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>development</category>
      <category>codeeditor</category>
      <category>git</category>
      <category>ide</category>
    </item>
    <item>
      <title>Recommended VS Code Extensions</title>
      <dc:creator>Elanna Grossman</dc:creator>
      <pubDate>Sat, 07 Jan 2023 02:10:54 +0000</pubDate>
      <link>https://dev.to/karvel/recommended-vs-code-extensions-1l8n</link>
      <guid>https://dev.to/karvel/recommended-vs-code-extensions-1l8n</guid>
      <description>&lt;p&gt;&lt;a href="https://code.visualstudio.com/" rel="noopener noreferrer"&gt;Visual Studio Code&lt;/a&gt; is my code editor of choice. I regularly use Linux, MacOS, and Windows, and it is very nice to have a consistent tool between each operating system. I have listed my recommended VS Code extensions below. &lt;a href="https://elanna.me/blog/2023/02/recommended-development-tools-in-linux-macos-and-windows/" rel="noopener noreferrer"&gt;In this post&lt;/a&gt;, I talk about general development tools I like to use in each operating system.&lt;/p&gt;

&lt;h2&gt;
  
  
  General
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens" rel="noopener noreferrer"&gt;GitLens — Git supercharged&lt;/a&gt;
Awesome parsing of the project’s git information. This extension pulls in the git history and blame from the project, and can show git history on the highlighted line.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://marketplace.visualstudio.com/items?itemName=wmaurer.change-case" rel="noopener noreferrer"&gt;change-case&lt;/a&gt;
An easy way to change the case of selected text.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker" rel="noopener noreferrer"&gt;Code Spell Checker&lt;/a&gt;
A regular spell checker. It’s smart enough to parse most words separately in a variable name.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://marketplace.visualstudio.com/items?itemName=Zignd.html-css-class-completion" rel="noopener noreferrer"&gt;IntelliSense for CSS class names in HTML&lt;/a&gt;
Auto-completion for CSS names based on existing rules.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://marketplace.visualstudio.com/items?itemName=christian-kohler.path-intellisense" rel="noopener noreferrer"&gt;Path Intellisense&lt;/a&gt;
Path auto-completions.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-docker" rel="noopener noreferrer"&gt;Docker&lt;/a&gt;
Useful tooling for Docker.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint" rel="noopener noreferrer"&gt;ESLint&lt;/a&gt;
The de facto Javascript linter.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://marketplace.visualstudio.com/items?itemName=GrapeCity.gc-excelviewer" rel="noopener noreferrer"&gt;Excel Viewer&lt;/a&gt;
Can parse CSV as a table.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://marketplace.visualstudio.com/items?itemName=yzhang.markdown-all-in-one" rel="noopener noreferrer"&gt;Markdown All in One&lt;/a&gt;
Shortcuts and tooling to help write Markdown in the VS Code editor. My personal favorite utility here is creating and keeping an up to date a table of contents based on the header structure.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer" rel="noopener noreferrer"&gt;Live Server&lt;/a&gt;
An easy way to launch a server from inside the project.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://marketplace.visualstudio.com/items?itemName=Gruntfuggly.todo-tree" rel="noopener noreferrer"&gt;Todo Tree&lt;/a&gt;
Helps organize TODOs so they don’t get lost.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://marketplace.visualstudio.com/items?itemName=MS-vsliveshare.vsliveshare-pack" rel="noopener noreferrer"&gt;Live Share Extension Pack&lt;/a&gt;
This is a really powerful paired programming tool. It works and is cross-compatible Visual Studio Code, Visual Studio, or in a browser (the session host has to use Visual Studio or Visual Studio Code).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Angular
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://marketplace.visualstudio.com/items?itemName=johnpapa.angular-essentials" rel="noopener noreferrer"&gt;Angular Essentials&lt;/a&gt;
A pack of extensions maintained by John Papa, one of the celebrities in the Angular world. I have found this to be a comprehensive set of extensions for Angular, especially since I don’t use snippets much.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  C#
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csharp" rel="noopener noreferrer"&gt;C#&lt;/a&gt;
This is the official C# extension from Microsoft and handles everything including syntax highlighting, IntelliSense, and debugging. It is a dogfooded tool and it shows.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Settings
&lt;/h2&gt;

&lt;p&gt;In addition to the above extensions, I have some settings I configure in Visual Studio Code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CodeLens for Javascript and Typescript. This parses projects for class, method, and property references and shows them above the class/method/property declaration. It does cause slight slowdown when loading a file, but I consider it a worthy tradeoff.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://code.visualstudio.com/docs/editor/editingevolved#_breadcrumbs" rel="noopener noreferrer"&gt;Breadcrumbs&lt;/a&gt;. These show file path breadcrumbs above the editor window.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If I find any other recommended VS Code extensions, I will add them here.&lt;/p&gt;

&lt;p&gt;The post &lt;a href="https://elanna.me/blog/2023/01/recommended-vs-code-extensions/" rel="noopener noreferrer"&gt;Recommended VS Code Extensions&lt;/a&gt; appeared first on &lt;a href="https://elanna.me" rel="noopener noreferrer"&gt;Hapax Legomenon&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>development</category>
      <category>linux</category>
      <category>macos</category>
      <category>osx</category>
    </item>
    <item>
      <title>A Curated List of Angular Resources</title>
      <dc:creator>Elanna Grossman</dc:creator>
      <pubDate>Tue, 10 Aug 2021 02:18:41 +0000</pubDate>
      <link>https://dev.to/karvel/a-curated-list-of-angular-resources-57ga</link>
      <guid>https://dev.to/karvel/a-curated-list-of-angular-resources-57ga</guid>
      <description>&lt;p&gt;Having worked with Angular since mid-2017, I have collected many resources that I have found useful. I will keep this list up to date.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Updated Oct 21, 2021&lt;/em&gt; - fixed broken links.&lt;/p&gt;

&lt;h2&gt;
  
  
  Angular Resources
&lt;/h2&gt;

&lt;h3&gt;
  
  
  General Documentation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://angular.io/docs/ts/latest/" rel="noopener noreferrer"&gt;Angular.io&lt;/a&gt; Includes good tutorials, detailed documentation, and API references along with interactive code snippets. This should be the default destination for Angular questions. Note: There are multiple versions of the “Tour of Heroes” tutorial demonstrating different concepts.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://angular.io/resources" rel="noopener noreferrer"&gt;Angular.io Resources&lt;/a&gt; The official and hopefully maintained list of resources from the Angular team.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://angular.io/docs/ts/latest/guide/style-guide.html" rel="noopener noreferrer"&gt;John Papa style guide&lt;/a&gt; The de facto style guide for Angular has been officially adopted by the Angular team and is now hosted on the official website. It is highly opinionated, but makes good cases for its suggestions. I follow most of it and do not disagree strongly with any of its suggestions.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://netbasal.com/" rel="noopener noreferrer"&gt;Netanel Basal&lt;/a&gt; Another skilled Angular developer with useful tutorials and deep dive articles.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://blog.angular-university.io/" rel="noopener noreferrer"&gt;Angular University&lt;/a&gt; Has multiple tutorials and deep dives for all things Angular.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://indepth.dev/" rel="noopener noreferrer"&gt;inDepth.dev community&lt;/a&gt; Formerly Angular In Depth. Covers RxJS, NgRX and Angular in depth – has articles explaining how and why Angular is architected.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ultimatecourses.com/blog/" rel="noopener noreferrer"&gt;Todd Motto&lt;/a&gt; A Google Developer Expert who writes detailed articles explaining Angular functionality and convention. His writing and explanation style differ sufficiently from the official documentation that it is a good supplemental resource.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://blog.strongbrew.io/" rel="noopener noreferrer"&gt;Strong Brew&lt;/a&gt; Technical high level articles for Angular, RxJS, and coding practices.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Essential articles
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://christiankohler.net/angular-dependency-injection-infographic" rel="noopener noreferrer"&gt;Angular Dependency Injection Infographic&lt;/a&gt; A visual explanation of how Dependency Injection works (and used to work) in Angular, and the different strategies available.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.jvandemo.com/the-7-step-process-of-angular-router-navigation/" rel="noopener noreferrer"&gt;The 7-step process of Angular router navigation&lt;/a&gt; Helps demystify the Angular routing lifecycle.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://blog.angular-university.io/angular-2-smart-components-vs-presentation-components-whats-the-difference-when-to-use-each-and-why/" rel="noopener noreferrer"&gt;Angular Architecture – Smart Components vs Presentational Components&lt;/a&gt; This explains the thought process behind the container/presenter pattern (also referred to as smart/dumb components) very well.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/this-is-angular/container-components-with-angular-4o05"&gt;Container components with Angular&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://indepth.dev/posts/1001/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error" rel="noopener noreferrer"&gt;Everything you need to know about the &lt;code&gt;ExpressionChangedAfterItHasBeenCheckedError&lt;/code&gt; error&lt;/a&gt; Here is an explanation of how to prevent an error that learning Angular developers run into a lot.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/angular/how-to-architect-epic-angular-app-in-less-than-10-minutes-35j2"&gt;How to architect epic Angular app in less than 10 minutes!&lt;/a&gt; Explanation of the project organization that I prefer.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://itnext.io/choosing-the-right-file-structure-for-angular-in-2020-and-beyond-a53a71f7eb05" rel="noopener noreferrer"&gt;Choosing The Right File Structure for Angular in 2020 and Beyond !&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Angular Best Practices
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://itnext.io/clean-code-checklist-in-angular-%EF%B8%8F-10d4db877f74" rel="noopener noreferrer"&gt;Clean Code Checklist in Angular&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://codeburst.io/angular-best-practices-4bed7ae1d0b7" rel="noopener noreferrer"&gt;Angular Best Practices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://craftsmen.nl/2-years-of-angular-lessons-learned/" rel="noopener noreferrer"&gt;2 years of Angular: lessons learned – Craftsmen&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Performance
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.bitsrc.io/10-tricks-to-optimize-your-angular-app-44208f616bf0" rel="noopener noreferrer"&gt;10 Tricks to Optimize Your Angular App – Bits and Pieces&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://angular.io/guide/lazy-loading-ngmodules" rel="noopener noreferrer"&gt;Angular – Lazy-loading feature modules&lt;/a&gt; Lazy loading splits the app into chunks that are only loaded on demand. This both reduces bundle size, and helps separate discrete features (e.g. the admin dashboard from the user dashboard).&lt;/li&gt;
&lt;li&gt;&lt;a href="https://netbasal.com/reduce-change-detection-cycles-with-event-coalescing-in-angular-c4037199859f" rel="noopener noreferrer"&gt;Reduce Change Detection Cycles with Event Coalescing in Angular&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.angular-university.io/onpush-change-detection-how-it-works/" rel="noopener noreferrer"&gt;Angular OnPush Change Detection and Component Design – Avoid Common Pitfalls&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://indepth.dev/posts/1053/everything-you-need-to-know-about-change-detection-in-angular" rel="noopener noreferrer"&gt;Everything you need to know about change detection in Angular&lt;/a&gt; A deep dive into how Angular’s change detection works.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.mokkapps.de/blog/the-last-guide-for-angular-change-detection-you-will-ever-need/" rel="noopener noreferrer"&gt;The Last Guide For Angular Change Detection You’ll Ever Need – Mokkapps (Michael Hoffmann) – Freelance Angular Software Engineer&lt;/a&gt; This is a wonderful article that explains change detection and provides a lot of visual examples and a working sandbox to test different change detection strategies.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://angular-change-detection-demo.netlify.app/complex-demo" rel="noopener noreferrer"&gt;Angular Change Detection Demos&lt;/a&gt; Code demo that goes with the above article.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://blog.angular-university.io/angular-2-ngfor/" rel="noopener noreferrer"&gt;Angular ngFor – Learn All Features, Not Only For Arrays&lt;/a&gt; Angular’s template looping directive &lt;code&gt;*ngFor&lt;/code&gt; has a lot of neat features, and one of them, &lt;code&gt;trackBy&lt;/code&gt;, can help a lot with performance.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Unit Testing
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://angular-university.io/course/angular-testing-course" rel="noopener noreferrer"&gt;Angular University – Testing Course&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/angular-university/angular-testing-course" rel="noopener noreferrer"&gt;GitHub – angular-university/angular-testing-course: Angular Testing Course – A complete guide to Angular Unit Testing and E2E Testing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://angular-university.io/lesson/angular-testing-jasmine-spies" rel="noopener noreferrer"&gt;Angular University – Spies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://christianlydemann.com/complete-guide-to-angular-testing/" rel="noopener noreferrer"&gt;The Complete Guide to Angular Testing and a Weird Trick for Faster Unit Tests (2019) – Christian Lüdemann&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://codecraft.tv/courses/angular/unit-testing/overview/" rel="noopener noreferrer"&gt;Unit Testing Overview • Angular&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://codecraft.tv/courses/angular/unit-testing/mocks-and-spies/" rel="noopener noreferrer"&gt;Testing with Mocks &amp;amp; Spies • Angular&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.sitepoint.com/angular-testing-introduction/" rel="noopener noreferrer"&gt;Angular Testing: A Developer’s Introduction — SitePoint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://scotch.io/tutorials/testing-angular-with-jasmine-and-karma-part-1" rel="noopener noreferrer"&gt;Testing Angular with Jasmine and Karma (Part 1) ― Scotch.io&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/ngconf/angular-unit-testing-code-coverage-lies-603c6c85f801" rel="noopener noreferrer"&gt;Angular Unit Testing Code-Coverage Lies – ngconf – Medium&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amadousall.com/unit-testing-angular-stubs-vs-spies-vs-mocks/" rel="noopener noreferrer"&gt;Unit Testing in Angular: Stubs vs Spies vs Mocks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/this-is-angular/create-a-component-harness-for-your-tests-with-angular-cdk-46bg"&gt;Create a component harness for your tests with Angular CDK&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://indepth.dev/author/layzee/" rel="noopener noreferrer"&gt;Lars Gyrup Brink Nielsen – WebDev inDepth&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/questions/40727581/cant-seem-to-catch-error-when-using-jasmine-tothrowerror" rel="noopener noreferrer"&gt;angular – Can’t seem to catch error when using Jasmine toThrowError – Stack Overflow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gist.github.com/amabes/88324d68690e0e7b8e313cd0cafaa219" rel="noopener noreferrer"&gt;Simplified example to demonstrate how to Mock a FileList for unit testing purposes. · GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/questions/54804034/testing-file-upload-in-angular-with-httpclient-unable-to-get-hold-of-http-post" rel="noopener noreferrer"&gt;jasmine – Testing file upload in Angular with HttpClient. Unable to get hold of HTTP POST body – Stack Overflow&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Testing Tools
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/ike18t/ng-mocks" rel="noopener noreferrer"&gt;GitHub – ike18t/ng-mocks: Angular 5+ component, directive, and pipe mocking library&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@abdul_74410/towards-better-testing-in-angular-part-1-mocking-child-components-b51e1fd571da" rel="noopener noreferrer"&gt;Towards Better Testing In Angular. Part 1 — Mocking Child Components | by Abdul Wahab Rafehi | Medium&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Error Handling
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://indepth.dev/posts/1465/expecting-the-unexpected-best-practices-for-error-handling-in-angular-2" rel="noopener noreferrer"&gt;Expecting the Unexpected — Best practices for Error handling in Angular – Angular inDepth&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Security
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://angular.io/guide/security" rel="noopener noreferrer"&gt;Angular.io Security&lt;/a&gt; This is the official documentation on security in Angular, best practices, and how to avoid creating vulnerabilities.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://snyk.io/blog/angular-security-best-practices/" rel="noopener noreferrer"&gt;6 Angular Security Best Practices – Snyk&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ordina-jworks.github.io/angular/2018/03/30/angular-security-best-practices.html" rel="noopener noreferrer"&gt;Angular Security Best Practices – Ordina JWorks Tech Blog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  RxJS
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Getting Started
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="http://reactivex.io/rxjs/manual/index.html" rel="noopener noreferrer"&gt;Official documentation&lt;/a&gt; The official guide for Observables. Easier to read than the API reference on the same site.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://angular.io/guide/observables" rel="noopener noreferrer"&gt;Angular.io Observables&lt;/a&gt; Angular documentation of Observables and RxJS. All examples are within Angular, and the guide explains how Angular uses RxJS beyond http calls.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.strongbrew.io/rxjs-best-practices-in-angular/" rel="noopener noreferrer"&gt;RxJS Best Practices&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Understanding Higher Order Observable Operators
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.angular-university.io/rxjs-higher-order-mapping/" rel="noopener noreferrer"&gt;Comprehensive Guide to Higher-Order RxJs Mapping Operators: switchMap, mergeMap, concatMap (and exhaustMap)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@shairez/a-super-ninja-trick-to-learn-rxjss-switchmap-mergemap-concatmap-and-exhaustmap-forever-88e178a75f1b" rel="noopener noreferrer"&gt;A Super Ninja Trick To Learn RxJS’s “switchMap”, “mergeMap”, “concatMap” and “exhaustMap”, FOREVER!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.strongbrew.io/building-a-safe-autocomplete-operator-with-rxjs/" rel="noopener noreferrer"&gt;Building a safe autocomplete operator in RxJS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Pitfalls
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.angular-university.io/angular-2-rxjs-common-pitfalls/" rel="noopener noreferrer"&gt;3 Common Rxjs Pitfalls that you might find while building Angular Applications&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://brianflove.com/2017-11-01/ngrx-anti-patterns/" rel="noopener noreferrer"&gt;RxJS Antipatterns&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.novanet.no/angular-pitfall-multiple-http-requests-with-rxjs-and-observable-async/" rel="noopener noreferrer"&gt;Angular pitfall: Multiple HTTP requests with RxJS and observable$ | async&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@paynoattn/3-common-mistakes-i-see-people-use-in-rx-and-the-observable-pattern-ba55fee3d031" rel="noopener noreferrer"&gt;3 Common Mistakes I see people use in Rx and the Observable Pattern&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Subscriptions
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://toddmotto.com/angular-ngif-async-pipe" rel="noopener noreferrer"&gt;Handling Observables with NgIf and the Async Pipe&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://netbasal.com/why-its-important-to-unsubscribe-from-rxjs-subscription-a7a6455d6a02" rel="noopener noreferrer"&gt;The Ultimate Answer To The Very Common Angular Question: subscribe() vs | async Pipe&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Understanding Hot and Cold Observables
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.thoughtram.io/angular/2016/06/16/cold-vs-hot-observables.html" rel="noopener noreferrer"&gt;Cold vs Hot Observables&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.strongbrew.io/my-favorite-metaphor-for-hot-vs-cold-observables/" rel="noopener noreferrer"&gt;My favorite metaphor for hot vs cold observables&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jaredforsyth.com/posts/visualizing-reactive-streams-hot-and-cold/" rel="noopener noreferrer"&gt;Visualizing Reactive Streams: Hot and Cold Observables&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.strongbrew.io/multicasting-operators-in-rxjs/" rel="noopener noreferrer"&gt;Multicasting operators in RxJS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://codingthesmartway.com/getting-started-with-rxjs-part-3-hot-and-cold-observables/" rel="noopener noreferrer"&gt;Getting Started With RxJS – Part 3: Hot And Cold Observables&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  RxJS Error Handling
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.angular-university.io/rxjs-error-handling/" rel="noopener noreferrer"&gt;RxJs Error Handling: Complete Practical Guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Advanced RxJS
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://xgrommx.github.io/rx-book/content/guidelines/introduction/index.html" rel="noopener noreferrer"&gt;Rx Book&lt;/a&gt; This is out of date but it is still one of the best deep dives into how ReactiveX works.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.strongbrew.io/what-are-schedulers-in-rxjs/" rel="noopener noreferrer"&gt;What are schedulers in RxJS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  TypeScript
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.typescriptlang.org/docs/home.html" rel="noopener noreferrer"&gt;Documentation · TypeScript&lt;/a&gt; Official documentation resource for TypeScript. Regularly updated.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Advanced Types
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.typescriptlang.org/docs/handbook/advanced-types.html" rel="noopener noreferrer"&gt;Advanced Types · TypeScript&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.logrocket.com/when-to-use-never-and-unknown-in-typescript-5e4d6c5799ad/" rel="noopener noreferrer"&gt;When to use &lt;code&gt;never&lt;/code&gt; and &lt;code&gt;unknown&lt;/code&gt; in TypeScript – LogRocket Blog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Generics
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.typescriptlang.org/docs/handbook/2/generics.html" rel="noopener noreferrer"&gt;Generics · TypeScript&lt;/a&gt; Generics are a great way of avoiding having to use &lt;code&gt;any&lt;/code&gt; because they enact ‘generic’ behavior on the type passed in.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of my posts on Angular are tagged and collected &lt;a href="https://elanna.me/tag/angular/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The post &lt;a href="https://elanna.me/blog/2021/08/a-curated-list-of-angular-resources/" rel="noopener noreferrer"&gt;A Curated List of Angular Resources&lt;/a&gt; appeared first on &lt;a href="https://elanna.me" rel="noopener noreferrer"&gt;Hapax Legomenon&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>development</category>
      <category>angular</category>
      <category>rxjs</category>
    </item>
    <item>
      <title>Creating a Multi-Control Custom Validator in Angular</title>
      <dc:creator>Elanna Grossman</dc:creator>
      <pubDate>Wed, 23 Jun 2021 03:56:30 +0000</pubDate>
      <link>https://dev.to/karvel/creating-a-multi-control-custom-validator-in-angular-if</link>
      <guid>https://dev.to/karvel/creating-a-multi-control-custom-validator-in-angular-if</guid>
      <description>&lt;p&gt;Custom validators in Angular’s reactive form library are one of the most powerful (and in my opinion overlooked) tools a developer has to create better form UI/UX. Custom validators aren’t just limited to a single control. It is easy to evaluate an entire group. This is great for comparing multiple controls. In this article I create a multi-control custom validator that validates two fields if their values match to show an example of what is possible.&lt;/p&gt;

&lt;p&gt;As I mentioned in my &lt;a href="https://elanna.me/blog/2021/06/exploring-custom-form-validators-in-angular/" rel="noopener noreferrer"&gt;previous article about custom validators&lt;/a&gt;, I like using them to both handle custom logic that the built-in validators don’t, and to be able to create the validation error messages in one spot. This makes custom validators powerful and very reusable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a Multi-Control Custom Validator
&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%2Felanna.me%2Fimages%2Fupload%2Fmatch-field-validator-1.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%2Felanna.me%2Fimages%2Fupload%2Fmatch-field-validator-1.gif" alt="Animated image demonstrating the multi-control custom validator"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Creating a multi-control custom validator is very similar to creating a single-control one. The validator needs a passed in &lt;a href="https://angular.io/api/forms/AbstractControl" rel="noopener noreferrer"&gt;&lt;code&gt;AbstractControl&lt;/code&gt;&lt;/a&gt; parameter. In single-control validators, the control is normally a &lt;a href="https://angular.io/api/forms/FormControl" rel="noopener noreferrer"&gt;&lt;code&gt;FormControl&lt;/code&gt;&lt;/a&gt;. However, for multi-control validators, I need to pass in the parent &lt;a href="https://angular.io/api/forms/FormGroup" rel="noopener noreferrer"&gt;&lt;code&gt;FormGroup&lt;/code&gt;&lt;/a&gt; as the control. Doing this gives me the scope of all of the children controls inside of the &lt;code&gt;FormGroup&lt;/code&gt;. To make this validator more reusable, I also pass in the names of the controls I want to compare. I also can pass in the name of the kind of values I am comparing to make the error messages more dynamic.&lt;/p&gt;

&lt;p&gt;I then create variables for the values from the form controls. Once I have those, I set up some simple conditionals. Since I passed in the &lt;code&gt;FormGroup&lt;/code&gt; as the &lt;code&gt;AbstractControl&lt;/code&gt; instead of a specific &lt;code&gt;FormControl&lt;/code&gt;, if I want to set errors on the &lt;code&gt;FormControls&lt;/code&gt;, I need to call &lt;a href="https://angular.io/api/forms/AbstractControl#seterrors" rel="noopener noreferrer"&gt;&lt;code&gt;setErrors()&lt;/code&gt;&lt;/a&gt; on the specific control. Otherwise, if I just return the &lt;a href="https://angular.io/api/forms/ValidationErrors" rel="noopener noreferrer"&gt;&lt;code&gt;ValidationErrors&lt;/code&gt;&lt;/a&gt;, they will apply to the &lt;code&gt;FormGroup&lt;/code&gt;, which isn’t what I want here.&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;MatchFieldValidator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="nf"&gt;validFieldMatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;controlName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;confirmControlName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;fieldName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Password&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="nx"&gt;ValidatorFn&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return &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;AbstractControl&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;ValidationErrors&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&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;const&lt;/span&gt; &lt;span class="na"&gt;controlValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;control&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;controlName&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;confirmControlValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;control&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;confirmControlName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nx"&gt;value&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;confirmControlValue&lt;/span&gt;&lt;span class="p"&gt;)&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="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;confirmControlName&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nf"&gt;setErrors&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;confirmFieldRequired&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Confirm &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fieldName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; is required.`&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;controlValue&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;confirmControlValue&lt;/span&gt;&lt;span class="p"&gt;)&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="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;confirmControlName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;setErrors&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;fieldsMismatched&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;fieldName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; fields do not match.`&lt;/span&gt; &lt;span class="p"&gt;});&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="nx"&gt;controlValue&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;controlValue&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;confirmControlValue&lt;/span&gt;&lt;span class="p"&gt;)&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="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;confirmControlName&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nf"&gt;setErrors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&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;Now that I have a working validator, I need to wire it up to the component. Since I want to be to interact with multiple &lt;code&gt;FormControls&lt;/code&gt;, I need to attach the validator to the parent &lt;code&gt;FormGroup&lt;/code&gt;. The &lt;a href="https://angular.io/api/forms/FormBuilder" rel="noopener noreferrer"&gt;&lt;code&gt;FormBuilder&lt;/code&gt;&lt;/a&gt; takes an options argument after the control config where I can pass in validators. I add the match field validator, along with the names of the controls I want to compare, and what kind of field I’m comparing. I’ve simplified the below code to just focus on what is relevant:&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;private&lt;/span&gt; &lt;span class="nf"&gt;createForm&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="kd"&gt;const&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;password&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="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="nf"&gt;compose&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;PasswordValidator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validPassword&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="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;confirmPassword&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="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;validators&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="nf"&gt;compose&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="nx"&gt;MatchFieldValidator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validFieldMatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;password&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;confirmPassword&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;Password&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;form&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;As I now having working validation, I can bind the errors to the template. I am still using the loop through the errors object via the &lt;code&gt;KeyValuePipe&lt;/code&gt; for simplicity.&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;"field-group"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;mat-form-field&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
      &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;
      &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;
      &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;
      &lt;span class="na"&gt;matInput&lt;/span&gt;
      &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Password"&lt;/span&gt;
      &lt;span class="na"&gt;formControlName=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;
    &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;mat-error&lt;/span&gt; &lt;span class="na"&gt;*ngIf=&lt;/span&gt;&lt;span class="s"&gt;"form.get('password')?.errors"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;ng-container&lt;/span&gt; &lt;span class="na"&gt;*ngFor=&lt;/span&gt;&lt;span class="s"&gt;"let error of form.get('password')?.errors | keyvalue"&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;*ngIf=&lt;/span&gt;&lt;span class="s"&gt;"error.key !== 'required'"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{ error.value }}&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/ng-container&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/mat-error&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/mat-form-field&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;mat-form-field&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
      &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"confirmPassword"&lt;/span&gt;
      &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"confirmPassword"&lt;/span&gt;
      &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;
      &lt;span class="na"&gt;matInput&lt;/span&gt;
      &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Confirm Password"&lt;/span&gt;
      &lt;span class="na"&gt;formControlName=&lt;/span&gt;&lt;span class="s"&gt;"confirmPassword"&lt;/span&gt;
      &lt;span class="na"&gt;required&lt;/span&gt;
    &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;mat-error&lt;/span&gt; &lt;span class="na"&gt;*ngIf=&lt;/span&gt;&lt;span class="s"&gt;"form.get('confirmPassword')?.errors"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;ng-container&lt;/span&gt; &lt;span class="na"&gt;*ngFor=&lt;/span&gt;&lt;span class="s"&gt;"let error of form.get('confirmPassword')?.errors | keyvalue"&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;*ngIf=&lt;/span&gt;&lt;span class="s"&gt;"error.key !== 'required'"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{ error.value }}&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/ng-container&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/mat-error&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/mat-form-field&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing the Validator
&lt;/h2&gt;

&lt;p&gt;Like other custom validators, it is easy to test multi-control custom validators. Writing unit tests for this validator helped me find and handle an edge case that I wasn’t handling initially also. Here are some of the example tests:&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="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;validFieldMatch() default field name&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;matchFieldValidator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;MatchFieldValidator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validFieldMatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;controlName&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;confirmControlName&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="kd"&gt;const&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;new&lt;/span&gt; &lt;span class="nc"&gt;FormGroup&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;controlName&lt;/span&gt;&lt;span class="p"&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="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;confirmControlName&lt;/span&gt;&lt;span class="p"&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="dl"&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;const&lt;/span&gt; &lt;span class="nx"&gt;controlName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;controlName&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;confirmControlName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;confirmControlName&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`should set control error as { confirmFieldRequired: 'Confirm Password is required.' } when value is an empty string`&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;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;controlName&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;confirmControlName&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;matchFieldValidator&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expectedValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;confirmFieldRequired&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Confirm Password is required.&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="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;confirmControlName&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expectedValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`should set control error as { fieldsMismatched: 'Password fields do not match.' } when values do not match`&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;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;controlName&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;password123!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;confirmControlName&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;password123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;matchFieldValidator&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expectedValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;fieldsMismatched&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Password fields do not match.&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="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;confirmControlName&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expectedValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`should set control error as null when values match`&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;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;controlName&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;password123!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;confirmControlName&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;password123!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;matchFieldValidator&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="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;controlName&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;confirmControlName&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`should set control error as null when control matches confirm after not matching`&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;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;controlName&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;password123!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;confirmControlName&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;password123!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;matchFieldValidator&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;controlName&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;password123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;matchFieldValidator&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;controlName&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;password123!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;matchFieldValidator&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="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;controlName&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;confirmControlName&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`should set control error as null when confirm matches control after not matching`&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;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;controlName&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;password123!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;confirmControlName&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;password123!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;matchFieldValidator&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;controlName&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;password123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;matchFieldValidator&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;confirmControlName&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;password123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;matchFieldValidator&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="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;controlName&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;confirmControlName&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&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;Custom validators are easy to make and very powerful. Since they can be made at any level of a reactive form, it is possible to make multi-control custom validators like this one that can interact with multiple controls. This helps developers craft highly reactive UI/UX for users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;The repository includes &lt;a href="https://github.com/Karvel/angular-password-strength/blob/main/src/app/infrastructure/utils/validators/match-field-validator.spec.ts" rel="noopener noreferrer"&gt;unit tests for the validator&lt;/a&gt; to help dial in the desired behavior. &lt;a href="https://github.com/Karvel/angular-password-strength" rel="noopener noreferrer"&gt;Here&lt;/a&gt; is the repository on GitHub, and &lt;a href="https://stackblitz.com/github/Karvel/angular-password-strength" rel="noopener noreferrer"&gt;here&lt;/a&gt; is a working demo of the code on StackBlitz. All of my posts on Angular are tagged and collected &lt;a href="https://elanna.me/tag/angular/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The post &lt;a href="https://elanna.me/blog/2021/06/creating-a-multi-control-custom-validator-in-angular/" rel="noopener noreferrer"&gt;Creating a Multi-Control Custom Validator in Angular&lt;/a&gt; appeared first on &lt;a href="https://elanna.me" rel="noopener noreferrer"&gt;Hapax Legomenon&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>customvalidator</category>
      <category>formvalidation</category>
      <category>reactiveforms</category>
    </item>
    <item>
      <title>Exploring Custom Form Validators In Angular</title>
      <dc:creator>Elanna Grossman</dc:creator>
      <pubDate>Wed, 16 Jun 2021 04:42:07 +0000</pubDate>
      <link>https://dev.to/karvel/exploring-custom-form-validators-in-angular-52fb</link>
      <guid>https://dev.to/karvel/exploring-custom-form-validators-in-angular-52fb</guid>
      <description>&lt;p&gt;I believe that the reactive form library in Angular is one of the most powerful tools in the framework. Developers can create performant and reactive forms that provide great UI/UX. One part of the reactive form toolkit that I think people often overlook is creating custom validators. In this article I show how to create a custom password field validator and how to use it.&lt;/p&gt;

&lt;p&gt;Reactive forms create objects that all inherit from the same &lt;a href="https://angular.io/api/forms/AbstractControl" rel="noopener noreferrer"&gt;&lt;code&gt;AbstractControl&lt;/code&gt;&lt;/a&gt;. The &lt;code&gt;AbstractControl&lt;/code&gt; has an &lt;a href="https://angular.io/api/forms/AbstractControl#errors" rel="noopener noreferrer"&gt;&lt;code&gt;errors&lt;/code&gt;&lt;/a&gt; object property, which is where I can get or set validation errors for the form or particular control. This &lt;code&gt;errors&lt;/code&gt; object contains key value pairs. When using the default built-in validation, these key value pairs are predefined with specific (often boolean) values. That means that I would need to evaluate the error value and decide what error message to show the user. However, it is possible to create &lt;a href="https://angular.io/guide/form-validation#defining-custom-validators" rel="noopener noreferrer"&gt;custom validators&lt;/a&gt;, and they can return key value pairs with error message values instead of booleans. This means that I can set up reusable validators to both perform validation, and handle setting up their own human-readable error messages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the Built-in Validators
&lt;/h2&gt;

&lt;p&gt;The built-in validators are powerful and easy to use. The &lt;a href="https://angular.io/api/forms/Validators#validators" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt; shows each one and how to use it. For this example, I want to add the following validation to the password field:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Make the field required.&lt;/li&gt;
&lt;li&gt;Require a minimum of 8 characters.&lt;/li&gt;
&lt;li&gt;Require at least one number.&lt;/li&gt;
&lt;li&gt;Require at least one special character.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In my sample register form, I could add four of the built-in validators to do this:&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;private&lt;/span&gt; &lt;span class="nf"&gt;createForm&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="kd"&gt;const&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;email&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="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;password&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="p"&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="nx"&gt;Validators&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;minLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&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="nf"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;-+_!@#$%^&amp;amp;*,.?&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&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="nf"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;0-9&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&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;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;form&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;Then I would bind the error properties to the template, and write messages based on which errors are active:&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;mat-form-field&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
    &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;
    &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;
    &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;
    &lt;span class="na"&gt;matInput&lt;/span&gt;
    &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Password"&lt;/span&gt;
    &lt;span class="na"&gt;formControlName=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;
  &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;mat-error&lt;/span&gt; &lt;span class="na"&gt;*ngIf=&lt;/span&gt;&lt;span class="s"&gt;"form.get('password')?.errors"&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;*ngIf=&lt;/span&gt;&lt;span class="s"&gt;"form.get('password')?.errors?.required"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      Password is required.
    &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;*ngIf=&lt;/span&gt;&lt;span class="s"&gt;"form.get('password')?.errors?.minlength"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      Password must be at least 8 characters.
    &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;*ngIf=&lt;/span&gt;&lt;span class="s"&gt;"form.get('password')?.errors?.pattern?.requiredPattern === '/[-+_!@#$%^&amp;amp;*,.?]/'"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      Password requires at least one special character.
    &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;*ngIf=&lt;/span&gt;&lt;span class="s"&gt;"form.get('password')?.errors?.pattern?.requiredPattern === '/[0-9]/'"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      Password requires at least one number.
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/mat-error&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/mat-form-field&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works fine, and gives the user reactive feedback on if their password meets the requirements. There are two reasons that I prefer using custom validators, however. The first is that the built-in validators only handle the most common use cases. The second is that I like to consolidate where I create validation error messages. The built-in validators provide me the tools I need to write error messages, but the properties are not readable by regular users. So I need to write the messages by hand, it makes this code hard to reuse. It would be nice to have code where I can keep the responsibility of creating human-readable error messages, and handle any complex validation logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a Custom PasswordValidator
&lt;/h2&gt;

&lt;p&gt;Custom form validators are simply functions. I prefer to put them in classes, and I usually make them static because of how straightforward the logic is to write. Custom validators act on the passed in &lt;code&gt;AbstractControl&lt;/code&gt;. This is where the I can evaluate whatever I want about the &lt;code&gt;AbstractControl&lt;/code&gt;. Custom validators expect one of two values returned. &lt;code&gt;null&lt;/code&gt; means that validation passed, and there are no errors. &lt;a href="https://angular.io/api/forms/ValidationErrors" rel="noopener noreferrer"&gt;&lt;code&gt;ValidationErrors&lt;/code&gt;&lt;/a&gt; is just a wrapping for a key value pair and is how I return error messages. These error messages can be static and hard coded, or dynamic. Below I show an example for some simple validation I could do for creating a new password:&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;PasswordValidator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="nf"&gt;validPassword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isRequired&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&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="nx"&gt;ValidatorFn&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return &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;AbstractControl&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;ValidationErrors&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&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="o"&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;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;isRequired&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;invalidPassword&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Password is required.`&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&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="nx"&gt;control&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;invalidPassword&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Password is too short.`&lt;/span&gt; &lt;span class="p"&gt;};&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;CONSTANTS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SYMBOL_REGEX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&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;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;invalidPassword&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Password requires at least one special character.`&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;CONSTANTS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DIGIT_REGEX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&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;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;invalidPassword&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Password requires at least one numeric character.`&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="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&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;This custom password validator checks for the same four requirements that I listed separately with the built-in validators. If I know that I will want to always check for those four requirements, it is nice to have them collected in a single method.&lt;/p&gt;

&lt;p&gt;I like putting a amount of logic to handle if the field is required or not here (as seen with &lt;code&gt;if (!control.value)&lt;/code&gt;) so I don’t need to bind multiple validators to a single control, but that’s personal preference. I moved the regular expressions to a constants file and named them since I can find them hard to read. The default behavior is that form validators update whenever a user changes a value on the form. However, it is possible to do something like add a debounce to tweak how often it fires.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the Validator
&lt;/h2&gt;

&lt;p&gt;Custom validators are easy to use. In the component where I set up my reactive form, I can use my custom validators at any level of the form. This means that I can apply the validator to a &lt;a href="https://angular.io/api/forms/FormControl" rel="noopener noreferrer"&gt;&lt;code&gt;FormControl&lt;/code&gt;&lt;/a&gt;, a &lt;a href="https://angular.io/api/forms/FormArray" rel="noopener noreferrer"&gt;&lt;code&gt;FormArray&lt;/code&gt;&lt;/a&gt;, or an entire &lt;a href="https://angular.io/api/forms/FormGroup" rel="noopener noreferrer"&gt;&lt;code&gt;FormGroup&lt;/code&gt;&lt;/a&gt;. In a future post I will show how to create a validator that can evaluate and compare multiple control values. Here though, I just need to pass the validator to the FormControl value I am creating. I am using &lt;a href="https://angular.io/api/forms/FormGroup" rel="noopener noreferrer"&gt;&lt;code&gt;FormBuilder&lt;/code&gt;&lt;/a&gt; in this example:&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;private&lt;/span&gt; &lt;span class="nf"&gt;createForm&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="kd"&gt;const&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;email&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="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;password&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="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="nf"&gt;compose&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;PasswordValidator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validPassword&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="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;form&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;Since I made my method static, I invoke it as &lt;code&gt;PasswordValidator.validPassword(true)&lt;/code&gt;. If I had used a service, I would inject like &lt;code&gt;this.passwordValidator.validPassword(true)&lt;/code&gt; instead. As I like to handle if it’s required or not with a single validator, I pass true to the method (again, this is just personal preference and not required when making a custom validator).&lt;/p&gt;

&lt;p&gt;Now that I have moved the logic of figuring out what message to show to the user out of the template, I can simplify what is in the template a lot:&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;mat-form-field&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
    &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;
    &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;
    &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;
    &lt;span class="na"&gt;matInput&lt;/span&gt;
    &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Password"&lt;/span&gt;
    &lt;span class="na"&gt;formControlName=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;
  &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;mat-error&lt;/span&gt; &lt;span class="na"&gt;*ngIf=&lt;/span&gt;&lt;span class="s"&gt;"form.get('password')?.errors"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ng-container&lt;/span&gt; &lt;span class="na"&gt;*ngFor=&lt;/span&gt;&lt;span class="s"&gt;"let error of form.get('password')?.errors | keyvalue"&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;*ngIf=&lt;/span&gt;&lt;span class="s"&gt;"error.key !== 'required'"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{ error.value }}&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ng-container&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/mat-error&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/mat-form-field&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I added the second check of &lt;code&gt;error.key !== 'required'&lt;/code&gt; here to skip over the required error that Angular adds automatically when I add the &lt;code&gt;required&lt;/code&gt; attribute to the input element. For non-example projects, I normally use a custom pipe to handle traversing the errors object rather than the &lt;a href="https://angular.io/api/common/KeyValuePipe" rel="noopener noreferrer"&gt;&lt;code&gt;keyvalue&lt;/code&gt;&lt;/a&gt; pipe here. I’ll explain that in more detail in a follow up article.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the Validator
&lt;/h2&gt;

&lt;p&gt;It is really easy to write unit tests for these kinds of validators. This way I can write custom logic and feel confident that it does what I expect and that I am handling edge cases. Below are some example test snippets, and the rest are &lt;a href="https://github.com/Karvel/angular-password-strength/blob/main/src/app/infrastructure/utils/validators/password-validator.spec.ts" rel="noopener noreferrer"&gt;here&lt;/a&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="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`should return null if value matches RegEx`&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;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;passwordControl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;passwordTest1!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;passwordValidator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;passwordControl&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`should return { invalidPassword: 'Password is too short.' } when value is too short`&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;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;passwordControl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expectedValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;invalidPassword&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Password is too short.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;passwordValidator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;passwordControl&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expectedValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`should return { invalidPassword: 'Password requires at least one special character.' } when missing special characters`&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;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;passwordControl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;passwordTest1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expectedValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;invalidPassword&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Password requires at least one special character.&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="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;passwordValidator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;passwordControl&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expectedValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`should return { invalidPassword: 'Password requires at least one numeric character.' } when missing numeric characters`&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;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;passwordControl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;passwordTest!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expectedValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;invalidPassword&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Password requires at least one numeric character.&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="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;passwordValidator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;passwordControl&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expectedValue&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;Between creating custom validators like this and then &lt;a href="https://elanna.me/blog/2021/06/making-a-password-strength-component-in-angular/" rel="noopener noreferrer"&gt;listening to the form state&lt;/a&gt;, developers can create reactive and engaging content for users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;The repository includes &lt;a href="https://github.com/Karvel/angular-password-strength/blob/main/src/app/infrastructure/utils/validators/password-validator.spec.ts" rel="noopener noreferrer"&gt;unit tests for the validator&lt;/a&gt; to help dial in the desired behavior. &lt;a href="https://github.com/Karvel/angular-password-strength" rel="noopener noreferrer"&gt;Here&lt;/a&gt; is the repository on GitHub, and &lt;a href="https://stackblitz.com/github/Karvel/angular-password-strength" rel="noopener noreferrer"&gt;here&lt;/a&gt; is a working demo of the code on StackBlitz. All of my posts on Angular are tagged and collected &lt;a href="https://elanna.me/tag/angular/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The post &lt;a href="https://elanna.me/blog/2021/06/exploring-custom-form-validators-in-angular/" rel="noopener noreferrer"&gt;Exploring Custom Form Validators In Angular&lt;/a&gt; appeared first on &lt;a href="https://elanna.me" rel="noopener noreferrer"&gt;Hapax Legomenon&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>customvalidator</category>
      <category>formvalidation</category>
      <category>reactiveforms</category>
    </item>
    <item>
      <title>Making a Password Strength Component in Angular</title>
      <dc:creator>Elanna Grossman</dc:creator>
      <pubDate>Wed, 09 Jun 2021 07:28:38 +0000</pubDate>
      <link>https://dev.to/karvel/making-a-password-strength-component-in-angular-40go</link>
      <guid>https://dev.to/karvel/making-a-password-strength-component-in-angular-40go</guid>
      <description>&lt;h2&gt;
  
  
  The Situation
&lt;/h2&gt;

&lt;p&gt;Reactive Forms in Angular are incredibly powerful and let developers design experiences that provide immediate feedback to users. In this post I show how to use RxJS in a reactive form to provide useful UI feedback for a common use case: creating a password strength component to show password indicators to users creating passwords.&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%2Felanna.me%2Fimages%2Fupload%2FPeek-2021-06-15-01-43.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%2Felanna.me%2Fimages%2Fupload%2FPeek-2021-06-15-01-43.gif" alt="Password Strength Component animation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Register Form
&lt;/h2&gt;

&lt;p&gt;First, I decided to create distinct check box form controls for each password requirement and strength indicator. This lets me update the control for each indicator independently. I set the controls to disabled, so that the values can only be triggered programmatically, rather than directly by the user. The requirement indicator values start as false since none of them should be checked at first. I like to set this up in the parent component where I set up my reactive form. That is because I believe the parent component should be the source of truth for the controls in a form.&lt;/p&gt;

&lt;p&gt;I go over creating custom validators like &lt;code&gt;PasswordValidator.validPassword()&lt;/code&gt; in a &lt;a href="https://elanna.me/blog/2021/06/exploring-custom-form-validators-in-angular/" rel="noopener noreferrer"&gt;follow up post&lt;/a&gt;. For the sake of simplicity in this example, I combined using both password requirement indicators with a validator and the password strength indicator. In a real world scenario, I don’t think I would use the validator with the password strength indicator. To achieve that effect, it would be easy enough to remove &lt;code&gt;Validators.compose([PasswordValidator.validPassword(true)]),&lt;/code&gt; below:&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;private&lt;/span&gt; &lt;span class="nf"&gt;createForm&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="kd"&gt;const&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;email&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="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;password&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="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="nf"&gt;compose&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;PasswordValidator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validPassword&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="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;passwordMin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&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="na"&gt;disabled&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="na"&gt;passwordDigit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&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="na"&gt;disabled&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="na"&gt;passwordSpecial&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&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="na"&gt;disabled&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="na"&gt;passwordSlider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&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="na"&gt;disabled&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="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;form&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;The register template is very straightforward. I moved the password strength part to its own component to make it easier to reuse. I pass the form instance with an input binding to &lt;code&gt;app-password-strength&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;"register-field"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;mat-form-field&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
        &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
        &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
        &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;
        &lt;span class="na"&gt;matInput&lt;/span&gt;
        &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Email"&lt;/span&gt;
        &lt;span class="na"&gt;formControlName=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;
      &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/mat-form-field&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&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;mat-form-field&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
        &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;
        &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;
        &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;
        &lt;span class="na"&gt;matInput&lt;/span&gt;
        &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Password"&lt;/span&gt;
        &lt;span class="na"&gt;formControlName=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;
      &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/mat-form-field&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;button&lt;/span&gt;
    &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"submit-button"&lt;/span&gt;
    &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;
    &lt;span class="na"&gt;mat-raised-button&lt;/span&gt;
    &lt;span class="na"&gt;color=&lt;/span&gt;&lt;span class="s"&gt;"primary"&lt;/span&gt;
    &lt;span class="na"&gt;[disabled]=&lt;/span&gt;&lt;span class="s"&gt;"!form.valid"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    Register
  &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;app-password-strength&lt;/span&gt; &lt;span class="na"&gt;[form]=&lt;/span&gt;&lt;span class="s"&gt;"form"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/app-password-strength&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The PasswordStrengthComponent
&lt;/h2&gt;

&lt;p&gt;In &lt;code&gt;PasswordStrengthComponent&lt;/code&gt;, most of the work happens in &lt;code&gt;setupConditionalValidators()&lt;/code&gt;. Reactive forms can expose observable streams for individual form controls or the form itself. There are two stream choices: &lt;a href="https://angular.io/api/forms/AbstractControl#statusChanges" rel="noopener noreferrer"&gt;&lt;code&gt;statusChanges&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://angular.io/api/forms/AbstractControl#valueChanges" rel="noopener noreferrer"&gt;&lt;code&gt;valueChanges&lt;/code&gt;&lt;/a&gt;. Here, I use &lt;code&gt;valueChanges&lt;/code&gt; because I want to update the password requirement and strength indicators as specific values change. &lt;code&gt;setupConditionalValidators()&lt;/code&gt; creates a subscription that listens to the &lt;code&gt;valueChanges&lt;/code&gt; stream on the password form control. This way it can listen to the values emitted by the password field and update the requirement and strength indicators for each value.&lt;/p&gt;

&lt;p&gt;The method then calls &lt;code&gt;setIndicatorValues()&lt;/code&gt;, which sets each of the indicator values based on simple checks. As I find some regular expressions hard to read, I moved them to a constants file and gave them descriptive names. The subscription needs to be actively managed, so I added it to an array that is managed by &lt;code&gt;ngOnDestroy&lt;/code&gt;. I prefer managing subscriptions this way instead of by using &lt;a href="https://www.learnrxjs.io/learn-rxjs/operators/filtering/takeuntil" rel="noopener noreferrer"&gt;&lt;code&gt;takeUntil()&lt;/code&gt;&lt;/a&gt;. &lt;code&gt;takeUntil()&lt;/code&gt; marks subscriptions as complete as a side effect, and I prefer not to do that. There is a little more code in the component like the control getter methods that I removed here for brevity.&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;public&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="nf"&gt;setInitialIndicatorValues&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="nf"&gt;setupConditionalValidators&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;setIndicatorValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;controlValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;passwordSliderMinValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;passwordSliderSpecialValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;passwordSliderDigitValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&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="nx"&gt;controlValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&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;passwordMin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setValue&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="nx"&gt;passwordSliderMinValue&lt;/span&gt; &lt;span class="o"&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;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;passwordMin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setValue&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="nx"&gt;passwordSliderMinValue&lt;/span&gt; &lt;span class="o"&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CONSTANTS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SYMBOL_REGEX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;controlValue&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;passwordSpecial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setValue&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="nx"&gt;passwordSliderSpecialValue&lt;/span&gt; &lt;span class="o"&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;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;passwordSpecial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setValue&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="nx"&gt;passwordSliderSpecialValue&lt;/span&gt; &lt;span class="o"&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CONSTANTS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DIGIT_REGEX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;controlValue&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;passwordDigit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setValue&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="nx"&gt;passwordSliderDigitValue&lt;/span&gt; &lt;span class="o"&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;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;passwordDigit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setValue&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="nx"&gt;passwordSliderDigitValue&lt;/span&gt; &lt;span class="o"&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;passwordSlider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;passwordSliderMinValue&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
      &lt;span class="nx"&gt;passwordSliderSpecialValue&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
      &lt;span class="nx"&gt;passwordSliderDigitValue&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;switch &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;passwordSlider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;0&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;strengthHint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Weak&lt;/span&gt;&lt;span class="dl"&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;strengthHint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;red&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;1&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;strengthHint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Okay&lt;/span&gt;&lt;span class="dl"&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;strengthHint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;orange&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;2&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;strengthHint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Good&lt;/span&gt;&lt;span class="dl"&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;strengthHint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;yellow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;3&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;strengthHint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Strong&lt;/span&gt;&lt;span class="dl"&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;strengthHint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;green&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;break&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="cm"&gt;/** Set the indicator values based on the initial password form control value. */&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;setInitialIndicatorValues&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="nf"&gt;setIndicatorValues&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;password&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/** Listens to the password input in the form and updates the requirements list. */&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;setupConditionalValidators&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="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;passwordControlSubscription&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Subscription&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;password&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;valueChanges&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;controlValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setIndicatorValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;controlValue&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;subscriptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;passwordControlSubscription&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;The constants file with the regular expressions looks like this:&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="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Constants&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;DIGIT_REGEX&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;SYMBOL_REGEX&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;RegExp&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;const&lt;/span&gt; &lt;span class="nx"&gt;CONSTANTS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Constants&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;DIGIT_REGEX&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;0-9&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;SYMBOL_REGEX&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;-+_!@#$%^&amp;amp;*,.?&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&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;The password strength template just contains the styling for the component along with the form controls:&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;"password-container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Password Requirements&lt;span class="nt"&gt;&amp;lt;/h2&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;"password-requirements-row"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;mat-checkbox&lt;/span&gt; &lt;span class="na"&gt;formControlName=&lt;/span&gt;&lt;span class="s"&gt;"passwordMin"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        Password length
      &lt;span class="nt"&gt;&amp;lt;/mat-checkbox&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;mat-checkbox&lt;/span&gt; &lt;span class="na"&gt;formControlName=&lt;/span&gt;&lt;span class="s"&gt;"passwordDigit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        Contains at least 1 digit
      &lt;span class="nt"&gt;&amp;lt;/mat-checkbox&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;mat-checkbox&lt;/span&gt; &lt;span class="na"&gt;formControlName=&lt;/span&gt;&lt;span class="s"&gt;"passwordSpecial"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        Contains at least 1 symbol
      &lt;span class="nt"&gt;&amp;lt;/mat-checkbox&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;mat-slider&lt;/span&gt;
      &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"password-strength"&lt;/span&gt;
      &lt;span class="na"&gt;[max]=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt;
      &lt;span class="na"&gt;[min]=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;
      &lt;span class="na"&gt;formControlName=&lt;/span&gt;&lt;span class="s"&gt;"passwordSlider"&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/mat-slider&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;mat-hint&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"hint-text"&lt;/span&gt; &lt;span class="na"&gt;[ngStyle]=&lt;/span&gt;&lt;span class="s"&gt;"{ color: strengthHint.color }"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{ strengthHint.message }}&lt;span class="nt"&gt;&amp;lt;/mat-hint&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I made a simple UI using checkboxes for the password strength indicators. Using the password &lt;code&gt;valueChanges&lt;/code&gt; subscription above though, it would be easy to design any number of highly reactive UIs to help the user as they set up a password.&lt;/p&gt;

&lt;p&gt;I made this as a simple tutorial example. If I were to use this in production, I would also pass a config object to &lt;code&gt;PasswordStrengthComponent&lt;/code&gt; which gives the names for the indicator controls. That way, I could have that implementation come from the parent component.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;The repository includes &lt;a href="https://github.com/Karvel/angular-password-strength/blob/main/src/app/infrastructure/shared/components/password-strength/password-strength.component.spec.ts" rel="noopener noreferrer"&gt;unit tests for the indicator controls&lt;/a&gt; to help dial in the desired behavior. &lt;a href="https://github.com/Karvel/angular-password-strength" rel="noopener noreferrer"&gt;Here&lt;/a&gt; is the repository on GitHub, and &lt;a href="https://stackblitz.com/github/Karvel/angular-password-strength" rel="noopener noreferrer"&gt;here&lt;/a&gt; is a working demo of the code on StackBlitz. All of my posts on Angular are tagged and collected &lt;a href="https://elanna.me/tag/angular/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The post &lt;a href="https://elanna.me/blog/2021/06/making-a-password-strength-component-in-angular/" rel="noopener noreferrer"&gt;Making a Password Strength Component in Angular&lt;/a&gt; appeared first on &lt;a href="https://elanna.me" rel="noopener noreferrer"&gt;Hapax Legomenon&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>reactiveforms</category>
      <category>rxjs</category>
    </item>
    <item>
      <title>Making a LocaleUpperCasePipe for Angular</title>
      <dc:creator>Elanna Grossman</dc:creator>
      <pubDate>Mon, 22 Mar 2021 11:57:04 +0000</pubDate>
      <link>https://dev.to/karvel/making-a-localeuppercasepipe-for-angular-1o57</link>
      <guid>https://dev.to/karvel/making-a-localeuppercasepipe-for-angular-1o57</guid>
      <description>&lt;p&gt;&lt;a href="https://www.flickr.com/photos/jadeilyn/2290027938/" rel="noopener noreferrer"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---Td4U5aY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://live.staticflickr.com/2262/2290027938_268e2f34c4_z.jpg" alt="Métro graffiti" width="615" height="462"&gt;&lt;/a&gt;&lt;/p&gt;
Paris Métro Graffiti



&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Angular uses pipes to help transform how data appears in the template. It provides a number of &lt;a href="https://angular.io/api?type=pipe" rel="noopener noreferrer"&gt;built-in pipes&lt;/a&gt; like &lt;code&gt;DatePipe&lt;/code&gt; and &lt;code&gt;UpperCasePipe&lt;/code&gt;. However, while working on a localization feature for a work project, a coworker pointed out that &lt;code&gt;UpperCasePipe&lt;/code&gt; uses &lt;a href="https://github.com/angular/angular/blob/master/packages/common/src/pipes/case_conversion_pipes.ts#L100" rel="noopener noreferrer"&gt;&lt;code&gt;toUpperCase()&lt;/code&gt; under the hood.&lt;/a&gt; &lt;code&gt;toUpperCase()&lt;/code&gt; is not locale aware, and will fail at capitalizing letters for certain locales correctly, &lt;a href="http://www.moserware.com/2008/02/does-your-code-pass-turkey-test.html" rel="noopener noreferrer"&gt;namely Turkish&lt;/a&gt;. Since it is easy to create custom pipes in Angular, I decided to create one that is locale-aware. In this post, I will share the code for the LocaleUpperCasePipe, explain how to use it, and provide links to the repository and a demo.&lt;/p&gt;

&lt;p&gt;When given a lowercase value like “ılıman ilik”, the &lt;code&gt;UpperCasePipe&lt;/code&gt; will return &lt;code&gt;ILIMAN ILIK&lt;/code&gt;, which is wrong. The correct result should be &lt;code&gt;ILIMAN İLİK&lt;/code&gt;. (I apologize for the silly Turkish. I am not a speaker and I was looking for an easy test value).&lt;/p&gt;

&lt;h2&gt;
  
  
  The LocaleUpperCasePipe
&lt;/h2&gt;

&lt;p&gt;Pipes can take arguments. I have designed this one to accept an optional locale argument. If it receives no argument, it uses a fallback value of a default language.&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;Pipe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PipeTransform&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Constants&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;../../utils/constants&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="nd"&gt;Pipe&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;localeuppercase&lt;/span&gt;&lt;span class="dl"&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;LocaleUpperCasePipe&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;PipeTransform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&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="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&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;typeof&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&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;return&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;language&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultLanguage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLocaleUpperCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLocaleUpperCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultLanguage&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;I designed the pipe to prefer a passed-in locale argument to the fallback value. If the locale argument is an invalid value, the pipe catches the error, outputs it as a warning to the console, and falls back to using the default locale value. There is a default language value so in cases where the default language would be something like Turkish, it can be set, and then the pipe can be used without needing to pass in an argument (unless capitalizing a word from a different language).&lt;/p&gt;

&lt;p&gt;The constants file looks like this:&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="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;IConstant&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;defaultLanguage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;const&lt;/span&gt; &lt;span class="nx"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IConstant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;defaultLanguage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en-US&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Using the Pipe
&lt;/h2&gt;

&lt;p&gt;Here is an example of using the pipe with no argument:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="nx"&gt;testValue&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;localeuppercase&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here is the pipe with a locale argument:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="nx"&gt;testValue&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="na"&gt;localeuppercase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tr-TR&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;The repository includes &lt;a href="https://github.com/Karvel/angular-locale-uppercase-pipe/blob/development/src/app/infrastructure/shared/pipes/locale-upper-case.pipe.spec.ts" rel="noopener noreferrer"&gt;unit tests for the pipe&lt;/a&gt; to help dial in the desired behavior, and shows &lt;a href="https://github.com/Karvel/angular-locale-uppercase-pipe/blob/development/src/app/features/sandbox/test/test.component.html" rel="noopener noreferrer"&gt;multiple examples&lt;/a&gt; of the pipe being used.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Karvel/angular-locale-uppercase-pipe" rel="noopener noreferrer"&gt;Here&lt;/a&gt; is the repository on GitHub, and &lt;a href="https://stackblitz.com/github/Karvel/angular-locale-uppercase-pipe" rel="noopener noreferrer"&gt;here&lt;/a&gt; is a working demo of the code on StackBlitz. All of my posts on Angular are tagged and collected &lt;a href="https://elanna.me/tag/angular" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The post &lt;a href="https://elanna.me/blog/2021/03/making-a-localeuppercasepipe-for-angular" rel="noopener noreferrer"&gt;Making a LocaleUpperCasePipe for Angular&lt;/a&gt; appeared first on &lt;a href="https://elanna.me" rel="noopener noreferrer"&gt;Hapax Legomenon&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>angularpipe</category>
      <category>tolocaleuppercase</category>
      <category>pipe</category>
    </item>
  </channel>
</rss>
