<?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: Danny Koppenhagen</title>
    <description>The latest articles on DEV Community by Danny Koppenhagen (@dkoppenhagen).</description>
    <link>https://dev.to/dkoppenhagen</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%2F201065%2F4f8ddac8-f22d-42d2-9d1f-b31639e7661b.jpg</url>
      <title>DEV Community: Danny Koppenhagen</title>
      <link>https://dev.to/dkoppenhagen</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dkoppenhagen"/>
    <language>en</language>
    <item>
      <title>When Your Live Region Isn't Live: Fixing aria-live in Angular, React, and Vue</title>
      <dc:creator>Danny Koppenhagen</dc:creator>
      <pubDate>Tue, 04 Nov 2025 22:11:03 +0000</pubDate>
      <link>https://dev.to/dkoppenhagen/when-your-live-region-isnt-live-fixing-aria-live-in-angular-react-and-vue-1g0j</link>
      <guid>https://dev.to/dkoppenhagen/when-your-live-region-isnt-live-fixing-aria-live-in-angular-react-and-vue-1g0j</guid>
      <description>&lt;p&gt;You've built a modern single-page application with dynamic content alerts and live tickers - of course: with accessibility in mind. Therefore, you've added &lt;code&gt;aria-live&lt;/code&gt; regions so screen reader users can hear what's changing. A success message here, a toast there. It &lt;em&gt;should&lt;/em&gt; just work.&lt;/p&gt;

&lt;p&gt;But when you test it with a screen reader… nothing. Silence. Your "live" region isn't so live after all.&lt;/p&gt;

&lt;p&gt;If that sounds familiar, you're not alone. Accessibility professionals and framework developers alike run into this issue across Angular, Vue, React and other frameworks. The problem isn't your markup — it's how these frameworks manage the DOM.&lt;/p&gt;

&lt;p&gt;Modern SPA frameworks do amazing things behind the scenes: they mount, unmount, and patch elements as state changes. Unfortunately, screen readers don't see your reactive data; they only notice &lt;em&gt;actual DOM mutations&lt;/em&gt;. When the element holding your &lt;code&gt;aria-live&lt;/code&gt; attribute is recreated or removed, assistive technologies lose track — and your updates are never announced.&lt;/p&gt;

&lt;p&gt;In this post, we'll break down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why live regions may fail in your SPAs&lt;/li&gt;
&lt;li&gt;The difference between &lt;strong&gt;polite&lt;/strong&gt; and &lt;strong&gt;assertive&lt;/strong&gt; announcements&lt;/li&gt;
&lt;li&gt;What the &lt;code&gt;aria-relevant&lt;/code&gt; and &lt;code&gt;aria-atomic&lt;/code&gt; attributes actually doing&lt;/li&gt;
&lt;li&gt;Two reliable solutions: &lt;strong&gt;local&lt;/strong&gt; vs. &lt;strong&gt;global&lt;/strong&gt; live regions&lt;/li&gt;
&lt;li&gt;Concrete implementations in &lt;strong&gt;Angular&lt;/strong&gt;, &lt;strong&gt;Vue&lt;/strong&gt;, and &lt;strong&gt;React&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the end, you'll know how to make sure your live regions stay &lt;em&gt;truly live&lt;/em&gt; — no matter what your framework is doing behind the scenes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Understanding Why Live Region Breaks in SPAs
&lt;/h2&gt;

&lt;p&gt;At its core, an &lt;code&gt;aria-live&lt;/code&gt; region is easily explained:&lt;br&gt;
it tells assistive technologies like screen readers,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Hey, whenever this content changes, read it out loud."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That sounds straightforward — but modern frameworks make this promise surprisingly hard to keep.&lt;/p&gt;

&lt;p&gt;When you update a variable in your app (like &lt;code&gt;message = 'Saved!'&lt;/code&gt;), the screen reader doesn't care. It only reacts to &lt;strong&gt;changes in the actual DOM text&lt;/strong&gt; inside an element that already has &lt;code&gt;aria-live&lt;/code&gt; on it. If that element doesn't exist yet, or is about to be replaced, your announcement vanishes into thin air. In SPAs, it's common to show or hide UI elements conditionally:&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="c"&gt;&amp;lt;!-- Angular --&amp;gt;&lt;/span&gt;
@if (showMessage) {
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;aria-live=&lt;/span&gt;&lt;span class="s"&gt;"polite"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{ message }}&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
}

&lt;span class="c"&gt;&amp;lt;!-- Vue --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"showMessage"&lt;/span&gt; &lt;span class="na"&gt;aria-live=&lt;/span&gt;&lt;span class="s"&gt;"polite"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{ message }}&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- React --&amp;gt;&lt;/span&gt;
{showMessage &lt;span class="err"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;aria-live=&lt;/span&gt;&lt;span class="s"&gt;"polite"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{message}&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That looks fine — but when &lt;code&gt;showMessage&lt;/code&gt; changes from &lt;code&gt;false&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt;, the framework &lt;strong&gt;creates a brand new element in the DOM&lt;/strong&gt;. From the screen reader's perspective, that's just &lt;em&gt;a new element appearing&lt;/em&gt;, not an update in a live region it's been tracking. And since the text &lt;code&gt;"Saved!"&lt;/code&gt; is already present when the node appears, the screen reader never gets a "text change" event — so it says nothing.&lt;/p&gt;

&lt;p&gt;So, how can we fix it? To make &lt;code&gt;aria-live&lt;/code&gt; work reliably, the element:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Must &lt;strong&gt;always exist in the DOM&lt;/strong&gt; (no conditional rendering), and&lt;/li&gt;
&lt;li&gt;Must have &lt;strong&gt;its text content changed dynamically&lt;/strong&gt;, not replaced by a new node.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's why we'll look at two approaches next:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Local live regions that stay mounted&lt;/li&gt;
&lt;li&gt;A global announcer that's always present&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But before that, let's clarify three critical ARIA attributes that often confuse developers: &lt;code&gt;aria-live&lt;/code&gt;'s &lt;strong&gt;politeness levels&lt;/strong&gt;, and its lesser-known partners, &lt;code&gt;aria-relevant&lt;/code&gt; and &lt;code&gt;aria-atomic&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding ARIA Attributes for dynamic announcements
&lt;/h2&gt;

&lt;p&gt;Let's have a short look at the three Attributes &lt;code&gt;aria-live&lt;/code&gt;, &lt;code&gt;aria-relevant&lt;/code&gt; and &lt;code&gt;aria-atomic&lt;/code&gt; and how they relate to each other.&lt;/p&gt;

&lt;h3&gt;
  
  
  Polite vs. Assertive — Choosing the Right "Voice"
&lt;/h3&gt;

&lt;p&gt;The Attribute &lt;code&gt;aria-live&lt;/code&gt; supports three "politeness" levels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;aria-live="off"&lt;/code&gt;&lt;/strong&gt; (default) Disables live region announcements entirely. Use this to temporarily silence a region or explicitly mark static content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;aria-live="polite"&lt;/code&gt;&lt;/strong&gt; Screen readers will wait until the user is idle before announcing changes. Use this for non-urgent updates — success toasts, progress updates, chat messages, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;aria-live="assertive"&lt;/code&gt;&lt;/strong&gt; Screen readers will &lt;em&gt;interrupt&lt;/em&gt; what they're currently reading to announce the change immediately. Use this sparingly, only for critical messages like errors or important alerts that require immediate attention.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Choosing between them is less about importance and more about &lt;em&gt;urgency&lt;/em&gt;. Overusing &lt;code&gt;assertive&lt;/code&gt; announcements can make your app feel chaotic or even hostile to users relying on assistive tech. A good rule of thumb:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Use &lt;code&gt;polite&lt;/code&gt; for 90% of updates, &lt;code&gt;assertive&lt;/code&gt; for things that truly can't wait, and &lt;code&gt;off&lt;/code&gt; when you need to temporarily disable announcements or when your whole page is clearly only displaying live messages which the user is aware of.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;aria-relevant&lt;/code&gt; — Controlling &lt;em&gt;What&lt;/em&gt; Triggers an Announcement
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;aria-relevant&lt;/code&gt; attribute refines what types of changes should be announced. It accepts values like &lt;code&gt;additions&lt;/code&gt;, &lt;code&gt;removals&lt;/code&gt;, &lt;code&gt;text&lt;/code&gt;, or &lt;code&gt;all&lt;/code&gt;. For most live regions, the default (&lt;code&gt;aria-relevant="additions text"&lt;/code&gt;) is ideal — it announces when new content is added or existing text changes.&lt;/p&gt;

&lt;p&gt;However, if you have a region where elements are frequently added and removed (like a list of active users or temporary notifications), you might want to control what triggers announcements:&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="c"&gt;&amp;lt;!-- Only announce when items are added, ignore removals --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;ul&lt;/span&gt; &lt;span class="na"&gt;aria-live=&lt;/span&gt;&lt;span class="s"&gt;"polite"&lt;/span&gt; &lt;span class="na"&gt;aria-relevant=&lt;/span&gt;&lt;span class="s"&gt;"additions"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;User Alice joined&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;User Bob joined&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- Announces "User Bob joined" when added, silent when removed --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also combine values for fine control:&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;aria-live=&lt;/span&gt;&lt;span class="s"&gt;"assertive"&lt;/span&gt; &lt;span class="na"&gt;aria-relevant=&lt;/span&gt;&lt;span class="s"&gt;"additions removals text"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Error occurred&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;aria-atomic&lt;/code&gt; — Controlling &lt;em&gt;How Much&lt;/em&gt; Gets Announced
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;aria-atomic&lt;/code&gt; attribute determines whether the screen reader should announce only the changed part of a live region or the entire content.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;aria-atomic="false"&lt;/code&gt;&lt;/strong&gt; (default) Only announces the specific text that changed. Good for regions where you append new content (like chat messages or logs).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;aria-atomic="true"&lt;/code&gt;&lt;/strong&gt; Announces the entire content of the live region, even if only part of it changed. Essential for regions where the full context matters (like status messages or form validation summaries).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Consider this example:&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="c"&gt;&amp;lt;!-- Without aria-atomic (default: false) --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;aria-live=&lt;/span&gt;&lt;span class="s"&gt;"polite"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;Items in cart: &lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;3&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;&amp;lt;!-- Only "3" gets announced when updated --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- With aria-atomic="true" --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;aria-live=&lt;/span&gt;&lt;span class="s"&gt;"polite"&lt;/span&gt; &lt;span class="na"&gt;aria-atomic=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;Items in cart: &lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;3&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;&amp;lt;!-- "Items in cart: 3" gets announced when updated --&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;p&gt;For most status messages and notifications, &lt;code&gt;aria-atomic="true"&lt;/code&gt; provides better context.&lt;/p&gt;

&lt;p&gt;For chat messages, you'd typically use &lt;code&gt;aria-live="polite"&lt;/code&gt; with &lt;code&gt;aria-atomic="false"&lt;/code&gt; so each new message is announced individually without interrupting the user:&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="c"&gt;&amp;lt;!-- Chat messages example --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;aria-live=&lt;/span&gt;&lt;span class="s"&gt;"polite"&lt;/span&gt; &lt;span class="na"&gt;aria-atomic=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;Alice: Hello!&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;Bob: Hi there!&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- Only "Bob: Hi there!" gets announced when added --&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;p&gt;In short:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;aria-live&lt;/code&gt;&lt;/strong&gt; defines &lt;em&gt;when&lt;/em&gt; to speak (or not at all with &lt;code&gt;off&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;aria-relevant&lt;/code&gt;&lt;/strong&gt; defines &lt;em&gt;what&lt;/em&gt; to speak&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;aria-atomic&lt;/code&gt;&lt;/strong&gt; defines &lt;em&gt;how much&lt;/em&gt; to speak&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Together, they let you tune your live regions for exactly the right balance of awareness and calm.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Two Main Solutions
&lt;/h2&gt;

&lt;p&gt;Once you understand &lt;em&gt;why&lt;/em&gt; &lt;code&gt;aria-live&lt;/code&gt; fails in SPAs, the fix becomes much clearer. There are essentially &lt;strong&gt;two reliable strategies&lt;/strong&gt; — and which one you choose depends on your use case.&lt;/p&gt;

&lt;h3&gt;
  
  
  Local Live Regions
&lt;/h3&gt;

&lt;p&gt;If you only need to announce updates inside a specific component — say, a chat window, a progress indicator, or a status label — a &lt;strong&gt;local live region&lt;/strong&gt; can work perfectly.&lt;/p&gt;

&lt;p&gt;The trick is to make sure &lt;strong&gt;the element itself never leaves the DOM&lt;/strong&gt;. Don't use &lt;code&gt;v-if&lt;/code&gt;, &lt;code&gt;@if()&lt;/code&gt;, or conditional JSX that destroys the node. Instead, keep it mounted and update its text content when something changes.&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="c"&gt;&amp;lt;!-- Angular --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;aria-live=&lt;/span&gt;&lt;span class="s"&gt;"polite"&lt;/span&gt; &lt;span class="na"&gt;aria-relevant=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;[hidden]=&lt;/span&gt;&lt;span class="s"&gt;"!showMessage"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{ message }}&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Vue example --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;aria-live=&lt;/span&gt;&lt;span class="s"&gt;"polite"&lt;/span&gt; &lt;span class="na"&gt;aria-relevant=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;v-show=&lt;/span&gt;&lt;span class="s"&gt;"showMessage"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  {{ statusMessage }}
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- React --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;aria-live=&lt;/span&gt;&lt;span class="s"&gt;"polite"&lt;/span&gt; &lt;span class="na"&gt;aria-relevant=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;hidden=&lt;/span&gt;&lt;span class="s"&gt;{!showMessage}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{message}&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ &lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keeps announcements close to their visual context&lt;/li&gt;
&lt;li&gt;Implementation on-site with minimal markup&lt;/li&gt;
&lt;li&gt;Lightweight for component-specific updates&lt;/li&gt;
&lt;li&gt;Works without global dependencies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You must ensure the live region never unmounts&lt;/li&gt;
&lt;li&gt;Tricky to coordinate if you have multiple regions in different places&lt;/li&gt;
&lt;li&gt;Some screen readers struggle if too many live regions are active at once&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Local live regions are great for self-contained components that are always rendered (like a chat transcript or a loading status). But for &lt;em&gt;transient messages&lt;/em&gt; — like success toasts, error banners, or form confirmations — they're not ideal. That's where the second pattern shines.&lt;/p&gt;

&lt;h3&gt;
  
  
  Global Live Region
&lt;/h3&gt;

&lt;p&gt;This is the most reliable and scalable approach. You create a &lt;strong&gt;single, persistent live region&lt;/strong&gt; that stays mounted for your entire app's lifetime — usually at the root level — and expose a  function or service to push messages into it.&lt;/p&gt;

&lt;p&gt;Think of it like a message bus for screen readers.&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="c"&gt;&amp;lt;!-- template for your root component or index.html --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"aria-live-polite"&lt;/span&gt; &lt;span class="na"&gt;aria-live=&lt;/span&gt;&lt;span class="s"&gt;"polite"&lt;/span&gt; &lt;span class="na"&gt;aria-atomic=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"sr-only"&lt;/span&gt;&lt;span class="nt"&gt;&amp;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;id=&lt;/span&gt;&lt;span class="s"&gt;"aria-live-assertive"&lt;/span&gt; &lt;span class="na"&gt;aria-live=&lt;/span&gt;&lt;span class="s"&gt;"assertive"&lt;/span&gt; &lt;span class="na"&gt;aria-atomic=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"sr-only"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To actually hide this live regions visually, you should use a &lt;a href="https://css-tricks.com/inclusively-hidden/" rel="noopener noreferrer"&gt;common CSS implementation&lt;/a&gt; which makes it hidden but accessible and ensures screen readers will pick it up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.sr-only&lt;/span&gt;&lt;span class="nd"&gt;:not&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;:focus&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nd"&gt;:not&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;:active&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;clip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;clip-path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;inset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;white-space&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;nowrap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&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;Whenever you now need to announce something, you just call a helper:&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;announce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Form submitted successfully.&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;p&gt;Under the hood, it clears and rewrites the text content to trigger a DOM mutation:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aria-live-polite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;region&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&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;setTimeout&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;region&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will see that we don't have to do this by hand since there are very popular solutions for our frameworks already implementing this approach.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Always present in the DOM and therefore extremely reliable&lt;/li&gt;
&lt;li&gt;Works across routes and components&lt;/li&gt;
&lt;li&gt;Centralized and easy to test&lt;/li&gt;
&lt;li&gt;Handles &lt;code&gt;polite&lt;/code&gt; vs. &lt;code&gt;assertive&lt;/code&gt; globally&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Announcements lose some &lt;em&gt;local context&lt;/em&gt; ("Where did that message come from?")&lt;/li&gt;
&lt;li&gt;Requires a global setup or shared service&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Implementing Reliable Live Regions in Angular, Vue, and React
&lt;/h2&gt;

&lt;p&gt;Now let's see how to make them work in practice — using the &lt;strong&gt;global live announcer pattern&lt;/strong&gt;, since it's the most robust option across all three frameworks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Angular
&lt;/h3&gt;

&lt;p&gt;Angular already ships an accessibility helper called &lt;a href="https://material.angular.dev/cdk/a11y/overview#liveannouncer" rel="noopener noreferrer"&gt;LiveAnnouncer&lt;/a&gt; in the Angular CDK.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ng add @angular/cdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you have installed the CDK (which I recommend since it also has other nice helpers for supporting accessibility), you can use the LiveAnnouncer as follows:&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="c1"&gt;// save-button.ts&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;LiveAnnouncer&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/cdk/a11y&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;Component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;inject&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="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-save-button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;button (click)="onSave()"&amp;gt;Save&amp;lt;/button&amp;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;SaveButton&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;liveAnnouncer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;LiveAnnouncer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;onSave&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="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;liveAnnouncer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;announce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Settings saved successfully.&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;polite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CDK automatically creates a hidden live region and manages timing — no manual DOM work needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Vue 3
&lt;/h3&gt;

&lt;p&gt;For Vue applications, I recommend using &lt;a href="https://github.com/vue-a11y/vue-announcer" rel="noopener noreferrer"&gt;vue-a11y/vue-announcer&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @vue-a11y/announcer@next &lt;span class="c"&gt;# Vue 3&lt;/span&gt;
&lt;span class="c"&gt;# OR:&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; @vue-a11y/announcer      &lt;span class="c"&gt;# Vue 2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once installed, setup the &lt;code&gt;VueAnnouncer&lt;/code&gt; for your &lt;code&gt;App&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// main.ts&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;createApp&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;vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;App&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;./App.vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;router&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;./router&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;VueAnnouncer&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;@vue-a11y/announcer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vue-a11y/announcer/dist/style.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nf"&gt;createApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;VueAnnouncer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#app&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;p&gt;After that, place the component containing the global live region(s) in your main component:&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="c"&gt;&amp;lt;!-- App.vue --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;VueAnnouncer&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"sr-only"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  ...
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last step is to use the composable &lt;code&gt;useAnnouncer&lt;/code&gt; which pushes messages into the live region:&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="c"&gt;&amp;lt;!-- SaveButton.vue --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"onSave"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Save&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="nt"&gt;&amp;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;useAnnouncer&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;@vue-a11y/announcer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;polite&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useAnnouncer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;onSave&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;polite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Settings saved successfully.&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="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  React
&lt;/h3&gt;

&lt;p&gt;For React, I recommend using &lt;a href="https://react-spectrum.adobe.com/blog/building-a-combobox.html#voiceover" rel="noopener noreferrer"&gt;@react-aria/live-announcer&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @react-aria/live-announcer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After installation, you can call the &lt;code&gt;announce&lt;/code&gt; function which will set up the global live region if not already present and push the message into it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// SaveButton.tsx&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;announce&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;@react-aria/live-announcer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;SaveButton&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;handleSave&lt;/span&gt; &lt;span class="o"&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="nf"&gt;announce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Settings saved successfully.&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleSave&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Save&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;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 library handles the DOM manipulation and timing automatically, making it a reliable choice for production apps.&lt;/p&gt;




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

&lt;p&gt;Making &lt;code&gt;aria-live&lt;/code&gt; work reliably in modern SPAs comes down to understanding how screen readers interact with the DOM. The core issue is that frameworks like Angular, Vue, and React often destroy and recreate elements, breaking the connection assistive technologies need to announce changes. By keeping live regions mounted and using established announcer services, you can ensure your dynamic content reaches all users effectively.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The root cause&lt;/strong&gt;: Screen readers track DOM mutations, not reactive state — when elements are recreated, announcements may fail&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep it stable&lt;/strong&gt;: Live regions must stay mounted; update text content, not structure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Choose wisely&lt;/strong&gt;: Use &lt;code&gt;polite&lt;/code&gt; for most updates, &lt;code&gt;assertive&lt;/code&gt; only for critical alerts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two patterns&lt;/strong&gt;: Local regions for persistent components, global announcers for transient messages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use proven tools&lt;/strong&gt;: Angular CDK's LiveAnnouncer, @vue-a11y/announcer for Vue, @react-aria/live-announcer for React&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test with real users&lt;/strong&gt;: Screen reader behavior varies — always validate with actual assistive technology&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The payoff&lt;/strong&gt;: Reliable announcements make your app more inclusive, responsive, and trustworthy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;small&gt;&lt;strong&gt;Thanks&lt;/strong&gt; for &lt;a href="https://github.com/fmalcher/" rel="noopener noreferrer"&gt;Ferdinand Malcher&lt;/a&gt;, &lt;a href="https://github.com/milan-w" rel="noopener noreferrer"&gt;Milan Wanielik&lt;/a&gt; and &lt;a href="https://github.com/mfranzke" rel="noopener noreferrer"&gt;Maximilian Franzke&lt;/a&gt; for reviewing this article.&lt;br&gt;&lt;strong&gt;Cover image:&lt;/strong&gt; Picture from &lt;a href="https://www.freepik.com/free-photo/paper-hand-holding-megaphone_19925176.htm" rel="noopener noreferrer"&gt;Freepik&lt;/a&gt;, edited.&lt;/small&gt;&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>angular</category>
      <category>vue</category>
      <category>react</category>
    </item>
    <item>
      <title>Route based navigation menus in Vue</title>
      <dc:creator>Danny Koppenhagen</dc:creator>
      <pubDate>Mon, 19 Dec 2022 16:53:38 +0000</pubDate>
      <link>https://dev.to/dkoppenhagen/route-based-navigation-menus-in-vue-od2</link>
      <guid>https://dev.to/dkoppenhagen/route-based-navigation-menus-in-vue-od2</guid>
      <description>&lt;p&gt;Recently while working on &lt;a href="https://vuejs.org/" rel="noopener noreferrer"&gt;Vue&lt;/a&gt; app, I asked myself: Isn’t the main navigation menu somehow related to the configuration of the routes and routing tree? And can't it be built dynamically from the router configuration?&lt;/p&gt;

&lt;p&gt;With this question in my mind, I started to work on a very simple but representative example of how to achieve this by enriching the route configuration using the &lt;code&gt;meta&lt;/code&gt; option.&lt;/p&gt;

&lt;p&gt;The following example allows you to easily wrap big parts of your app into a module that is self contained and only exposes a bit of route configuration which can be imported and included in the main router configuration.&lt;/p&gt;

&lt;p&gt;The app has a simple navigation component that extracts all available routes provided by the &lt;a href="https://router.vuejs.org/" rel="noopener noreferrer"&gt;Vue Router&lt;/a&gt;.&lt;br&gt;
These routes have all the information needed by a navigation item to build a menu point and define the routing target.&lt;/p&gt;

&lt;p&gt;The following picture shows an high level overview of the architecture.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fk9n.dev%2Fassets%2Fimages%2Fblog%2Fvue-route-menu%2Fnav-structure.drawio.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fk9n.dev%2Fassets%2Fimages%2Fblog%2Fvue-route-menu%2Fnav-structure.drawio.svg" alt="Planned structure" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;You can check out the complete working example with the source code in the following Stackblitz project:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://stackblitz.com/edit/vue3-dynamic-menu" rel="noopener noreferrer"&gt;https://stackblitz.com/edit/vue3-dynamic-menu&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  basic app setup
&lt;/h2&gt;

&lt;p&gt;Let's create a simple Vue project using Vue3 and Vue-Router.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm init vue@latest
npm i vue-router@4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setup the router
&lt;/h2&gt;

&lt;p&gt;First we need the basic route configuration which represents the routing tree and in the end our menu structure.&lt;/p&gt;

&lt;p&gt;We want to focus on the basic menu configuration and the initial page we are loading.&lt;br&gt;
Therefore we will create the &lt;code&gt;MainPage&lt;/code&gt; component which we can place in the &lt;code&gt;src/components&lt;/code&gt; directory.&lt;br&gt;
The component should simply display its name for demonstartion purpose:&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;script &lt;/span&gt;&lt;span class="na"&gt;setup&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;MainPage.vue&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next thing we want to do is to setup the route for this component.&lt;br&gt;
Therefore we are creating the &lt;code&gt;router.ts&lt;/code&gt; file within the &lt;code&gt;src&lt;/code&gt; directory.&lt;br&gt;
We are importing the &lt;code&gt;MainPage&lt;/code&gt; component and using it for the route &lt;code&gt;main&lt;/code&gt;.&lt;br&gt;
Furthermore we are adding a redirect to "&lt;code&gt;/main&lt;/code&gt;" when the root-route "&lt;code&gt;/&lt;/code&gt;" is opened.&lt;br&gt;
To be able to get the displayble menu label later, we add the &lt;code&gt;meta&lt;/code&gt; object to the route configuration containing the &lt;code&gt;label&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createRouter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createWebHistory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;RouteRecordRaw&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;vue-router&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="nx"&gt;MainPage&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;./components/MainPage.vue&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;const&lt;/span&gt; &lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RouteRecordRaw&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="c1"&gt;//default route redirection&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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;redirect&lt;/span&gt;&lt;span class="p"&gt;:&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;main&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="c1"&gt;// common app routes&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/main&lt;/span&gt;&lt;span class="dl"&gt;'&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;main&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MainPage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Main Page&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="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;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createRouter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;history&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;createWebHistory&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="nx"&gt;routes&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 exported router must now be used in the &lt;code&gt;main.ts&lt;/code&gt; file:&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;createApp&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;vue&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./style.css&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="nx"&gt;App&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;./App.vue&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;getRouter&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;./router&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;routes&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;./app-section-1/routes&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;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getRouter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#app&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;p&gt;Now we have to add the &lt;code&gt;&amp;lt;router-view /&amp;gt;&lt;/code&gt; to our &lt;code&gt;App.vue&lt;/code&gt; file to be able to render the correct component routed by the Vue Router.&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;script &lt;/span&gt;&lt;span class="na"&gt;setup&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;router-view&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Build the menu items based on route &lt;code&gt;meta&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;So far so good: we’ve configured our first route so that we can later build a single menu item using the route configuration.&lt;/p&gt;

&lt;p&gt;The next step is to create the navigation component (&lt;code&gt;AppNav&lt;/code&gt;) that extracts the &lt;code&gt;meta&lt;/code&gt; information from the route for the menu item and renders it. Therefore we have to filter for the occurrence of our provided meta data as we only want to display menu items that have a &lt;code&gt;label&lt;/code&gt; configured in the &lt;code&gt;meta&lt;/code&gt; information.&lt;/p&gt;

&lt;p&gt;The result is an array of all relevant routes.&lt;br&gt;
We iterate over the items with &lt;code&gt;v-for&lt;/code&gt; and pass each element to a new component &lt;code&gt;NavItem&lt;/code&gt; that takes a route configuration object for rendering a single navigation menu item.&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;script &lt;/span&gt;&lt;span class="na"&gt;setup&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;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;useRouter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useRoute&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;vue-router&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="nx"&gt;NavItem&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;./NavItem.vue&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;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRouter&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;filteredRoutes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;nav&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;NavItem&lt;/span&gt;
        &lt;span class="na"&gt;v-for=&lt;/span&gt;&lt;span class="s"&gt;"(routeConfig, index) in filteredRoutes"&lt;/span&gt;
        &lt;span class="na"&gt;:key=&lt;/span&gt;&lt;span class="s"&gt;"index"&lt;/span&gt;
        &lt;span class="na"&gt;:route-config=&lt;/span&gt;&lt;span class="s"&gt;"routeConfig"&lt;/span&gt;
      &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/nav&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before we forget, let's add the &lt;code&gt;AppNav&lt;/code&gt; component to our &lt;code&gt;App&lt;/code&gt; component above the &lt;code&gt;&amp;lt;router-view /&amp;gt;&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;script &lt;/span&gt;&lt;span class="na"&gt;setup&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;AppNav&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;./components/AppNav.vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;AppNav&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;hr&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;router-view&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we are create the &lt;code&gt;NavItem&lt;/code&gt; component.&lt;br&gt;
We are defining a single prop which gets passed by the parent component called &lt;code&gt;routeConfig&lt;/code&gt; which contains a whole route configuration record.&lt;br&gt;
Now we can focus on the template.&lt;br&gt;
Next add a &lt;code&gt;&amp;lt;router-link&amp;gt;&lt;/code&gt; and pass the route target using the unique &lt;code&gt;name&lt;/code&gt;.&lt;br&gt;
For the label of the link we can use the &lt;code&gt;label&lt;/code&gt; from our &lt;code&gt;meta&lt;/code&gt; information object which we defined in the router configuration.&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;script &lt;/span&gt;&lt;span class="na"&gt;setup&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;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;computed&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;vue&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="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RouteRecordRaw&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;vue-router&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;props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;defineProps&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;routeConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RouteRecordRaw&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="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"nav-item"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;router-link&lt;/span&gt; &lt;span class="na"&gt;:to=&lt;/span&gt;&lt;span class="s"&gt;"{ name: routeConfig.name }"&lt;/span&gt; &lt;span class="na"&gt;aria-current-value=&lt;/span&gt;&lt;span class="s"&gt;"page"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      {{ routeConfig.meta.label }}
    &lt;span class="nt"&gt;&amp;lt;/router-link&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great! The hardest part is now done (wasn't that tricky right?) and probably this solution already fit's for the majority.&lt;br&gt;
However there are two things I would like to describe in advance, as they may be relevant for you:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How to make the navigation easily extensible&lt;/li&gt;
&lt;li&gt;How to implement child menu items&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Make the navigation extensible
&lt;/h3&gt;

&lt;p&gt;Let's assume we have an extensible app where we outsource some pages and its child route configurations and make them includable in our app.&lt;br&gt;
This could for example be relevent when adding complete menus and pages for specific users with appropriate permissions.&lt;/p&gt;

&lt;p&gt;Therefore we want to make our route configuration extensible, so we can pass additional routes and child routes linked with their components to our router.&lt;/p&gt;

&lt;p&gt;To do this, we simply wrap the exported route into a function that accepts a list of route configurations as a Rest parameter.&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="cm"&gt;/* ... */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getConfiguredRouter&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;pluginRoutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RouteRecordRaw&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="nf"&gt;createRouter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;history&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;createWebHistory&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;pluginRoutes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flat&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;Next we need to adjust our &lt;code&gt;main.ts&lt;/code&gt; file.&lt;br&gt;
We pass the result received from &lt;code&gt;getConfiguredRouter()&lt;/code&gt; containing the additional routes we want to add.&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="cm"&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;getConfiguredRouter&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;./router&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;routes&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;./app-section-1/routes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="cm"&gt;/* ... */&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getConfiguredRouter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="cm"&gt;/* ... */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's create a new folder &lt;code&gt;app-section-1&lt;/code&gt; simulating this kind of app plugin or modules containing the extensible part for our app.&lt;br&gt;
Here we create another &lt;code&gt;routes.ts&lt;/code&gt; file that holds the route configuration for this app part.&lt;/p&gt;

&lt;p&gt;The configuration defines a base route that represents the main navigation item and redirects to its first child route &lt;code&gt;page-1&lt;/code&gt;.&lt;br&gt;
Here, we configure two child routes linked to their corresponding components which are created in the next step.&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="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RouteRecordRaw&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;vue-router&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="nx"&gt;Page1&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;./components/Page1.vue&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="nx"&gt;Page2&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;./components/Page2.vue&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;const&lt;/span&gt; &lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RouteRecordRaw&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/app-section-1&lt;/span&gt;&lt;span class="dl"&gt;'&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;appSection1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;:&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;appSection1Page1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;App Section 1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;children&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;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;page-1&lt;/span&gt;&lt;span class="dl"&gt;'&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;appSection1Page1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Page1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Page 1&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;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;page-2&lt;/span&gt;&lt;span class="dl"&gt;'&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;appSection1Page2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Page2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Page 2&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="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are creating the components &lt;code&gt;Page1&lt;/code&gt; and &lt;code&gt;Page2&lt;/code&gt; wihtin the &lt;code&gt;/src/app-section-1/components&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;Their implementation follows the one of the &lt;code&gt;MainPage&lt;/code&gt; component: They simply display their component names in the template for demo purposes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Render child menu items
&lt;/h2&gt;

&lt;p&gt;With the current version, we will already see both main navigation menu entries.&lt;br&gt;
But as we configured child elements with &lt;code&gt;label&lt;/code&gt;s, we also want to display them in the menu too.&lt;br&gt;
Therefore we simply add the appropriate template in the &lt;code&gt;NavItem&lt;/code&gt; component, as we already have everything we need by receiving the route configuration of the parent which contains all the information to render the child items.&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="c"&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"nav-item"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;router-link&lt;/span&gt; &lt;span class="na"&gt;:to=&lt;/span&gt;&lt;span class="s"&gt;"{ name: routeConfig.name }"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      {{ routeConfig.meta.label }}
    &lt;span class="nt"&gt;&amp;lt;/router-link&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ul&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"routeConfig.children"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt;
        &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"child-item"&lt;/span&gt;
        &lt;span class="na"&gt;v-for=&lt;/span&gt;&lt;span class="s"&gt;"(r, index) in routeConfig.children"&lt;/span&gt;
        &lt;span class="na"&gt;:key=&lt;/span&gt;&lt;span class="s"&gt;"index"&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;router-link&lt;/span&gt; &lt;span class="na"&gt;:to=&lt;/span&gt;&lt;span class="s"&gt;"{ name: r.name }"&lt;/span&gt; &lt;span class="na"&gt;aria-current-value=&lt;/span&gt;&lt;span class="s"&gt;"page"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          {{ r.meta.label }}
        &lt;span class="nt"&gt;&amp;lt;/router-link&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Styling active links
&lt;/h2&gt;

&lt;p&gt;To highlight the active menu items, we can now use the two automatically create CSS classes &lt;code&gt;router-link-active&lt;/code&gt; and &lt;code&gt;router-link-exact-active&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;router-link-active&lt;/code&gt;: Matches when a part of the URL matches the target route path of the &lt;code&gt;&amp;lt;router-link&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;router-link-exact-active&lt;/code&gt;: Matches when the whole route in the URL matches the exact target route path of the &lt;code&gt;&amp;lt;router-link&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="nc"&gt;.router-link-active&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;lightblue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="nc"&gt;.router-link-exact-active&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#f3aff8&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;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Passing meta information like &lt;code&gt;label&lt;/code&gt; to the vue router configuration lets us easily build dynamic generated menus.&lt;br&gt;
We no longer have to manually adjust our main navigation when adding new sites to our page as the menu is automatically extended by accessing the routes &lt;code&gt;meta&lt;/code&gt; information.&lt;br&gt;
This approach can reduce some template boilerplate.&lt;/p&gt;

&lt;p&gt;You can use this approach to loosely couple whole parts of your app by adding them as separate modules without the need to add internals like the navigation titles to the main app part.&lt;/p&gt;

&lt;p&gt;The whole working example can be seen in the following Stackblitz project:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://stackblitz.com/edit/vue3-dynamic-menu" rel="noopener noreferrer"&gt;https://stackblitz.com/edit/vue3-dynamic-menu&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Thanks for &lt;a href="https://github.com/dc7590/" rel="noopener noreferrer"&gt;Darren Cooper&lt;/a&gt; and &lt;a href="https://github.com/jschirrmacher/" rel="noopener noreferrer"&gt;Joachim Schirrmacher&lt;/a&gt; for reviewing this article.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>cheatsheet</category>
      <category>github</category>
    </item>
    <item>
      <title>Speed up your Angular schematics development with useful helper functions</title>
      <dc:creator>Danny Koppenhagen</dc:creator>
      <pubDate>Mon, 14 Sep 2020 09:33:12 +0000</pubDate>
      <link>https://dev.to/dkoppenhagen/speed-up-your-angular-schematics-development-with-useful-helper-functions-1kb2</link>
      <guid>https://dev.to/dkoppenhagen/speed-up-your-angular-schematics-development-with-useful-helper-functions-1kb2</guid>
      <description>&lt;h1&gt;
  
  
  Speed up your Angular schematics development with useful helper functions
&lt;/h1&gt;

&lt;p&gt;Angular CLI schematics offer us a way to add, scaffold and update app-related files and modules. However, there are some common things we will probably want integrate in our schematics: updating your &lt;code&gt;package.json&lt;/code&gt; file, adding or removing an Angular module or updating component imports.&lt;/p&gt;

&lt;p&gt;Currently, the way of authoring an Angular Schematic is documented &lt;a href="https://angular.io/guide/schematics-authoring"&gt;on angular.io&lt;/a&gt;.&lt;br&gt;
However, there is one big thing missing there: the way of integrating typical and repeating tasks.&lt;br&gt;
The Angular CLI itself uses schematics for e.g. generating modules and components, adding imports or modifying the &lt;code&gt;package.json&lt;/code&gt; file.&lt;br&gt;
Under the hood each of the schematics uses some very common utils which are not yet documented but available for all developers anyway.&lt;br&gt;
In the past, I've seen some Angular CLI Schematic projects where people were trying to implement almost the same common util methods on their own.&lt;br&gt;
However, since some of these are already implemented in the Angular CLI, I want to show you some of those typical helpers that you can use for you Angular CLI Schematic project to prevent any pitfalls.&lt;/p&gt;



&lt;h2&gt;
  
  
  ⚠️ Attention: not officially supported
&lt;/h2&gt;

&lt;p&gt;The helper functions I present you in this article are neither documented nor officially supported, and they may change in the future.&lt;br&gt;
&lt;a href="https://twitter.com/AlanAgius4"&gt;Alan Agius&lt;/a&gt;, member of the Angular CLI core team replied in a &lt;a href="https://github.com/angular/angular-cli/issues/15335#issuecomment-660609283"&gt;related issue (#15335)&lt;/a&gt; for creating a public schematics API reference:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;[...] those utils are not considered as part of the public API and might break without warning in any release.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, there are plans to provide some utilities via a public API but this is still in the planning stage.&lt;br&gt;
While things evolve, it's my intention to keep this article as up-to-date as possible.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The following Angular CLI schematics util functions are based on the Angular CLI version &lt;code&gt;12.0.0&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you use these functions and they will break in the future, you can check out the &lt;a href="https://github.com/angular/angular-cli/tree/master/packages/schematics/angular/utility"&gt;source code changes&lt;/a&gt; for the utility functions and adjust your code.&lt;/p&gt;
&lt;h2&gt;
  
  
  🕹 Examples and playground on GitHub
&lt;/h2&gt;

&lt;p&gt;To follow and try out the examples I present you in this article, I &lt;a href="https://github.com/d-koppenhagen/schematics-helpers-playground"&gt;prepared a playground repository on GitHub&lt;/a&gt;.&lt;br&gt;
Clone this repo and check out the &lt;code&gt;README.md&lt;/code&gt; inside to get started with the playground. 🚀&lt;/p&gt;
&lt;h2&gt;
  
  
  Create an Angular schematics example project
&lt;/h2&gt;

&lt;p&gt;First things first: We need a project where we can try things out.&lt;br&gt;
You can either use an existing schematics project or simply create a new blank one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @angular-devkit/schematics-cli blank &lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;playground
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;If you are not familar with the basics of authoring schematics, I recommend you to read the &lt;a href="https://angular.io/guide/schematics-authoring"&gt;Angular Docs&lt;/a&gt; and the &lt;a href="https://medium.com/@tomastrajan/total-guide-to-custom-angular-schematics-5c50cf90cdb4"&gt;blog post &lt;em&gt;"Total Guide To Custom Angular schematics"&lt;/em&gt; by Tomas Trajan&lt;/a&gt; first as well as the &lt;a href="https://indepth.dev/angular-schematics-from-0-to-publishing-your-own-library-i"&gt;article series "Angular Schematics from 0 to publishing your own library" by Natalia Venditto&lt;/a&gt; first.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After setting up the new blank project we should have this file available: &lt;code&gt;src/playground/index.ts&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Rule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SchematicContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Tree&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-devkit/schematics&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;function&lt;/span&gt; &lt;span class="nx"&gt;playground&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Rule&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;tree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SchematicContext&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;schematic works&lt;/span&gt;&lt;span class="dl"&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;tree&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 is the base for the following examples and explanations.&lt;br&gt;
Please make sure that you can execute the blank schematic by calling it on the console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @angular-devkit/schematics-cli .:playground
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or if you installed the schematics CLI globally via &lt;code&gt;npm i @angular-devkit/schematics-cli&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;schematics .:playground
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;.&lt;/code&gt; refers to the current directory where our schematics project lives.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/d-koppenhagen/schematics-helpers-playground/tree/master/playground/src/playground"&gt;Check out the basic example in the playground repository on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Basic types
&lt;/h3&gt;

&lt;p&gt;In case you are not familiar with the structure of schematics, I will just explain some very basic things shortly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;&lt;code&gt;Tree&lt;/code&gt;&lt;/strong&gt; is the structured virtual representation of every file in the workspace which we apply the schematic to.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;&lt;code&gt;Rule&lt;/code&gt;&lt;/strong&gt; is called with a &lt;code&gt;Tree&lt;/code&gt; and a &lt;code&gt;SchematicContext&lt;/code&gt;. The &lt;code&gt;Rule&lt;/code&gt; is supposed to make changes on the &lt;code&gt;Tree&lt;/code&gt; and returns the adjusted &lt;code&gt;Tree&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;&lt;code&gt;SchematicContext&lt;/code&gt;&lt;/strong&gt; contains information necessary for the schematics to execute some rules.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Install the helpers from &lt;code&gt;@schematics/angular&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;A second thing we need to do is to install the package &lt;code&gt;@schematics/angular&lt;/code&gt; which contains all the utils we need for the next steps.&lt;br&gt;
This package contains all the schematics the Angular CLI uses by itself when running commands like &lt;code&gt;ng generate&lt;/code&gt; or &lt;code&gt;ng new&lt;/code&gt; etc.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;--save&lt;/span&gt; @schematics/angular
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Changing the &lt;code&gt;package.json&lt;/code&gt;: Get, Add and Remove (dev-, peer-) dependencies
&lt;/h2&gt;

&lt;p&gt;A very common thing when authoring a schematic is adding a dependency to the &lt;code&gt;package.json&lt;/code&gt; file.&lt;br&gt;
Of course, we can implement a function that parses and writes to/from our JSON file.&lt;br&gt;
But why should we solve a problem that's already solved?&lt;/p&gt;

&lt;p&gt;For this, we can use the functions provided by &lt;code&gt;@schematics/angular/utility/dependencies&lt;/code&gt; to handle dependency operations.&lt;br&gt;
The function &lt;code&gt;addPackageJsonDependency()&lt;/code&gt; allows us to add a dependency object of type &lt;code&gt;NodeDependency&lt;/code&gt; to the &lt;code&gt;package.json&lt;/code&gt; file.&lt;br&gt;
The property &lt;code&gt;type&lt;/code&gt; must contain a value of the &lt;code&gt;NodeDependencyType&lt;/code&gt; enum.&lt;br&gt;
Its values represent the different sections of a &lt;code&gt;package.json&lt;/code&gt; file:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;dependencies&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;devDependencies&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;peerDependencies&lt;/code&gt; and&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;optionalDependencies&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first parameter to this util function is the &lt;code&gt;Tree&lt;/code&gt; with all its files.&lt;br&gt;
The function will not just append the dependency to the appropriate section, it will also insert the dependency at the right position, so that the dependencies list is ordered ascending by its keys.&lt;/p&gt;

&lt;p&gt;We can use the &lt;code&gt;getPackageJsonDependency()&lt;/code&gt; function to request the dependency configuration as a &lt;code&gt;NodeDependency&lt;/code&gt; object.&lt;br&gt;
The good thing here is: We don't need to know in which of the sections a dependency is located. It will look up the dependency in sections of the &lt;code&gt;package.json&lt;/code&gt; file: &lt;code&gt;dependencies&lt;/code&gt;, &lt;code&gt;devDependencies&lt;/code&gt;, &lt;code&gt;peerDependencies&lt;/code&gt; and &lt;code&gt;optionalDependencies&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The third function I want to show is &lt;code&gt;removePackageJsonDependency()&lt;/code&gt;.&lt;br&gt;
Just like &lt;code&gt;getPackageJsonDependency()&lt;/code&gt;, it can be called with a &lt;code&gt;Tree&lt;/code&gt; and the package name and it will remove this dependency from the &lt;code&gt;package.json&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;By default, all these functions will use the &lt;code&gt;package.json&lt;/code&gt; file in the root of the tree, but we can pass a third parameter containing a specific path to another &lt;code&gt;package.json&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Last but not least we don't want our users to manually run &lt;code&gt;npm install&lt;/code&gt; on the console after adding dependencies.&lt;br&gt;
Therefore, we can add a new &lt;code&gt;NodePackageInstallTask&lt;/code&gt; via the &lt;code&gt;addTask&lt;/code&gt; method on our &lt;code&gt;context&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Rule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SchematicContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Tree&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-devkit/schematics&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;NodePackageInstallTask&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-devkit/schematics/tasks&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;NodeDependency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;NodeDependencyType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;getPackageJsonDependency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;addPackageJsonDependency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;removePackageJsonDependency&lt;/span&gt;&lt;span class="p"&gt;,&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;@schematics/angular/utility/dependencies&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;function&lt;/span&gt; &lt;span class="nx"&gt;playground&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Rule&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;tree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SchematicContext&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="na"&gt;dep&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NodeDependency&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NodeDependencyType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Dev&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;moment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;~2.27.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;overwrite&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="nx"&gt;addPackageJsonDependency&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dep&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;getPackageJsonDependency&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;moment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="c1"&gt;// { type: 'devDependencies', name: 'moment', version: '~2.27.0' }&lt;/span&gt;

    &lt;span class="nx"&gt;removePackageJsonDependency&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;protractor&lt;/span&gt;&lt;span class="dl"&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;getPackageJsonDependency&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;protractor&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="c1"&gt;// null&lt;/span&gt;

    &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;NodePackageInstallTask&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;tree&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;To really check that the &lt;code&gt;NodePackageInstallTask&lt;/code&gt; is properly executed, you need to disable the schematics debug mode that's enabled by default during development and local execution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;schematics .:playground &lt;span class="nt"&gt;--debug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/angular/angular-cli/blob/master/packages/schematics/angular/utility/dependencies.ts"&gt;Check out the implementation of the dependency operations in detail.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/d-koppenhagen/schematics-helpers-playground/tree/master/playground/src/dependencies"&gt;Check out the examples in the playground repository on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Add content on a specific position
&lt;/h2&gt;

&lt;p&gt;Sometimes we need to change some contents of a file.&lt;br&gt;
Independently of the type of a file, we can use the &lt;code&gt;InsertChange&lt;/code&gt; class.&lt;br&gt;
This class returns a change object which contains the content to be added and the position where the change is being inserted.&lt;/p&gt;

&lt;p&gt;In the following example we will create a new file called &lt;code&gt;my-file.extension&lt;/code&gt; with the content &lt;code&gt;const a = 'foo';&lt;/code&gt; inside the virtual tree.&lt;br&gt;
First, we will instantiate a new &lt;code&gt;InsertChange&lt;/code&gt; with the file path, the position where we want to add the change and finally the content we want to add.&lt;br&gt;
The next step for us is to start the update process for the file using the &lt;code&gt;beginUpdate()&lt;/code&gt; method on our tree.&lt;br&gt;
This method returns an object of type &lt;code&gt;UpdateRecorder&lt;/code&gt;.&lt;br&gt;
We can now use the &lt;code&gt;insertLeft()&lt;/code&gt; method and hand over the position and the content (&lt;code&gt;toAdd&lt;/code&gt;) from the &lt;code&gt;InsertChange&lt;/code&gt;.&lt;br&gt;
The change is now marked but not proceeded yet.&lt;br&gt;
To really update the file's content we need to call the &lt;code&gt;commitUpdate()&lt;/code&gt; method on our tree with the &lt;code&gt;exportRecorder&lt;/code&gt;.&lt;br&gt;
When we now call &lt;code&gt;tree.get(filePath)&lt;/code&gt; we can log the file's content and see that the change has been proceeded.&lt;br&gt;
To delete a file inside the virtual tree, we can use the &lt;code&gt;delete()&lt;/code&gt; method with the file path on the tree.&lt;/p&gt;

&lt;p&gt;Let's have a look at an implementation 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;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Rule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SchematicContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Tree&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-devkit/schematics/&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;InsertChange&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;@schematics/angular/utility/change&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;function&lt;/span&gt; &lt;span class="nx"&gt;playground&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Rule&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;tree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SchematicContext&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;filePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-file.extension&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`const a = 'foo';`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// insert a new change&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;insertChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;InsertChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;const b = &lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;bar&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;;&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;exportRecorder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;beginUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;exportRecorder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;insertLeft&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;insertChange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;insertChange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toAdd&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commitUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;exportRecorder&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="c1"&gt;// const a = 'foo';&lt;/span&gt;
    &lt;span class="c1"&gt;// const b = 'bar';&lt;/span&gt;

    &lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// cleanup (if not running schematic in debug mode)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;tree&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;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/angular/angular-cli/blob/master/packages/schematics/angular/utility/change.ts"&gt;Check out the implementation for &lt;code&gt;InsertChange&lt;/code&gt; in detail.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/d-koppenhagen/schematics-helpers-playground/tree/master/playground/src/insert"&gt;Check out the example in the playground repository on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Determine relative path to the project root
&lt;/h2&gt;

&lt;p&gt;You might want to determine the relative path to your project root e.g. for using it in a template you want to apply in some location of your application.&lt;br&gt;
To determine the correct relative import path string for the target, you can use the helper function &lt;code&gt;relativePathToWorkspaceRoot()&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Rule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;SchematicContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;mergeWith&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-devkit/schematics/&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;relativePathToWorkspaceRoot&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;@schematics/angular/utility/paths&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;function&lt;/span&gt; &lt;span class="nx"&gt;playground&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Rule&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;_tree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SchematicContext&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;nonRootPathDefinition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;foo/bar/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// "./foo/bar" | "foo/bar/" work also&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rootPathDefinition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// "." | "./" work also&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;relativePathToWorkspaceRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nonRootPathDefinition&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="c1"&gt;// "../.."&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;relativePathToWorkspaceRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rootPathDefinition&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="c1"&gt;// "."&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sourceTemplates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./files&lt;/span&gt;&lt;span class="dl"&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;mergeWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;sourceTemplates&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;relativePathToWorkspaceRoot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;relativePathToWorkspaceRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nonRootPathDefinition&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="p"&gt;}),&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;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;If you have e.g. a JSON file template in the directory &lt;code&gt;files&lt;/code&gt; and you want to insert the path, you can use the helper function in the template as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;%= relativePathToWorkspaceRoot %&amp;gt;/my-file-ref.json"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For more details about how to use and apply templates in your own schematics, check out the &lt;a href="https://medium.com/@tomastrajan/total-guide-to-custom-angular-schematics-5c50cf90cdb4"&gt;blog post by Tomas Trajan: &lt;em&gt;"Total Guide To Custom Angular schematics"&lt;/em&gt;&lt;/a&gt; and the &lt;a href="https://indepth.dev/angular-schematics-from-0-to-publishing-your-own-library-i"&gt;article series &lt;em&gt;"Angular Schematics from 0 to publishing your own library"&lt;/em&gt; by Natalia Venditto&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/angular/angular-cli/blob/master/packages/schematics/angular/utility/paths.ts"&gt;Check out the implementation for &lt;code&gt;relativePathToWorkspaceRoot()&lt;/code&gt; in detail.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/d-koppenhagen/schematics-helpers-playground/tree/master/playground/src/relative-path"&gt;Check out the example in the playground repository on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Add TypeScript imports
&lt;/h2&gt;

&lt;p&gt;In the previous section we learned how to add content to some file.&lt;br&gt;
However, this way for changing a file isn't the best and only works well when we know the exact position where to add some content.&lt;br&gt;
Now imagine a user changes the format of the file before: This would lead to problems with finding the correct file position.&lt;/p&gt;

&lt;p&gt;In many cases we want to modify TypeScript files and insert code into them.&lt;br&gt;
And indeed there are also lots of utils that will help us to manage such operations.&lt;/p&gt;

&lt;p&gt;Imagine you want the schematic to import the class &lt;code&gt;Bar&lt;/code&gt; in a specific file from the file &lt;code&gt;bar.ts&lt;/code&gt;;&lt;br&gt;
You could simply add the whole import line but there are edge cases:&lt;br&gt;
What if the target file already contains an import or even a default import from &lt;code&gt;bar.ts&lt;/code&gt;.&lt;br&gt;
In that case we would have multiple import lines for &lt;code&gt;bar.ts&lt;/code&gt; which causes problems.&lt;/p&gt;

&lt;p&gt;Luckily there is another great helper that takes care of adding imports or updating existing ones.&lt;br&gt;
The function &lt;code&gt;insertImport()&lt;/code&gt; needs the source file to update and the path to the file followed by the import name and the file path for the import to be added.&lt;br&gt;
The last parameter is optional – if set to &lt;code&gt;true&lt;/code&gt;, the import will be added as a default import.&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ts&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;typescript&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;Rule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SchematicContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Tree&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-devkit/schematics/&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;insertImport&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;@schematics/angular/utility/ast-utils&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;InsertChange&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;@schematics/angular/utility/change&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;function&lt;/span&gt; &lt;span class="nx"&gt;playground&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Rule&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;tree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SchematicContext&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;filePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;some-file.ts&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;fileContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`import { Foo } from 'foo';
const bar = 'bar;
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fileContent&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;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createSourceFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;fileContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ScriptTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Latest&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;updateRecorder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;beginUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&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;change&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;insertImport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bar&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;./bar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;change&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;InsertChange&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;updateRecorder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;insertRight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;change&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;change&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toAdd&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commitUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;updateRecorder&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toString&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;tree&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;The example above will add the content &lt;code&gt;import Bar from './bar';&lt;/code&gt; right before the constant.&lt;br&gt;
As we marked it as default import, the import name is not put in curly braces (&lt;code&gt;{ }&lt;/code&gt;).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/angular/angular-cli/blob/master/packages/schematics/angular/utility/change.ts"&gt;Check out the implementation for &lt;code&gt;insertImport()&lt;/code&gt; in detail.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/d-koppenhagen/schematics-helpers-playground/tree/master/playground/src/import"&gt;Check out the example in the playground repository on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Update &lt;code&gt;NgModule&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Now we know how we can modify TypeScript imports using the util functions.&lt;br&gt;
However, just importing something isn't enough in most cases.&lt;br&gt;
There are common things like importing a component and adding it to the &lt;code&gt;NgModule&lt;/code&gt; in the &lt;code&gt;declarations&lt;/code&gt; array or inserting a module in the &lt;code&gt;imports&lt;/code&gt; section.&lt;br&gt;
Luckily, there are some helpers provided for these operations.&lt;br&gt;
These function also based on the &lt;code&gt;insertImport()&lt;/code&gt; function, so that they will handle existing file imports and just update the import lists accordingly.&lt;/p&gt;
&lt;h3&gt;
  
  
  Add a declaration to a module
&lt;/h3&gt;

&lt;p&gt;The first thing I want to show you is how you can add a component to the &lt;code&gt;declarations&lt;/code&gt; of an &lt;code&gt;NgModule&lt;/code&gt;.&lt;br&gt;
For this, let's assume you create a schematic that adds a new &lt;code&gt;DashboardComponent&lt;/code&gt; to your project.&lt;br&gt;
You don't need to add the import manually and then determine the right place to insert the component to the &lt;code&gt;declarations&lt;/code&gt; of the &lt;code&gt;NgModule&lt;/code&gt;.&lt;br&gt;
Instead, you can use the &lt;code&gt;addDeclarationToModule()&lt;/code&gt; function exported from &lt;code&gt;@schematics/angular/utility/ast-utils&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the following example we will create an &lt;code&gt;AppModule&lt;/code&gt; from the &lt;code&gt;moduleContent&lt;/code&gt; using &lt;code&gt;ts.createSourceFile()&lt;/code&gt; first.&lt;br&gt;
Then we will register the &lt;code&gt;updateRecorder&lt;/code&gt; as learned in the examples before.&lt;br&gt;
Now we call the &lt;code&gt;addDeclarationToModule()&lt;/code&gt; function with the source file and the module path followed by the name of the component we want to import and the relative path to the module where we can find the component.&lt;br&gt;
As a result it returns us an array of &lt;code&gt;Change&lt;/code&gt; objects that contain the positions and the contents for the change.&lt;br&gt;
Finally, we can handle these changes one-by-one by iterating over the array.&lt;br&gt;
For all changes of type &lt;code&gt;InsertChange&lt;/code&gt; we can now call the method &lt;code&gt;updateRecorder.insertleft()&lt;/code&gt; with the position of the change and the content to be added.&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ts&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;typescript&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;Rule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SchematicContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Tree&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-devkit/schematics/&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;addDeclarationToModule&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;@schematics/angular/utility/ast-utils&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;InsertChange&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;@schematics/angular/utility/change&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;function&lt;/span&gt; &lt;span class="nx"&gt;playground&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Rule&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;tree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SchematicContext&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;modulePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app.module.ts&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;moduleContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;modulePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;moduleContent&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;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createSourceFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;modulePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;moduleContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ScriptTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Latest&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;updateRecorder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;beginUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;modulePath&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;changes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;addDeclarationToModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;modulePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DashboardComponent&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;./dashboard.component&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;InsertChange&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="k"&gt;for&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;change&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;changes&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;change&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;InsertChange&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;updateRecorder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;insertLeft&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;change&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;change&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toAdd&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commitUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;updateRecorder&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;modulePath&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toString&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;tree&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;When we execute this schematic now, we can see in the log that the following import line has been added to the file:&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="cm"&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;DashboardComponent&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;./dashboard.component&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;NgModule&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;declarations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;AppComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;DashboardComponent&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="cm"&gt;/* ... */&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;AppModule&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;h3&gt;
  
  
  &lt;code&gt;NgModule&lt;/code&gt;: add &lt;code&gt;imports&lt;/code&gt;, &lt;code&gt;exports&lt;/code&gt;, &lt;code&gt;providers&lt;/code&gt;, and &lt;code&gt;bootstrap&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Similar to the previous example we can re-export something we imported by using the &lt;code&gt;addExportToModule()&lt;/code&gt; function and adding an import to the &lt;code&gt;NgModule&lt;/code&gt; by using &lt;code&gt;addImportToModule()&lt;/code&gt;.&lt;br&gt;
We can also modify the &lt;code&gt;providers&lt;/code&gt;, and &lt;code&gt;bootstrap&lt;/code&gt; arrays by using  &lt;code&gt;addProviderToModule()&lt;/code&gt; and  &lt;code&gt;addBootstrapToModule()&lt;/code&gt;.&lt;br&gt;
Again, it will take care of all the things necessary such as extending and creating imports, checking for existing entries in the &lt;code&gt;NgModule&lt;/code&gt; metadata and much more.&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="cm"&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;addImportToModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;addExportToModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;addProviderToModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;addBootstrapToModule&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;@schematics/angular/utility/ast-utils&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="cm"&gt;/* ... */&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;playground&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Rule&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;tree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SchematicContext&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="cm"&gt;/* ... */&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;exportChanges&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;addExportToModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;modulePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FooModule&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;./foo.module&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;InsertChange&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;importChanges&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;addImportToModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;modulePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;BarModule&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;./bar.module&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;InsertChange&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;providerChanges&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;addProviderToModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;modulePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MyProvider&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;./my-provider.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;InsertChange&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;bootstrapChanges&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;addBootstrapToModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;modulePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MyComponent&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;./my.component.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt;  &lt;span class="nx"&gt;InsertChange&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="cm"&gt;/* ... */&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;modulePath&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toString&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;tree&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;Our result will now look 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;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;BrowserModule&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/platform-browser&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;NgModule&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="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;AppComponent&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;./app.component&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;FooModule&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;./foo.module&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;BarModule&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;./bar.module&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;MyProvider&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;./my-provider.ts&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;MyComponent&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;./my.component.ts&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;BazComponent&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;./baz.component.ts&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;NgModule&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;declarations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;AppComponent&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;BrowserModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;BarModule&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;MyProvider&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;bootstrap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;MyComponent&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;FooModule&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;AppModule&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;h3&gt;
  
  
  Add route declarations
&lt;/h3&gt;

&lt;p&gt;Let's have a look at another common scenario: We want our schematic to insert a route definition to a module that calls &lt;code&gt;RouterModule.forRoot()&lt;/code&gt; or &lt;code&gt;.forChild()&lt;/code&gt; with a route definition array.&lt;br&gt;
For this, we can use the helper function &lt;code&gt;addRouteDeclarationToModule()&lt;/code&gt; which returns a &lt;code&gt;Change&lt;/code&gt; object which we need to handle as an &lt;code&gt;InsertChange&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ts&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;typescript&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;Rule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SchematicContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Tree&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-devkit/schematics/&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;addRouteDeclarationToModule&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;@schematics/angular/utility/ast-utils&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;InsertChange&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;@schematics/angular/utility/change&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;function&lt;/span&gt; &lt;span class="nx"&gt;playground&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Rule&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;tree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SchematicContext&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;modulePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-routing.module.ts&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;moduleContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`import { NgModule } from '@angular/core';

    const myRoutes = [
      { path: 'foo', component: FooComponent }
    ];

    @NgModule({
      imports: [
        RouterModule.forChild(myRoutes)
      ],
    })
    export class MyRoutingModule { }
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;modulePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;moduleContent&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;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createSourceFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;modulePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;moduleContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ScriptTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Latest&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;updateRecorder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;beginUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;modulePath&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;change&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;addRouteDeclarationToModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src/app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;`{ path: 'bar', component: BarComponent }`&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;InsertChange&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;updateRecorder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;insertLeft&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;change&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;change&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toAdd&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commitUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;updateRecorder&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;modulePath&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toString&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;tree&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;The example above will insert the route definition object &lt;code&gt;{ path: 'bar', component: BarComponent }&lt;/code&gt; into the &lt;code&gt;myRoutes&lt;/code&gt; array by finding the variable associated in &lt;code&gt;forRoot()&lt;/code&gt; or &lt;code&gt;forChild()&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/angular/angular-cli/blob/master/packages/schematics/angular/utility/ast-utils.ts"&gt;Check out the implementation for ast-utils in detail.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/d-koppenhagen/schematics-helpers-playground/tree/master/playground/src/module"&gt;Check out the examples for module operations in the playground repository on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Retrieve the Angular workspace configuration
&lt;/h2&gt;

&lt;p&gt;Each Angular app lives in an Angular workspace containing an &lt;code&gt;angular.json&lt;/code&gt; configuration file.&lt;br&gt;
If we want to get either the path to the workspace configuration file or the configuration from the file itself, we can use the &lt;code&gt;getWorkspacePath()&lt;/code&gt; and &lt;code&gt;getWorkspace()&lt;/code&gt; functions by passing in the current &lt;code&gt;Tree&lt;/code&gt; object.&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;Rule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SchematicContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Tree&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-devkit/schematics&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;getWorkspacePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getWorkspace&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;@schematics/angular/utility/config&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;function&lt;/span&gt; &lt;span class="nx"&gt;playground&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Rule&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;tree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SchematicContext&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="c1"&gt;// returns the path to the Angular configuration file&lt;/span&gt;
    &lt;span class="c1"&gt;// ('/angular.json' or probably `.angular.json` for older Angular projects)&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;getWorkspacePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="c1"&gt;// returns the whole configuration object from the 'angular.json' file&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;getWorkspace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&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="mi"&gt;2&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;To try out things locally, we need to execute the schematics from an Angular app root path on our system.&lt;br&gt;
To do so, navigate into an existing Angular app or create a new one for testing purposes.&lt;br&gt;
Then, execute the schematic from there by using the relative path to the &lt;code&gt;src/collection.json&lt;/code&gt; file and adding the schematic name after the colon (&lt;code&gt;:&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ng new some-test-project &lt;span class="nt"&gt;--routing&lt;/span&gt;  &lt;span class="c"&gt;# create a new test project&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;some-test-project      &lt;span class="c"&gt;# be sure to be in the root of the angular project&lt;/span&gt;
&lt;span class="c"&gt;# assume the schematics project itself is located relatively to the angular project in '../playground'&lt;/span&gt;
schematics ../playground/src/collection.json:playground &lt;span class="c"&gt;# execute the 'playground' schematic&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/angular/angular-cli/blob/master/packages/schematics/angular/utility/config.ts"&gt;Check out the implementation in detail.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/d-koppenhagen/schematics-helpers-playground/tree/master/playground/src/config"&gt;Check out the example in the playground repository on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Get default path for an app inside the workspace
&lt;/h2&gt;

&lt;p&gt;An Angular workspace can contain multiple applications or libraries.&lt;br&gt;
To find their appropriate main paths, you can use the helper function &lt;code&gt;createDefaultPath()&lt;/code&gt;.&lt;br&gt;
We need to pass in the &lt;code&gt;Tree&lt;/code&gt; object and the name of the app or library we want to get the path for.&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;Rule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SchematicContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Tree&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-devkit/schematics&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;createDefaultPath&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;@schematics/angular/utility/workspace&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;function&lt;/span&gt; &lt;span class="nx"&gt;playground&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Rule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SchematicContext&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;defaultPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;createDefaultPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-lib&lt;/span&gt;&lt;span class="dl"&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;defaultPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// '/projects/my-lib/src/lib'&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;Let's create a new library inside our testing Angular app called &lt;code&gt;my-lib&lt;/code&gt;, to try it out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ng g lib my-lib  &lt;span class="c"&gt;# create a new library inside the Angular workspace&lt;/span&gt;
&lt;span class="c"&gt;# assume the schematics project itself is located relatively to the angular project in '../playground'&lt;/span&gt;
schematics ../playground/src/collection.json:playground &lt;span class="c"&gt;# execute the 'playground' schematic&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/angular/angular-cli/blob/master/packages/schematics/angular/utility/workspace.ts"&gt;Check out the implementation for &lt;code&gt;createDefaultPath()&lt;/code&gt; in detail.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/d-koppenhagen/schematics-helpers-playground/tree/master/playground/src/worksapce"&gt;Check out the example in the playground repository on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Call schematics from schematics
&lt;/h2&gt;

&lt;p&gt;If you run a schematic, you may come to the point where one schematic should execute another one.&lt;br&gt;
For example: You create schematics for generating a specific component.&lt;br&gt;
You also develop a &lt;code&gt;ng add&lt;/code&gt; or &lt;code&gt;ng new&lt;/code&gt; schematic to set up things for you and create an example component by default.&lt;br&gt;
In such cases you may want to combine multiple schematics.&lt;/p&gt;
&lt;h3&gt;
  
  
  Run local schematics using the &lt;code&gt;RunSchematicTask&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;First we want to use the &lt;code&gt;RunSchematicTask&lt;/code&gt; class to achieve our goal.&lt;br&gt;
Let's say we have a collection file like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"$schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"../node_modules/@angular-devkit/schematics/collection-schema.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"schematics"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ng-add"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Demo that calls the 'playground' schematic inside"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"factory"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./ng-add/index#ngAdd"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"playground"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"An example schematic."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"factory"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./playground/index#playground"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The factory for &lt;code&gt;ng-add&lt;/code&gt; is located in &lt;code&gt;src/ng-add/index.ts&lt;/code&gt;.&lt;br&gt;
Then inside this schematic we can call a new &lt;code&gt;RunSchematicTask&lt;/code&gt; with the name of the schematic we want to execute and the project name from the Angular workspace.&lt;br&gt;
To really execute the operation we need to pass the task to the &lt;code&gt;context&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Rule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SchematicContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Tree&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-devkit/schematics&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;RunSchematicTask&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-devkit/schematics/tasks&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;function&lt;/span&gt; &lt;span class="nx"&gt;ngAdd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Rule&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;tree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SchematicContext&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;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;RunSchematicTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;playground&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;project&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-workspace&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="nx"&gt;tree&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;To check if it works we can fill our playground (&lt;code&gt;src/playground/index.ts&lt;/code&gt;) schematic as follows and log the call:&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;Rule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SchematicContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Tree&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-devkit/schematics&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;function&lt;/span&gt; &lt;span class="nx"&gt;playground&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Rule&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;tree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SchematicContext&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;schematic &lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;playground&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt; called&lt;/span&gt;&lt;span class="dl"&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;tree&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;If we now run &lt;code&gt;schematics ../playground/src/collection.json:ng-add --debug=false&lt;/code&gt; from our example Angular project, we can see that the &lt;code&gt;ng-add&lt;/code&gt; schematic has called the &lt;code&gt;playground&lt;/code&gt; schematic.&lt;/p&gt;

&lt;p&gt;With this knowledge you can define small atomic schematics that can be executed "standalone" or from another Schematic that combines multiple standalone schematics and calls them with specific parameters.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/d-koppenhagen/schematics-helpers-playground/tree/master/playground/src/ng-add"&gt;Check out the example in the playground repository on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Run schematics by using the &lt;code&gt;schematic()&lt;/code&gt; and &lt;code&gt;externalSchematic()&lt;/code&gt; function
&lt;/h3&gt;

&lt;p&gt;Perfect, we can now execute and combine our schematics.&lt;br&gt;
But what if we want to combine external schematics developed by others and integrate them in our own schematics?&lt;br&gt;
Users are lazy, so we don't want to leave it up to them to manually execute some other things before running our schematics.&lt;/p&gt;

&lt;p&gt;Imagine you are working in a big company with multiple different Angular projects.&lt;br&gt;
This company already has its own standardized UI library, but all the applications are very different and ran by different teams (so not really a use case for a Monorepo).&lt;br&gt;
However, there are also things they all have in common like a Single Sign-On.&lt;br&gt;
Also, the basic design always looks similar – at least the header and the footer of all the apps.&lt;/p&gt;

&lt;p&gt;I've often seen companies building a reference implementation for such apps that's then cloned / copied and adjusted by all developers.&lt;br&gt;
However, there are some problems with this kind of workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You always have to keep the reference project up-to-date.&lt;/li&gt;
&lt;li&gt;You have to clean up your copy of the project from stuff you don't need.&lt;/li&gt;
&lt;li&gt;You need to tell all teams to copy this reference to keep track of changes and to adjust their copies frequently.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thus, a better solution in my opinion is to use schematics for the whole integration and upgrade workflow.&lt;br&gt;
You can create an &lt;code&gt;ng new&lt;/code&gt; schematic that will scaffold the whole project code for you.&lt;br&gt;
But you don't want to start from scratch, so you probably want to combine things like these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ng add&lt;/code&gt;: Add your schematics to an existing project

&lt;ul&gt;
&lt;li&gt;Corporate UI library (always)&lt;/li&gt;
&lt;li&gt;Single Sign-On (optional)&lt;/li&gt;
&lt;li&gt;Header Component (optional)&lt;/li&gt;
&lt;li&gt;Footer Component (optional)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ng new&lt;/code&gt;: Create a new project with your company defaults

&lt;ul&gt;
&lt;li&gt;Create the basic application generated by the Angular CLI (&lt;strong&gt;&lt;code&gt;externalSchematic&lt;/code&gt;&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;Run the &lt;code&gt;ng add&lt;/code&gt; Schematic&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Alright, we already know how we can achieve most of these things.&lt;br&gt;
However, there's one thing we haven't learned yet: How to run other (external) schematics?&lt;br&gt;
We can use the &lt;code&gt;externalSchematic&lt;/code&gt; function for this.&lt;/p&gt;

&lt;p&gt;But first things first, let's check if our collection file is ready to start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"$schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"../node_modules/@angular-devkit/schematics/collection-schema.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"schematics"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ng-add"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Call other schematics from the same or other packages"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"factory"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./ng-add/index#playground"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ng-new"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Execute `ng new` with predefined options and run other stuff"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"factory"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./ng-new/index#playground"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Using the special Schematic names &lt;code&gt;ng-add&lt;/code&gt; and &lt;code&gt;ng-new&lt;/code&gt; let's you later use the schematic by just executing &lt;code&gt;ng add&lt;/code&gt;/&lt;code&gt;ng new&lt;/code&gt; (instead of other schematics called with &lt;code&gt;ng generate&lt;/code&gt;).&lt;br&gt;
There is also a special Schematic named &lt;code&gt;ng-update&lt;/code&gt; which will be called in the end with the &lt;code&gt;ng update&lt;/code&gt; Angular CLI command.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After we defined the schema, we can now start to implement our schematics.&lt;br&gt;
To execute an external Schematic, it must be available in the scope of the project..&lt;br&gt;
However, since we want to create a completely new project with the &lt;code&gt;ng new&lt;/code&gt; Schematic, we don't have any &lt;code&gt;node_modules&lt;/code&gt; installed in the target directory where we want to initialize the Angular workspace.&lt;br&gt;
To run an external command we can use the &lt;a href="https://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options"&gt;&lt;code&gt;spawn&lt;/code&gt; method from &lt;code&gt;child_process&lt;/code&gt;&lt;/a&gt; (available globally for Node.js).&lt;br&gt;
This creates a new process that executes a command (in our case: &lt;code&gt;npm install @schematics/angular&lt;/code&gt;).&lt;br&gt;
To make things look synchronous we wrap the method call into a Promise and &lt;code&gt;await&lt;/code&gt; for the Promise to be resolved.&lt;br&gt;
Now we listen to the &lt;code&gt;close&lt;/code&gt; event from &lt;code&gt;spawn&lt;/code&gt; and check that there was no error during install (code equals &lt;code&gt;0&lt;/code&gt;).&lt;br&gt;
If everything worked fine, we will resolve the Promise, otherwise we can throw an error.&lt;br&gt;
The last step is to &lt;code&gt;chain&lt;/code&gt; all of our &lt;code&gt;Rule&lt;/code&gt;s:&lt;br&gt;
We first use the &lt;code&gt;externalSchematic()&lt;/code&gt; function to run the &lt;code&gt;ng new&lt;/code&gt; Schematic from Angular itself and set up the basic app.&lt;br&gt;
We will hand over some default options here such a using &lt;code&gt;SCSS&lt;/code&gt;, support legacy browsers, strict mode, etc.&lt;br&gt;
Angulars &lt;code&gt;ng new&lt;/code&gt; schematic requires also, that we define the specific version for their schematic to be used.&lt;br&gt;
In our case we want to use the &lt;code&gt;ng new&lt;/code&gt; schematic from the Angular CLI version &lt;code&gt;12.0.0&lt;/code&gt;.&lt;br&gt;
The second call is our &lt;code&gt;ng add&lt;/code&gt; Schematic that adds our company specific components, UI libs and so on to the project.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We've already learned how to run a local Schematic by using the &lt;code&gt;RunSchematicTask&lt;/code&gt; class that we need to add to our &lt;code&gt;context&lt;/code&gt; object.&lt;br&gt;
In this example we are using the &lt;code&gt;schematic()&lt;/code&gt; function to achieve the same goal.&lt;br&gt;
Why are there two ways? To be honest: I actually don't know.&lt;br&gt;
I found both implementations in the source code of Angular CLI.&lt;br&gt;
&lt;/p&gt;


&lt;/blockquote&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;Rule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;SchematicContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;externalSchematic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;schematic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;chain&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-devkit/schematics&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;Schema&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;AngularNgNewSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;PackageManager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Style&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;@schematics/angular/ng-new/schema&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;spawn&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;child_process&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;function&lt;/span&gt; &lt;span class="nx"&gt;playground&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AngularNgNewSchema&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Rule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_tree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SchematicContext&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;angularSchematicsPackage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@schematics/angular&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="na"&gt;ngNewOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AngularNgNewSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;12.0.0&lt;/span&gt;&lt;span class="dl"&gt;'&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="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;routing&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;strict&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;legacyBrowsers&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;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Scss&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;packageManager&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PackageManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Npm&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&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;resolve&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;📦 Installing packages...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;npm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;install&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;angularSchematicsPackage&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;close&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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="nx"&gt;code&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;📦 Packages installed successfully ✅&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nx"&gt;resolve&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;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="s2"&gt;`❌ install Angular schematics from '&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;angularSchematicsPackage&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;' failed`&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;chain&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="nx"&gt;externalSchematic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;angularSchematicsPackage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ng-new&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ngNewOptions&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nx"&gt;schematic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ng-add&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="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;When we now run the &lt;code&gt;ng new&lt;/code&gt; Schematic from somewhere outside an Angular workspace, we can see that first of all the Angular &lt;code&gt;ng new&lt;/code&gt; Schematic is executed with our predefined settings.&lt;br&gt;
After this, the &lt;code&gt;ng add&lt;/code&gt; schematics is called.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;schematics ./playground/src/collection.json:ng-new &lt;span class="nt"&gt;--debug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;
📦 Installing packages...
📦 Packages installed successfully ✅
? What name would you like to use &lt;span class="k"&gt;for &lt;/span&gt;the new workspace and initial project? my-project
CREATE my-project/README.md &lt;span class="o"&gt;(&lt;/span&gt;1027 bytes&lt;span class="o"&gt;)&lt;/span&gt;
CREATE my-project/.editorconfig &lt;span class="o"&gt;(&lt;/span&gt;274 bytes&lt;span class="o"&gt;)&lt;/span&gt;
CREATE my-project/.gitignore &lt;span class="o"&gt;(&lt;/span&gt;631 bytes&lt;span class="o"&gt;)&lt;/span&gt;
CREATE my-project/angular.json &lt;span class="o"&gt;(&lt;/span&gt;3812 bytes&lt;span class="o"&gt;)&lt;/span&gt;
...
CREATE my-project/src/app/app.component.scss &lt;span class="o"&gt;(&lt;/span&gt;0 bytes&lt;span class="o"&gt;)&lt;/span&gt;
CREATE my-project/src/app/app.component.html &lt;span class="o"&gt;(&lt;/span&gt;25757 bytes&lt;span class="o"&gt;)&lt;/span&gt;
CREATE my-project/src/app/app.component.spec.ts &lt;span class="o"&gt;(&lt;/span&gt;1069 bytes&lt;span class="o"&gt;)&lt;/span&gt;
CREATE my-project/src/app/app.component.ts &lt;span class="o"&gt;(&lt;/span&gt;215 bytes&lt;span class="o"&gt;)&lt;/span&gt;
CREATE my-project/src/app/package.json &lt;span class="o"&gt;(&lt;/span&gt;816 bytes&lt;span class="o"&gt;)&lt;/span&gt;
CREATE my-project/e2e/protractor.conf.js &lt;span class="o"&gt;(&lt;/span&gt;869 bytes&lt;span class="o"&gt;)&lt;/span&gt;
CREATE my-project/e2e/tsconfig.json &lt;span class="o"&gt;(&lt;/span&gt;294 bytes&lt;span class="o"&gt;)&lt;/span&gt;
CREATE my-project/e2e/src/app.e2e-spec.ts &lt;span class="o"&gt;(&lt;/span&gt;643 bytes&lt;span class="o"&gt;)&lt;/span&gt;
CREATE my-project/e2e/src/app.po.ts &lt;span class="o"&gt;(&lt;/span&gt;301 bytes&lt;span class="o"&gt;)&lt;/span&gt;
⠏ Installing packages...
✔ Packages installed successfully.
schematic works
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After you have deployed the Schematic, you can now execute it by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;-g&lt;/span&gt; my-schematic-package-name &lt;span class="c"&gt;# install the Schematic so it's available globally&lt;/span&gt;
ng new my-app &lt;span class="nt"&gt;--collection&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;my-schematic-package-name &lt;span class="c"&gt;# Run the Angular CLI's `ng new` Schematic with the defined collection&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similar to this example you can call the &lt;code&gt;ng add&lt;/code&gt; Schematic from the collection if you are in an existing Angular workspace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ng add my-schematic-package-name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/d-koppenhagen/schematics-helpers-playground/tree/master/playground/src/ng-new"&gt;Check out the example in the playground repository on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;The presented util functions are great and comfortable helpers you can use to create your own Angular CLI schematics.&lt;br&gt;
However, as they aren't officially published until now, you should keep track of any changes by keeping an eye on the &lt;a href="https://github.com/angular/angular-cli/issues/15335"&gt;documentation issue (#15335)&lt;/a&gt; and &lt;a href="https://github.com/angular/angular-cli/tree/master/packages/schematics/angular/utility"&gt;changes on the related code&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;getPackageJsonDependency()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Get a package configuration from the &lt;code&gt;package.json&lt;/code&gt; (dev-, peer-, optional-) dependencies config.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;addPackageJsonDependency()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Add a NPM package to the &lt;code&gt;package.json&lt;/code&gt; as (dev-, peer-, optional-) dependency.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;removePackageJsonDependency()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Remove a NPM package from the &lt;code&gt;package.json&lt;/code&gt; (dev-, peer-, optional-) dependencies.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;relativePathToWorkspaceRoot()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Get the relative import path to the root of the workspace for a given file inside the workspace.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;insertImport()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Insert an import statement for a file to an existing TypeScript file.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;addDeclarationToModule()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Import a declaration (e.g. Component or Directive) and add it to the &lt;code&gt;declarations&lt;/code&gt; array of an Angular module.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;addImportToModule()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Import an Angular Module and add it to the &lt;code&gt;imports&lt;/code&gt; array of another Angular module.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;addExportToModule()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Import an Angular Module and add it to the &lt;code&gt;exports&lt;/code&gt; array of another Angular module.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;addProviderToModule()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Import a service / provider and add it to the &lt;code&gt;providers&lt;/code&gt; array of an Angular module.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;addBootstrapToModule()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Import a Component and add it to the &lt;code&gt;bootstrap&lt;/code&gt; array of an Angular module.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;addRouteDeclarationToModule()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Add a route definition to the router configuration in an Angular routing module.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;getWorkspacePath()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Retrieve the path to the Angular workspace configuration file (&lt;code&gt;angular.json&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;getWorkspace()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Get the configuration object from the Angular workspace configuration file (&lt;code&gt;angular.json&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;createDefaultPath()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Get the default application / library path for a project inside an Angular workspace.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Class&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;InsertChange&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;This class returns a change object with the content to be added and the position where a change is being inserted.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;NodePackageInstallTask&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A task instance that will perform a &lt;code&gt;npm install&lt;/code&gt; once instantiated and added to the &lt;code&gt;context&lt;/code&gt; via &lt;code&gt;addTask()&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RunSchematicTask&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A task that runs another schematic after instantiation and adding it to the &lt;code&gt;context&lt;/code&gt; via &lt;code&gt;addTask()&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Thank you&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Special thanks goes to &lt;a href="https://twitter.com/mgechev"&gt;Minko Gechev&lt;/a&gt;, &lt;a href="https://twitter.com/tomastrajan"&gt;Tomas Trajan&lt;/a&gt; and &lt;a href="https://twitter.com/fmalcher01"&gt;Ferdinand Malcher&lt;/a&gt; for the feedback and revising this article.&lt;/p&gt;





</description>
    </item>
    <item>
      <title>My Development Setup</title>
      <dc:creator>Danny Koppenhagen</dc:creator>
      <pubDate>Thu, 27 Aug 2020 18:21:18 +0000</pubDate>
      <link>https://dev.to/dkoppenhagen/my-development-setup-1ne2</link>
      <guid>https://dev.to/dkoppenhagen/my-development-setup-1ne2</guid>
      <description>&lt;h1&gt;
  
  
  My Development Setup
&lt;/h1&gt;

&lt;p&gt;In the following article I will present you what tools I am using during my day-to-day development workflow.&lt;br&gt;
Also, I will show you a list of extensions and their purpose that will help me and probably you too being more productive.&lt;/p&gt;



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

&lt;p&gt;As a developer I always give my best to be productive, to write good and well documented source code and to share my knowledge.&lt;br&gt;
For all this, I use great tools and extensions that will help me to get the most of it and working faster and cleaner.&lt;br&gt;
Those tools and extensions help me during my everyday life as a developer for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Development of Angular, Vue.js apps,&lt;/li&gt;
&lt;li&gt;sharing knowledge with recordings, GIFs, screenshots, code snippets etc.,&lt;/li&gt;
&lt;li&gt;writing good documentation, and&lt;/li&gt;
&lt;li&gt;being more productive in general.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both at work and in my free time I am working on an Apple MacBook Pro.&lt;br&gt;
But most of the tools and software listed here is available for Windows, macOS and Linux distributions.&lt;/p&gt;
&lt;h2&gt;
  
  
  Software
&lt;/h2&gt;

&lt;p&gt;Let's get started with some basic software that I use.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;a href="https://tabby.sh"&gt;Tabby&lt;/a&gt; / &lt;a href="https://iterm2.com"&gt;iTerm2&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;The default Terminal app for macOS fulfills its purpose but for being more productive I can recommend the alternative terminal applications &lt;strong&gt;&lt;a href="https://tabby.sh"&gt;Tabby&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;a href="https://iterm2.com"&gt;iTerm2&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I used iTerm2 for a long time and it's a great tool with lots of configuration options.&lt;br&gt;
You can create tabs, use profiles and customize styles and layouts.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ipUBA1Ib--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://k9n.dev/assets/images/blog/dev-setup/iterm2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ipUBA1Ib--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://k9n.dev/assets/images/blog/dev-setup/iterm2.png" alt="Screenshot: iTerm2" width="880" height="207"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, a few weeks ago I switched from iTerm2 to Tabby (formerly Terminus).&lt;br&gt;
Tabby is a quite new terminal app and it's Open Source!&lt;br&gt;
Behind the scenes it's an &lt;a href="https://www.electronjs.org/"&gt;Electron App&lt;/a&gt; using &lt;a href="https://angular.io"&gt;Angular&lt;/a&gt;.&lt;br&gt;
You can also customize it a lot.&lt;br&gt;
A feature I like particularly is that you can define SSH and Serial Port profiles.&lt;br&gt;
You can then later activate those profiles so that you can access an SSH remote host or a serial port without entering anything manually in the terminal.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--89JooTYV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://k9n.dev/assets/images/blog/dev-setup/terminus.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--89JooTYV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://k9n.dev/assets/images/blog/dev-setup/terminus.png" alt="Screenshot: Tabby" width="880" height="254"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;a href="https://www.cakebrew.com"&gt;Cakebrew&lt;/a&gt; – macOS only
&lt;/h3&gt;

&lt;p&gt;If you are on a Mac, you probably know &lt;strong&gt;&lt;a href="https://brew.sh"&gt;Homebrew&lt;/a&gt;&lt;/strong&gt; – the package manager for macOS.&lt;br&gt;
If you don't know Homebrew: You should definitely check it out.&lt;br&gt;
Personally I prefer to install most of my Software via Homebrew as I can easily manage updates later without having to manually download the packages.&lt;br&gt;
However, since I don't remember all Homebrew commands all the time and to get an overview of the installed packages, I use a Homebrew GUI called &lt;strong&gt;CakeBrew&lt;/strong&gt;.&lt;br&gt;
This lets me easily update and manage my installed Homebrew packages.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MATz2F1t--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://k9n.dev/assets/images/blog/dev-setup/cakebrew.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MATz2F1t--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://k9n.dev/assets/images/blog/dev-setup/cakebrew.png" alt="Screenshot: CakeBrew" width="880" height="464"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;a href="https://github.com/nvm-sh/nvm"&gt;NVM&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Working on multiple projects requires sometimes different environments.&lt;br&gt;
As a web developer Node.js is essential and should always be up-to-date but sometimes you need to use different versions of Node.js and NPM for different projects.&lt;br&gt;
NVM (Node Version Manager) let's you easily install and switch between multiple Node.js Versions.&lt;br&gt;
My recommendation is also to check-in the &lt;code&gt;.nvmrc&lt;/code&gt; file in every project to help other team members to use the right version of Node.js automatically.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;a href="https://git-fork.com"&gt;Fork&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;As a developer, version control with &lt;a href="https://git-scm.com"&gt;Git&lt;/a&gt; is essential for your development workflow.&lt;br&gt;
Personally I do all of the basic operations via the command line, like creating commits, adding files to the staging area, pulling and pushing.&lt;br&gt;
However, there are some things where I prefer to use a GUI for assistance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;graphical overview of commits and branches (history)&lt;/li&gt;
&lt;li&gt;interactive rebase&lt;/li&gt;
&lt;li&gt;managing multiple remote repositories&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Fork&lt;/strong&gt; is a very nice, clean and powerful Git GUI that helps you to control Git in an easy and interactive way.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cEADqPAy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://k9n.dev/assets/images/blog/dev-setup/fork.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cEADqPAy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://k9n.dev/assets/images/blog/dev-setup/fork.png" alt="Screenshot: Fork" width="880" height="421"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;a href="https://code.visualstudio.com"&gt;Visual Studio Code&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;In the last years, Microsoft has improved its free IDE &lt;strong&gt;Visual Studio Code&lt;/strong&gt; a lot, and with every frequent release it gets even more powerful.&lt;br&gt;
One of the things that makes &lt;strong&gt;VSCode&lt;/strong&gt; such a great IDE is the wide range of available extensions and plugins.&lt;br&gt;
VSCode is a very universal and adoptable IDE which has great support and tools for lots of programming languages.&lt;br&gt;
So it doesn't matter if you develop a web application, a mobile app using e.g. Flutter, a C# or a Python app: You can use VSCode for all of that and you don't need to get start using a new specific IDE for each and every specific language.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XVcRV09J--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://k9n.dev/assets/images/blog/dev-setup/vscode.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XVcRV09J--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://k9n.dev/assets/images/blog/dev-setup/vscode.png" alt="Screenshot: VSCode" width="880" height="490"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Later in this article, I will present you a list of my favorite extensions for VSCode.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;a href="https://google.com/chrome"&gt;Chrome&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;As a web developer, a modern browser like &lt;strong&gt;Google Chrome&lt;/strong&gt; is essential.&lt;br&gt;
In my opinion the developer and debugging tools are more mature compared to &lt;a href="https://www.mozilla.org/firefox"&gt;Firefox&lt;/a&gt;.&lt;br&gt;
Chrome is also very up-to-date in terms of the latest JavaScript features and it has a wide range of extensions you can install that will help you to even be more productive developing web applications.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6_bZMMfY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://k9n.dev/assets/images/blog/dev-setup/chrome.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6_bZMMfY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://k9n.dev/assets/images/blog/dev-setup/chrome.png" alt="Screenshot: Google Chrome" width="880" height="467"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A list of my favorite extensions for Google Chrome can be found further down in this article.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;a href="https://insomnia.rest"&gt;Insomnia&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Insomnia is a great and simple tools that's very easy to use when you want to interact with REST or &lt;a href="https://graphql.org"&gt;GraphQL&lt;/a&gt; endpoints. It has a very simple UI but it's powerful nonetheless.&lt;br&gt;
You can call endpoints, check their responses and also create batch requests or save your requests for using them later again.&lt;br&gt;
I personally prefer Insomnia over &lt;a href="https://www.postman.com"&gt;Postman&lt;/a&gt; which I used before and which is also very powerful.&lt;br&gt;
However, in my opinion the UI of Postman got a bit confusing during the last years by introducing new features.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uJsfWkvV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://k9n.dev/assets/images/blog/dev-setup/insomnia.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uJsfWkvV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://k9n.dev/assets/images/blog/dev-setup/insomnia.png" alt="Screenshot: Insomnia" width="880" height="391"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;a href="https://draw.io"&gt;draw.io&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;From time to time as a developer you need to illustrate things and flows.&lt;br&gt;
For this, I mostly use &lt;strong&gt;draw.io&lt;/strong&gt;. It's available as a web application but also as an installable desktop version.&lt;br&gt;
Draw.io provides a lot of basic icons and vector graphics for network illustrations, UML, diagrams, engineering icons for circuits and much more.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vfYdiwa0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://k9n.dev/assets/images/blog/dev-setup/drawio.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vfYdiwa0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://k9n.dev/assets/images/blog/dev-setup/drawio.png" alt="Screenshot: draw.io" width="880" height="474"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;a href="https://recordit.co"&gt;RecordIt&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;When I develop extensions for VSCode or other tools I always give my best to present users how to use these tools.&lt;br&gt;
The best way to present things is in a visual way as a screenshot or a GIF / video screencast.&lt;br&gt;
This supplements the textual description and thus, can be processed more easily by users.&lt;br&gt;
&lt;strong&gt;RecordIt&lt;/strong&gt; lets you record your screen and create small screencasts that can be shared via a simple URL as a video or GIF.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YruUuOKC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://k9n.dev/assets/images/blog/dev-setup/recordit.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YruUuOKC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://k9n.dev/assets/images/blog/dev-setup/recordit.png" alt="Screenshot: RecordIt" width="880" height="430"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;a href="https://github.com/keycastr/keycastr"&gt;KeyCastr&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Recording screencasts can be supplemented by displaying the keys you are pressing during the recording.&lt;br&gt;
This is a great way to present keyboard shortcuts without having to manually explain which keys you're using.&lt;br&gt;
For that purpose I use the tool &lt;strong&gt;KeyCastr&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bt3slbxs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://k9n.dev/assets/images/blog/dev-setup/keycastr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bt3slbxs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://k9n.dev/assets/images/blog/dev-setup/keycastr.png" alt="Screenshot: KeyCastr" width="880" height="240"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Visual Studio Code Extensions
&lt;/h2&gt;

&lt;p&gt;Since &lt;a href="https://code.visualstudio.com/updates/v1_48"&gt;Visual Studio Code Version 1.48.0&lt;/a&gt; the Feature &lt;strong&gt;Settings Sync&lt;/strong&gt; became stable and available for everyone.&lt;br&gt;
This feature allows you to sync your settings between different installations of VSCode using your Microsoft or GitHub account.&lt;br&gt;
When you've installed VSCode on multiple machines and you always want to have the instances in sync, you should definitely &lt;a href="https://code.visualstudio.com/docs/editor/settings-sync"&gt;set up this feature&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The next part is all about the VSCode extensions I am using so let's walk quickly over the plugins I've installed on my VSCode instances:&lt;/p&gt;
&lt;h3&gt;
  
  
  Appearance
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;del&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--I2Eqzrvg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://coenraads.gallerycdn.vsassets.io/extensions/coenraads/bracket-pair-colorizer-2/0.2.0/1594062809176/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="128" height="128"&gt;&lt;/del&gt;&lt;/td&gt;
&lt;td&gt;
&lt;del&gt;&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=CoenraadS.bracket-pair-colorizer-2"&gt;Bracket Pair Colorizer 2&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;This tool will colorize opening and closing brackets in different colors, so you get a better overview in your code.&lt;/del&gt; &lt;strong&gt;Note:&lt;/strong&gt; Visual Studio Code now  &lt;a href="https://code.visualstudio.com/blogs/2021/09/29/bracket-pair-colorization"&gt;includes native support for bracket pair colorization&lt;/a&gt; which is way faster than using the extension.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tDSH78cg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://naumovs.gallerycdn.vsassets.io/extensions/naumovs/color-highlight/2.3.0/1499789961213/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="128" height="128"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=naumovs.color-highlight"&gt;Color Highlight&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;After installing this extension it will highlight all valid web color values with their appropriate color so that you can directly see the color.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QFvqxGzF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://kisstkondoros.gallerycdn.vsassets.io/extensions/kisstkondoros/vscode-gutter-preview/0.26.2/1591049586324/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="128" height="128"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=kisstkondoros.vscode-gutter-preview"&gt;Image Preview&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;After installing this plugin, you will see a little preview image right next to your code line number when a source code line contains a valid image file path.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VU4317Hx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://byi8220.gallerycdn.vsassets.io/extensions/byi8220/indented-block-highlighting/1.0.7/1524962452244/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="128" height="128"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=byi8220.indented-block-highlighting"&gt;Indented Block Highlighting&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;This extensions highlights the intented area that contains the cursor.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IbjGCnBa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://oderwat.gallerycdn.vsassets.io/extensions/oderwat/indent-rainbow/7.4.0/1552964364054/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="128" height="128"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/search?term=Indent%20Rainbow&amp;amp;target=VSCode&amp;amp;category=All%20categories&amp;amp;sortBy=Relevance"&gt;Indent Rainbow&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;This plugin colorizes the different indentation levels. This is especially helpful when writing YAML files or Python applications where the indentations play an important role for the code semantics.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SFSPho0V--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://emilast.gallerycdn.vsassets.io/extensions/emilast/logfilehighlighter/2.9.0/1594820309342/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="128" height="128"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=emilast.LogFileHighlighter"&gt;Log File Highlighter&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;If you ever have to investigate a bunch of log files you will love this plugin. It highlights log information like the severity or timestamps so it will be easier for you to inspect them.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---Yypu70e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://johnpapa.gallerycdn.vsassets.io/extensions/johnpapa/vscode-peacock/3.8.0/1597093468460/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="256" height="256"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=johnpapa.vscode-peacock"&gt;Peacock&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;Peacock is great when you are working with multiple VSCode windows at the same time. You can simply colorize them to quickly find out which project is currently open.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NI4HecdN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://mechatroner.gallerycdn.vsassets.io/extensions/mechatroner/rainbow-csv/1.7.1/1596509943679/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="128" height="128"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=mechatroner.rainbow-csv"&gt;Rainbow CSV&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;This extension will colorize each column in a CSV file in a different color, so you can better read the file contents.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4Vhpx7u4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://wayou.gallerycdn.vsassets.io/extensions/wayou/vscode-todo-highlight/1.0.4/1532254554587/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="128" height="128"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=wayou.vscode-todo-highlight"&gt;TODO Highlight&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;This extension will highlight &lt;code&gt;TODO&lt;/code&gt;, &lt;code&gt;FIXME&lt;/code&gt; and some other annotations within comments in your code.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h3&gt;
  
  
  Docs
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3lhNLBTh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://asciidoctor.gallerycdn.vsassets.io/extensions/asciidoctor/asciidoctor-vscode/2.8.3/1594560690791/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="200" height="200"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=asciidoctor.asciidoctor-vscode"&gt;AsciiDoc&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;The AsciiDoc Plugin gives you syntax highlighting, a preview and snippets for the &lt;a href="https://asciidoc.org"&gt;AsciiDoc&lt;/a&gt; format.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yMQnq2so--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://d-koppenhagen.gallerycdn.vsassets.io/extensions/d-koppenhagen/vscode-code-review/1.11.0/1598341798822/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="400" height="400"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=d-koppenhagen.vscode-code-review"&gt;Code Review&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;This is an extension I wrote by myself. It allows you to create expert reviews / code reviews for a workspace that can be exported as a formatted HTML report or importable CSV/JSON file.&lt;br&gt;This extension is pretty helpful if you are doing for example one-time code reviews for customers or probably students but you don't have direct access to a Gitlab or GitHub repository where you can directly add comments in a merge/pull request.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MfLOKA9G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://bierner.gallerycdn.vsassets.io/extensions/bierner/emojisense/0.7.0/1588199773410/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="220" height="220"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=bierner.emojisense"&gt;emojisense&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;I personally like using emojis and I use them in &lt;code&gt;README.md&lt;/code&gt; files for my open source projects at GitHub. However, one thing I always have to look up are the &lt;a href="https://gist.github.com/rxaviers/7360908"&gt;emjoykey&lt;/a&gt; for the supported GitHub emojis. With this extension I have an autocompletion and I can search for them.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A_oQEyK2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://d-koppenhagen.gallerycdn.vsassets.io/extensions/d-koppenhagen/file-tree-to-text-generator/1.2.1/1568782852679/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="400" height="400"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=d-koppenhagen.file-tree-to-text-generator"&gt;File Tree to Text Generator&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;An extension I created by myself: It lets you generate file / directory trees for Markdown, LaTeX, ASCII or even a custom format right from your VSCode file explorer.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1wrTx8Js--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://bierner.gallerycdn.vsassets.io/extensions/bierner/markdown-mermaid/1.8.1/1597947461090/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="128" height="128"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=bierner.markdown-mermaid"&gt;Markdown Preview Mermaid support&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;Have you ever visualized things in your &lt;code&gt;README.md&lt;/code&gt; file? Thanks to &lt;a href="https://mermaid-js.github.io/mermaid"&gt;Mermaid.js&lt;/a&gt; you can easily create beautiful flowcharts, sequence diagrams and more. This plugin enables the preview support in VSCode for mermaid diagrams embedded in your markdown files.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--chPQqTjO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://yzhang.gallerycdn.vsassets.io/extensions/yzhang/markdown-all-in-one/3.2.0/1595650433498/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="128" height="128"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=yzhang.markdown-all-in-one"&gt;Markdown All in One&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;This extension enables some great helpful features when writing Markdown files such as table of contents generation, auto-formatting the document and it gives you a great autocompletion feature.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h3&gt;
  
  
  Graphics
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rnlnwMrQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://hediet.gallerycdn.vsassets.io/extensions/hediet/vscode-drawio/0.7.2/1593456864928/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="128" height="128"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=hediet.vscode-drawio"&gt;Draw.io Integration&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;This extension integrates the &lt;a href="https://draw.io"&gt;draw.io&lt;/a&gt; editor in VSCode. You can directly edit draw.io associated files.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--D7mD2sk6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jock.gallerycdn.vsassets.io/extensions/jock/svg/1.3.8/1596248511766/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="128" height="128"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=jock.svg"&gt;SVG&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;This extension will give you SVG code highlight and preview support. You can even export the SVG directly from the preview as a &lt;code&gt;*.png&lt;/code&gt; graphic.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h3&gt;
  
  
  JavaScript / TypeScript
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--91C9Cb8U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dbaeumer.gallerycdn.vsassets.io/extensions/dbaeumer/vscode-eslint/2.1.8/1594861497267/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="600" height="600"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint"&gt;ESLint&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;This extensions detects your ESLint configuration for code conventions and displays the violations. Furthermore, it offers you to quick fix detected violations against some rules.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aCZMidkQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://wix.gallerycdn.vsassets.io/extensions/wix/vscode-import-cost/2.12.0/1543514109720/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="128" height="128"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=wix.vscode-import-cost"&gt;Import Cost&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;With this plugin installed, projects using webpack will be analyzed for their imports and the bundle size they will have. The resulting size is displayed right next to the import. This helps you to identify very big imported libs.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XwiXLwND--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://xabikos.gallerycdn.vsassets.io/extensions/xabikos/javascriptsnippets/1.8.0/1587489699375/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="128" height="128"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=xabikos.JavaScriptSnippets"&gt;JavaScript (ES6) code snippets&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;This extension brings some great snippets for JavaScript / TypeScript like a short snippet &lt;code&gt;clg&lt;/code&gt; for writing &lt;code&gt;console.log()&lt;/code&gt; or &lt;code&gt;imp&lt;/code&gt; for &lt;code&gt;import &amp;lt;foo&amp;gt; from '&amp;lt;foo&amp;gt;';&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lSsCm5ef--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://orta.gallerycdn.vsassets.io/extensions/orta/vscode-jest/3.2.0/1588254430761/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="128" height="128"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=Orta.vscode-jest"&gt;Jest&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;The Jest extension gives you auto completion and color indicators for successful / failing Jest tests.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tKe7-DWe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cmstead.gallerycdn.vsassets.io/extensions/cmstead/jsrefactor/2.20.6/1586404799977/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="128" height="128"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=cmstead.jsrefactor"&gt;JS Refactor&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;This extension gives you a lot of JavaScript refactoring utilities like &lt;em&gt;convert to arrow function&lt;/em&gt; or &lt;em&gt;rename variable&lt;/em&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bhDzR4Al--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.vsassets.io/v/M174_20200820.2/_content/Header/default_icon_128.png" alt="" width="128" height="128"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=ghmcadams.lintlens"&gt;LintLens&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;With this extension, metadata and usage information for ESLint rules will be displayed beside each rule.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KHnKY2yd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://eg2.gallerycdn.vsassets.io/extensions/eg2/vscode-npm-script/0.3.13/1596482164436/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="242" height="242"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=eg2.vscode-npm-script"&gt;npm&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;This extension let's you run your NPM scripts right from your &lt;code&gt;package.json&lt;/code&gt; file.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FkXzrEVq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://richie5um2.gallerycdn.vsassets.io/extensions/richie5um2/vscode-sort-json/1.18.0/1572459185562/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="128" height="128"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=richie5um2.vscode-sort-json"&gt;Sort JSON Objects&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;With this extension you can simply right click on a JSON object and sort the items inside.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qx9fItzW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ms-vscode.gallerycdn.vsassets.io/extensions/ms-vscode/vscode-typescript-tslint-plugin/1.2.3/1573518112983/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="120" height="120"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode.vscode-typescript-tslint-plugin"&gt;TSLint&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;This extension enables TSLint support for linting TypeScript files in VSCode.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hcj0XV4g--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://visualstudioexptteam.gallerycdn.vsassets.io/extensions/visualstudioexptteam/vscodeintellicode/1.2.10/1597190336810/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="128" height="128"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=VisualStudioExptTeam.vscodeintellicode"&gt;Visual Studio IntelliCode&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;Installing this extension will give you an even better IntelliSense which is AI-assisted.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JUF0O22I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pflannery.gallerycdn.vsassets.io/extensions/pflannery/vscode-versionlens/1.0.8/1592083401206/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="128" height="128"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=pflannery.vscode-versionlens"&gt;Version Lens&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;With Version Lens you can directly see the currently installed and the latest versions of a package in your &lt;code&gt;package.json&lt;/code&gt; file.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h3&gt;
  
  
  HTML/Handlebars/CSS/SASS/SCSS/LESS
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DwPxSX8m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pranaygp.gallerycdn.vsassets.io/extensions/pranaygp/vscode-css-peek/4.0.0/1595892543531/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="738" height="738"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=pranaygp.vscode-css-peek"&gt;CSS Peak&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;With this extension you can see and edit your CSS definitions from a related stylesheet directly from your HTML files.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3jmF1wvK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://zignd.gallerycdn.vsassets.io/extensions/zignd/html-css-class-completion/1.19.0/1558208838135/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="207" height="207"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=Zignd.html-css-class-completion"&gt;IntelliSense for CSS class names in HTML&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;This plugin gives you auto suggestions for CSS class names in your HTML templates.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2-s8FBCa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://maxvanderschee.gallerycdn.vsassets.io/extensions/maxvanderschee/web-accessibility/0.2.82/1584101774211/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="128" height="128"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=MaxvanderSchee.web-accessibility"&gt;Web Accessibility&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;Using this plugin helps you to find accessibility violations in your markup and displays hints for &lt;a href="https://www.w3.org/WAI/standards-guidelines/aria/"&gt;WAI-ARIA best practices&lt;/a&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gvPUaiZK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://stylelint.gallerycdn.vsassets.io/extensions/stylelint/vscode-stylelint/0.85.0/1597235740165/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="512" height="512"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=stylelint.vscode-stylelint"&gt;stylelint&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;Lint CSS/SCSS/SASS/Less Style definitions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h3&gt;
  
  
  Angular
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--G6pDgb8P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://obenjiro.gallerycdn.vsassets.io/extensions/obenjiro/arrr/0.1.0/1598284087751/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="128" height="128"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=obenjiro.arrr"&gt;Arrr&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;Arrr is a great extension for the Angular Framework. It lets you easily refactor code and for example mark some lines in the template and move it into a child component.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---KBaUSHF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://angular.gallerycdn.vsassets.io/extensions/angular/ng-template/0.1000.7/1596155297383/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="250" height="250"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=obenjiro.arrr"&gt;Angular Language Service&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;When you are developing an &lt;a href="https://angular.io"&gt;Angular&lt;/a&gt; app, this extension is essential: It gives you quick info, autocompletion and diagnostic information.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lbGTgois--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://github.com/SanderLedegen/vscode-angular-follow-selector/raw/master/images/logo.png" alt="" width="500" height="500"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=sanderledegen.angular-follow-selector"&gt;Angular Follow Selector&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;This extensions allows you to click on &lt;a href="https://angular.io"&gt;Angular&lt;/a&gt; selectors in the template  and get redirected to their definition in the component.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h3&gt;
  
  
  Vue.js
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ivA7XOnk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://octref.gallerycdn.vsassets.io/extensions/octref/vetur/0.26.1/1596770796928/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="400" height="400"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=octref.vetur"&gt;Vetur&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;Vetur gives you tooling for &lt;a href="https://vuejs.org/"&gt;Vue.js&lt;/a&gt; development.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fGAFBnMm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dariofuzinato.gallerycdn.vsassets.io/extensions/dariofuzinato/vue-peek/1.0.2/1503254564126/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="600" height="600"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=dariofuzinato.vue-peek"&gt;Vue Peak&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;This extension gives you &lt;em&gt;Go To Definition&lt;/em&gt; and &lt;em&gt;Peek Definition&lt;/em&gt; support for &lt;a href="https://vuejs.org/"&gt;Vue&lt;/a&gt; components.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h3&gt;
  
  
  Handlebars
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mBs8yxDH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andrejunges.gallerycdn.vsassets.io/extensions/andrejunges/handlebars/0.4.1/1526248443531/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="128" height="128"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=andrejunges.Handlebars"&gt;Handlebars&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;This extension provides you with code snippets for &lt;a href="https://handlebarsjs.com"&gt;Handlebars&lt;/a&gt; files as well as with syntax highlighting.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--R_mMjHZ4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://greenbyte.gallerycdn.vsassets.io/extensions/greenbyte/handlebars-preview/1.1.2/1597830971683/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="128" height="128"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=greenbyte.handlebars-preview"&gt;Handlebars Preview&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;With the Handlebars Preview extension you can put a JSON file right next to your Handlebars template and the plugin will inject the data into the template to display a preview of how it would look like.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jJHjNjGy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://bee.gallerycdn.vsassets.io/extensions/bee/git-temporal-vscode/1.0.0/1581641807688/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="128" height="128"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=bee.git-temporal-vscode"&gt;Git Temporal&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;Git Temporal is a great plugin for interactively searching in your git history based on a timeline. You can even mark ranges on a timeline to see all the changes in between the chosen time range.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ub19f_VB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://eamodio.gallerycdn.vsassets.io/extensions/eamodio/gitlens/10.2.2/1591818157905/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="128" height="128"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens"&gt;GitLens&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;Show the authorship of code lines right next to them. This can help you a lot if you may not understand some part of the code: just check out who created it and ask for support.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h3&gt;
  
  
  Other
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--g65yLdJ_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://sygene.gallerycdn.vsassets.io/extensions/sygene/auto-correct/0.2.1/1582635101990/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="128" height="128"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=sygene.auto-correct"&gt;Auto Correct&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;You may be like me and there are some words you'll always spell wrong or you making the same typo all the time. One of my common mistakes is to write &lt;code&gt;seperate&lt;/code&gt; instead of &lt;code&gt;separate&lt;/code&gt;. With this plugin you can define such words or patterns that will be automatically replaced with the correctly spelled word.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GZy9ev9T--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://streetsidesoftware.gallerycdn.vsassets.io/extensions/streetsidesoftware/code-spell-checker/1.9.0/1589974448396/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="880" height="880"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker"&gt;Code Spell Checker&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;With this extension your code is checked for common typos and such unknown words will be highlighted as you may know it from Microsoft Word or other text editor software.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OV34kZhA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://mikestead.gallerycdn.vsassets.io/extensions/mikestead/dotenv/1.0.1/1519894859412/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="200" height="200"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=mikestead.dotenv"&gt;DotENV&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;This extensions highlights the configuration in &lt;code&gt;.env&lt;/code&gt; files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3Pn2pJ_0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://grapecity.gallerycdn.vsassets.io/extensions/grapecity/gc-excelviewer/3.0.40/1597894261914/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="128" height="128"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=GrapeCity.gc-excelviewer"&gt;Excel Viewer&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;If you open and edit CSV or Excel files from VSCode, you will probably need this extension. This allows you to display the data in a formatted table that you can sort and filter.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YNb5ZihJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://msjsdiag.gallerycdn.vsassets.io/extensions/msjsdiag/debugger-for-chrome/4.12.10/1597702740002/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="256" height="256"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-chrome"&gt;Debugger for Chrome&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;This debugger extensions allows you to debug your JavaScript code in the Google Chrome browser from your code editor.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--i7-iTYs9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ms-azuretools.gallerycdn.vsassets.io/extensions/ms-azuretools/vscode-docker/1.5.0/1597688601690/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="128" height="128"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-docker"&gt;Docker&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;The Docker extension easily lets you create, manage, and debug your applications containerized with Docker. It also gives you IntelliSense for your related Docker configuration files.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---9XDcG8I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://editorconfig.gallerycdn.vsassets.io/extensions/editorconfig/editorconfig/0.15.1/1590371230963/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="120" height="120"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig"&gt;EditorConfig&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;This plugin will check your workspace for an &lt;a href="https://editorconfig.org"&gt;EditorConfig&lt;/a&gt; configuration file and applies these settings to your workspace.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BvIAhKI8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ritwickdey.gallerycdn.vsassets.io/extensions/ritwickdey/liveserver/5.6.1/1555497731217/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="256" height="256"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer"&gt;Live Server&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;With the Live Server extension you can open an HTML file in the browser and the server watches for changes and refreshes the Browser preview.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rY4iFrc2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://nrwl.gallerycdn.vsassets.io/extensions/nrwl/angular-console/13.0.0/1595622774663/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="880" height="880"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=nrwl.angular-console"&gt;Nx Console&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;This plugin gives you a UI for &lt;a href="https://nx.dev"&gt;Nx Monorepos&lt;/a&gt; and the Nx CLI.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XaQDzP1v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://christian-kohler.gallerycdn.vsassets.io/extensions/christian-kohler/path-intellisense/2.2.1/1591957111022/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="128" height="128"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=christian-kohler.path-intellisense"&gt;Path Intellisense&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;This plugin brings you autocompletion for filenames.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gaSyCS_s--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pnp.gallerycdn.vsassets.io/extensions/pnp/polacode/0.3.4/1569601471865/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="264" height="264"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=pnp.polacode"&gt;Polacode&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;With Polacode you can mark code lines and create screenshots from the selection. This is great for e.g. presentations or sharing stuff on Twitter.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zQongRpm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://esbenp.gallerycdn.vsassets.io/extensions/esbenp/prettier-vscode/5.2.1/1598366482789/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="128" height="128"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode"&gt;Prettier&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;&lt;a href="https://prettier.io"&gt;Prettier&lt;/a&gt; is – in my opinion – the best code formatter especially when working with JavaScript / TypeScript. The extension for Prettier will allow you to set up Prettier as default formatter for VSCode or just for specific programming languages.&lt;br&gt;&lt;pre&gt;&lt;code&gt;{&lt;br&gt;  "editor.defaultFormatter": "esbenp.prettier-vscode",  "[javascript]": {&lt;br&gt;    "editor.defaultFormatter": "esbenp.prettier-vscode"&lt;br&gt;  }&lt;br&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qJ66jZS8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://louiswt.gallerycdn.vsassets.io/extensions/louiswt/regexp-preview/0.1.5/1583773383502/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="240" height="240"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=louiswt.regexp-preview&amp;amp;WT.mc_id=devcloud-00000-cxa"&gt;Regexp Explain&lt;/a&gt;&lt;/strong&gt;&lt;br&gt; Running this extension will let you explore regular expressions visually in a realtime preivew editor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7sSL6_OV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://knisterpeter.gallerycdn.vsassets.io/extensions/knisterpeter/vscode-commitizen/0.9.3/1595315799607/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="128" height="128"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=KnisterPeter.vscode-commitizen"&gt;Visual Studio Code Commitizen Support&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;This plugin adds a command panel for creating &lt;a href="https://www.conventionalcommits.org"&gt;Conventional Commits&lt;/a&gt; with support.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k6nKxaZa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://henrynguyen5-vsc.gallerycdn.vsassets.io/extensions/henrynguyen5-vsc/vsc-nvm/0.0.3/1549657966675/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="880" height="880"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=henrynguyen5-vsc.vsc-nvm"&gt;vsc-nvm&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;Automatically use the right Node.js version form the NVM.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oSk7_WVN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://redhat.gallerycdn.vsassets.io/extensions/redhat/vscode-yaml/0.10.0/1597935539888/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="128" height="128"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml"&gt;YAML&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;This extension gives you a comprehensive YAML language support.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vdV-z5nX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://zeplin.gallerycdn.vsassets.io/extensions/zeplin/zeplin/2.1.0/1592227616388/Microsoft.VisualStudio.Services.Icons.Default" alt="" width="256" height="256"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=zeplin.zeplin"&gt;Zeplin&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;If you work with &lt;a href="https://zeplin.io"&gt;Zeplin&lt;/a&gt;, this official plugin provides you with quick access to your designs.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h2&gt;
  
  
  Google Chrome Extensions
&lt;/h2&gt;

&lt;p&gt;Google Chrome is great and it already brings a lot of possibilities that help developers to improve their web apps.&lt;br&gt;
However, there are some really great plugins I am using that I can recommend:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2N_3oJCQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/My9ZJOQeBDEqm2Snb3zQCspE4neCdXNuv6dYuZ9z1DN6T2P52Df7jBNLYA2SKxqN4e6M_V33KESTGknFXcYEXWzJ%3Dw128-h128-e365-rj-sc0x00ffffff" alt="" width="128" height="128"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://chrome.google.com/webstore/detail/angular-devtools/ienfalfjdbdpebioblfackkekamfmbnh"&gt;Angular DevTools&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;A must have addon for all Angular Developers that will help you profile and debug and optimize your Angular app.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YYTjmqP3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/nJRS4ToYWi4cJ_cSgT884h1Ixv0bYD4MWYq3aAa85JY8OKgluXgN7zYd_hbLTNKW8zPfonC9Ig%3Dw128-h128-e365" alt="" width="128" height="128"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://chrome.google.com/webstore/detail/aqua-buddy/kidoeajcechhpbhmbeligfiakhhlchom"&gt;Aqua Buddy&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;Stay hydrated by using Aqua Buddy! It will notify you to drink some water frequently.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZmWZnjOD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/CzEnlG42oD6E_kIGkLujYfQnz43jx8ml2ezY-rFloQgNDPiB0PRJsLWwn6N4LJZsdpEAHlebDQ%3Dw128-h128-e365" alt="" width="128" height="128"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd"&gt;Axe&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;Axe helps you to improve the grade of accessibility of your site.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--831j3lHT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/8BLPoOrRiXqc_lxLjwjsaqwp3LwfW-1XDf1BmWUVDe5DfqgopAbgKVhZqnfz1IVmMIjHy_u1%3Dw128-h128-e365" alt="" width="128" height="128"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://chrome.google.com/webstore/detail/chrome-capture-screenshot/ggaabchcecdbomdcnbahdfddfikjmphe?hl=en"&gt;Chrome Capture&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;This extension lets you record and share screenshots, videos and GIFs from a website.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uipbY1pg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/fSH1EIpxkNfV3-37vG0lVuCuDCMIhRRQS88LRVA6NzazUdB5F7447__B8gEuxp3uH0ecKSCAs7fzpDcLNdCO0DlQ%3Dw128-h128-e365-rj-sc0x00ffffff" alt="" width="128" height="128"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://chrome.google.com/webstore/detail/chrome-regex-search/bpelaihoicobbkgmhcbikncnpacdbknn"&gt;Chrome Regex Search&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;Ever wanted to search on a site for more complex expressions? With this extension you can search a sites content by entering regular expressions.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WMO5Io8_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/xk2lp5Tkh-hN1SNWxb1lsxyitzwrpsmzdruqEcuymQIq-9DOhMlhkNz14MSJoWvu0jNnCfRX_SZEvpBwtIY5k8jNDQ%3Dw128-h128-e365-rj-sc0x00ffffff" alt="" width="128" height="128"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://chrome.google.com/webstore/detail/export-selective-bookmark/ahgbiciilcpclcekhegbhofljoolnfei"&gt;Export Selective Bookmarks&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;This extensions lets you select specific directories and bookmarks from all your bookmarks to export them. Perfect if you want to help colleagues to onboard in your team or project.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--R8orww_U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/u_2WHT7oINbcGWWN0MRxogjDHAPYB9Izk_MUdcF0yukvabP9uEeIGJaFpg2Ac9vv5mrm7A5ZVw%3Dw128-h128-e365" alt="" width="128" height="128"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://chrome.google.com/webstore/detail/json-viewer-awesome/iemadiahhbebdklepanmkjenfdebfpfe"&gt;JSON Viewer Awesome&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;This extension will automatically detect and format JSON files once you open them by calling the URL.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--w4862mNx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/_6Y4tjYdppsZJlWOnAzFy2A8JjGwJpQOvoKocTYMfl66bTJg20mJ6pojdQaUGtvXa9HYurDChQ%3Dw128-h128-e365" alt="" width="128" height="128"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://chrome.google.com/webstore/detail/lighthouse/blipmdconlkpinefehnmjammfjpmpbjk"&gt;Lighthouse&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;Lighthouse analyzes your site for performance, accessibility and other common things and gives you a site score as well as some useful information how to improve your site.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_V3ZENjo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/JsB6yUqV6WbFREmfjpdIY6ctrf3IKZfdhWa1BbHiD0MwHvilHzJJdM5TGycosq8_rEdf3FFYXA%3Dw128-h128-e365" alt="" width="128" height="128"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://chrome.google.com/webstore/detail/pesticide-for-chrome/bblbgcheenepgnnajgfpiicnbbdmmooh"&gt;Pesticide&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;Activating these extension lets you outline all elements on a site so that you can easier analyze paddings, margins and borders.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7cXdDhEw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/ZccCYQft-IxBg8e8B2FsZTPXyu9kYaTKGX4vxVoTVztguJVsrhTlJo_qVd1H1tesz03BxwMdYg%3Dw128-h128-e365" alt="" width="128" height="128"&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;a href="https://chrome.google.com/webstore/detail/web-developer/bfbameneiokkgbdmiekhjnmfkcnldhhm"&gt;Web Developer&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;With this extension you will get a toolbox for website manipulation and testing tools. You can e.g. easily disable JavaScript or styles to see how your site behaves.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h2&gt;
  
  
  Scripts and other tools
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Dotfiles
&lt;/h3&gt;

&lt;p&gt;Personally, I like to keep my Mac up-to-date and therefore I will install most of my software via the &lt;a href="https://brew.sh"&gt;Homebrew&lt;/a&gt; package manager.&lt;br&gt;
Also, to be more efficient I use the &lt;a href="https://github.com/ohmyzsh/ohmyzsh"&gt;&lt;code&gt;oh-my-zsh&lt;/code&gt;&lt;/a&gt; framework for managing my shell configuration.&lt;br&gt;
Most of my configurations is also published in my GitHub repository called &lt;a href="https://github.com/d-koppenhagen/.dotfiles"&gt;&lt;code&gt;.dotfiles&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Exclude all &lt;code&gt;node_modules&lt;/code&gt; from TimeMachine backups (macOS only)
&lt;/h3&gt;

&lt;p&gt;One thing I learned during my career is: &lt;strong&gt;backup, backup, backup&lt;/strong&gt;.&lt;br&gt;
It's better to have one more backup than data loss.&lt;br&gt;
Fortunately, on a macOS system it's possible to set up Apple's TimeMachine for creating continuous system backups.&lt;br&gt;
However, backing up everything takes a lot of time and there are things you don't need to back up like directories syncing with a cloud provider like Dropbox, Sharepoint, etc.&lt;br&gt;
Those directories can easily be &lt;a href="https://osxdaily.com/2012/01/27/exclude-folders-from-time-machine-backups/"&gt;excluded from the TimeMachine backup by configuration&lt;/a&gt;.&lt;br&gt;
This will keep the backups smaller and also in case of a restore, the system ready to use after a much shorter time.&lt;br&gt;
But what about &lt;code&gt;node_modules&lt;/code&gt; directories?&lt;br&gt;
For sure: You can exclude them in the same way, but this is very sophisticated and you always need to remember this step once you create a new project.&lt;br&gt;
Therefore, I am using a simple script.&lt;br&gt;
It looks for all &lt;code&gt;node_modules&lt;/code&gt; directories in my development directory (&lt;code&gt;~/dev&lt;/code&gt; in my case) and excludes them from the backup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# exclude all `node_modules` folders within the dev directory&lt;/span&gt;
find &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/dev"&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'node_modules'&lt;/span&gt; &lt;span class="nt"&gt;-prune&lt;/span&gt; &lt;span class="nt"&gt;-type&lt;/span&gt; d &lt;span class="nt"&gt;-exec&lt;/span&gt; tmutil addexclusion &lt;span class="o"&gt;{}&lt;/span&gt; &lt;span class="se"&gt;\;&lt;/span&gt; &lt;span class="nt"&gt;-exec&lt;/span&gt; tmutil isexcluded &lt;span class="o"&gt;{}&lt;/span&gt; &lt;span class="se"&gt;\;&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Done. The excluded files won't be part of a time machine backup anymore."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To be sure the script updates the list of excluded directories frequently, I added a cronjob:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;crontab &lt;span class="nt"&gt;-e&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The actual cronjob config is the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;0 12 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;  &lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="nv"&gt;$HOME&lt;/span&gt;/dev/.dotfiles &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ./time-machine-excludes.sh &lt;span class="c"&gt;# every day at 12:00&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Feedback
&lt;/h2&gt;

&lt;p&gt;Now as you know my dev setup, it's your turn! Is there any great tool or plugin you can recommend?&lt;br&gt;
Then just contact me via &lt;a href="//mailto:mail@k9n.dev"&gt;E-Mail&lt;/a&gt; or &lt;a href="https://twitter.com/d_koppenhagen"&gt;Twitter&lt;/a&gt; and let me know!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thank you&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Special thanks goes to &lt;a href="https://twitter.com/fmalcher01"&gt;Ferdinand Malcher&lt;/a&gt; for revising this article.&lt;/p&gt;




&lt;p&gt;&lt;span&gt;Photo by &lt;a href="https://unsplash.com/@toddquackenbush"&gt;Todd Quackenbush&lt;/a&gt; on &lt;a href="https://unsplash.com/"&gt;Unsplash&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Dig deeper into static site generation with Scully and use the most out of it</title>
      <dc:creator>Danny Koppenhagen</dc:creator>
      <pubDate>Mon, 02 Mar 2020 19:14:51 +0000</pubDate>
      <link>https://dev.to/dkoppenhagen/dig-deeper-into-static-site-generation-with-scully-and-use-the-most-out-of-it-4cn5</link>
      <guid>https://dev.to/dkoppenhagen/dig-deeper-into-static-site-generation-with-scully-and-use-the-most-out-of-it-4cn5</guid>
      <description>&lt;h1&gt;
  
  
  Dig deeper into static site generation with &lt;em&gt;Scully&lt;/em&gt; and use the most out of it
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;If you haven't heard about &lt;em&gt;Scully&lt;/em&gt; yet, you should first check out my introduction article about it: &lt;em&gt;&lt;a href="https://k9n.dev/blog/2020-01-angular-scully"&gt;»Create powerful fast pre-rendered Angular Apps using Scully static site generator«&lt;/a&gt;&lt;/em&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In my &lt;a href="https://k9n.dev/blog/2020-01-angular-scully"&gt;last blog post&lt;/a&gt; I gave you a short introduction to &lt;em&gt;Scully&lt;/em&gt; and how to easily set up a very simple blogging website that is server-side rendered and ready to be shipped for production.&lt;br&gt;
In the following article I will introduce some more advanced things you can do with &lt;em&gt;Scully&lt;/em&gt;.&lt;br&gt;
You will learn how you can setup a custom Markdown module or even use Asciidoc instead of Markdown.&lt;br&gt;
I will guide you through the process of how to handle protected routes using a custom route plugin.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;This blog post is based on versions:&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@scullyio/ng-lib: 1.1.1
@scullyio/init: 1.1.4
@scullyio/scully: 1.1.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;



&lt;h2&gt;
  
  
  Generate a post with a meta data template
&lt;/h2&gt;

&lt;p&gt;As we already created our blogging site using &lt;em&gt;Scully&lt;/em&gt;, we want to fill it with content now.&lt;br&gt;
We already learned how we can use the &lt;code&gt;@scullyio/init:post&lt;/code&gt; schematic to easily generate a new blog post.&lt;br&gt;
Often posts do not only need the content, but also some meta information like &lt;code&gt;thumbnail&lt;/code&gt; or &lt;code&gt;author&lt;/code&gt;.&lt;br&gt;
This meta information can be processed by the &lt;code&gt;ScullyRouteService&lt;/code&gt; and it will be converted to JSON.&lt;br&gt;
It can be quite handy to always remember to add such information right after creating a new post.&lt;br&gt;
To make things easier we can specify a YAML template file with the meta information that will always be added when creating a new blog post using the schematic, like the following one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;fill in a short description for the overview page&amp;gt;&lt;/span&gt;
&lt;span class="na"&gt;published&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
&lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Danny Koppenhagen&lt;/span&gt;
  &lt;span class="na"&gt;mail&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mail@k9n.dev&lt;/span&gt;
&lt;span class="na"&gt;updated&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dd.mm.yyyy&lt;/span&gt;
&lt;span class="na"&gt;keywords&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Angular&lt;/span&gt;
&lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;en&lt;/span&gt;
&lt;span class="na"&gt;thumbnail&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;assets/images/default.jpg&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can use the template when calling the &lt;code&gt;@scullyio/init:post&lt;/code&gt; schematic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ng g @scullyio/init:post &lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"a new post"&lt;/span&gt; &lt;span class="nt"&gt;--meta-data-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;meta.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When we check our &lt;code&gt;blog&lt;/code&gt; directory now we will see that the schematic added our YAML template to the meta data section of the newly created post file &lt;code&gt;a-new-post.md&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you have trouble remembering to add the &lt;code&gt;meta-data-file&lt;/code&gt; option, just add a script to your &lt;code&gt;package.json&lt;/code&gt; without the &lt;code&gt;name&lt;/code&gt; option.&lt;br&gt;
When you call the script using &lt;code&gt;npm run &amp;lt;script-name&amp;gt;&lt;/code&gt; you will be prompted to input the file name.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Generate a custom Markdown module
&lt;/h2&gt;

&lt;p&gt;Let's assume we want to add another module to our blogging website.&lt;br&gt;
We want to have a &lt;code&gt;projects&lt;/code&gt; section in our site that lists some information about current projects we are working on.&lt;br&gt;
Like for our &lt;code&gt;blog&lt;/code&gt; section, we want to easily write our content using Markdown.&lt;br&gt;
To do so, we can use the &lt;code&gt;@scullyio/init:markdown&lt;/code&gt; schematic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ng g @scullyio/init:markdown &lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;projects &lt;span class="nt"&gt;--slug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;projectId &lt;span class="nt"&gt;--sourceDir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;projects &lt;span class="nt"&gt;--route&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;projects
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's have a look at the options we set:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;name&lt;/code&gt;: This is the base name for the generated Angular module that &lt;em&gt;Scully&lt;/em&gt; created for us.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;slug&lt;/code&gt;: Here we define the placeholder name for the URL that will be filled with the basename of the Markdown files.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sourceDir&lt;/code&gt;: That's where we will store our Markdown files whose content is rendered by the &lt;em&gt;Scully&lt;/em&gt; Markdown file plugin.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;route&lt;/code&gt;: This is the name for the route before the &lt;code&gt;:slug&lt;/code&gt; in the URLs where we can see our rendered content later.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Good to know: Under the hood the &lt;code&gt;@scullyio/init:blog&lt;/code&gt; schematic just calls &lt;code&gt;@scullyio/init:markdown&lt;/code&gt; with default options set. So in fact it's just a shortcut.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The basic things we need for our projects page are now available.&lt;br&gt;
Let's have a look at it and see if it's working:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run build                   &lt;span class="c"&gt;# Angular build&lt;/span&gt;
npm run scully &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--scanRoutes&lt;/span&gt;  &lt;span class="c"&gt;# generate static build and force checking new routes&lt;/span&gt;
npm run scully serve            &lt;span class="c"&gt;# serve the scully results&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Rj5CEYRp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://k9n.dev/assets/images/blog/scully/scully-markdown-projects.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Rj5CEYRp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://k9n.dev/assets/images/blog/scully/scully-markdown-projects.png" alt="the initial projects post generated with the Markdown schematic"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The &lt;code&gt;AsciiDoc&lt;/code&gt; File Handler Plugin
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Scully&lt;/em&gt; provides another &lt;em&gt;File Handler Plugin&lt;/em&gt; out-of-the-box: The &lt;em&gt;AsciiDoc&lt;/em&gt; plugin.&lt;br&gt;
When you want to put the generated post files in a specific directory (not &lt;code&gt;blog&lt;/code&gt;), you can define this via the &lt;code&gt;target&lt;/code&gt; option.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ng g @scullyio/init:post &lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"asciidoc example"&lt;/span&gt; &lt;span class="nt"&gt;--target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;projects
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generated file will be a Markdown file initially.&lt;br&gt;
Let's change the file extension, rename it to &lt;code&gt;*.adoc&lt;/code&gt; and add a bit of content after it has been generated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;:title: 2020-01-21-projects
:description: blog description
:published: false

= 2020-01-21-projects

Let's show some source code!

.index.html
[#src-listing]
[source,html]
----
&amp;lt;div&amp;gt;
  &amp;lt;span&amp;gt;Hello World!&amp;lt;/span&amp;gt;
&amp;lt;/div&amp;gt;
----
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally we build our project again and see if it works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run build                   &lt;span class="c"&gt;# Angular build&lt;/span&gt;
npm run scully &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--scanRoutes&lt;/span&gt;  &lt;span class="c"&gt;# generate static build and force checking new routes&lt;/span&gt;
npm run scully serve            &lt;span class="c"&gt;# serve the scully results&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great, as we can see: AsciiDoc files will be rendered as well out-of-the-box.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lbCj55Ry--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://k9n.dev/assets/images/blog/scully/scully-asciidoc-projects.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lbCj55Ry--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://k9n.dev/assets/images/blog/scully/scully-asciidoc-projects.png" alt="a scully rendered asciidoc file"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also define your own File Handler Plugin for other content formats.&lt;br&gt;
Check out the &lt;a href="https://scully.io/docs/plugins#file-plugin"&gt;official docs&lt;/a&gt; for it to see how it works.&lt;/p&gt;
&lt;h2&gt;
  
  
  Protect your routes with a custom plugin
&lt;/h2&gt;

&lt;p&gt;Let's assume we have a protected section at our site that should only be visible for specific users.&lt;br&gt;
For sure we can secure this space using an &lt;a href="https://angular.io/guide/router#milestone-5-route-guards"&gt;Angular Route Guard&lt;/a&gt; that checks if we have the correct permissions to see the pages.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Scully&lt;/em&gt; will by default try to identify all available app routes.&lt;br&gt;
In fact it will also try to visit the protected pages and pre-render the result.&lt;br&gt;
When &lt;em&gt;Scully&lt;/em&gt; tries to do this, the Angular route guard kicks in and redirects us to an error or login page.&lt;br&gt;
The page shown after the redirect is the page &lt;em&gt;Scully&lt;/em&gt; will see and render.&lt;br&gt;
This default behaviour is pretty okay, as &lt;em&gt;Scully&lt;/em&gt; won't expose any protected information by creating static content from the protected data.&lt;br&gt;
However, on the other hand, we don't want to pre-render such pages at all, so we need a way to tell &lt;em&gt;Scully&lt;/em&gt; what pages to exclude from the rendering.&lt;br&gt;
Another scenario you can imagine is when a page displays a prompt or a confirm dialog.&lt;br&gt;
When &lt;em&gt;Scully&lt;/em&gt; tries to render such pages it runs into a timeout and cannot render the page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
Puppeteer error while rendering "/secure" TimeoutError: Navigation timeout of 30000 ms exceeded
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To prevent &lt;em&gt;Scully&lt;/em&gt; from rendering specific pages we can simply create a custom plugin that will skip some routes.&lt;/p&gt;

&lt;p&gt;To do so, we will create a new directory &lt;code&gt;extraPlugin&lt;/code&gt; with the file &lt;code&gt;skip.js&lt;/code&gt; inside:&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;registerPlugin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;yellow&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@scullyio/scully&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;skipPlugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Skip Route "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;yellow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;route&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="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolve&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;validator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;conf&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;registerPlugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;router&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;skip&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;skipPlugin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;validator&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;skipPlugin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;skipPlugin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will import the function &lt;code&gt;registerPlugin()&lt;/code&gt; which will register a new router plugin called &lt;code&gt;skip&lt;/code&gt;.&lt;br&gt;
The last parameter is the plugin function &lt;code&gt;skipPlugin()&lt;/code&gt; that will return a promise resolving the routes.&lt;br&gt;
It receives the route and options for the route that should be handled.&lt;br&gt;
We will simply return an empty array as we won't proceed routes handled by the plugin.&lt;br&gt;
We can use the exported &lt;code&gt;log()&lt;/code&gt; function from &lt;em&gt;Scully&lt;/em&gt; to log the action in a nice way.&lt;/p&gt;

&lt;p&gt;Last but not least we will use the &lt;code&gt;skip&lt;/code&gt; plugin in our &lt;code&gt;scully.scully-blog.config.ts&lt;/code&gt; configuration file and tell the plugin which routes to handle:&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;ScullyConfig&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;@scullyio/scully&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./extraPlugin/skip&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;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ScullyConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/secure&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;skip&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Checking the plugin by running &lt;code&gt;npm run scully&lt;/code&gt; will output us the following result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; ☺   new Angular build imported
 ☺   Started servers in background
Finding all routes in application.
...
Skip Route "/secure"
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Perfect! As you can see the route is ignored by &lt;em&gt;Scully&lt;/em&gt; now.&lt;/p&gt;

&lt;p&gt;You can have a look at a more detailed example in my &lt;a href="https://github.com/d-koppenhagen/scully-blog-example"&gt;scully-blog-example&lt;/a&gt; repository.&lt;/p&gt;

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

&lt;p&gt;In this follow-up article you learned how to add a custom Markdown module to &lt;em&gt;Scully&lt;/em&gt; and how you can use the AsciiDoc plugin for rendering &lt;code&gt;adoc&lt;/code&gt; files.&lt;br&gt;
What is more, you can now handle protected routes by using a custom &lt;em&gt;Scully&lt;/em&gt; route plugin.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thank you&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Special thanks go to &lt;a href="https://twitter.com/jorgeucano"&gt;Jorge Cano&lt;/a&gt; from the &lt;em&gt;Scully&lt;/em&gt; core team and &lt;a href="https://twitter.com/fmalcher01"&gt;Ferdinand Malcher&lt;/a&gt; for revising this article.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Create powerful fast pre-rendered Angular Apps using Scully static site generator</title>
      <dc:creator>Danny Koppenhagen</dc:creator>
      <pubDate>Wed, 01 Jan 2020 17:12:44 +0000</pubDate>
      <link>https://dev.to/dkoppenhagen/create-powerfull-fast-pre-rendered-angular-apps-using-scully-static-site-generator-31fb</link>
      <guid>https://dev.to/dkoppenhagen/create-powerfull-fast-pre-rendered-angular-apps-using-scully-static-site-generator-31fb</guid>
      <description>&lt;h1&gt;
  
  
  Create powerful fast pre-rendered Angular Apps using &lt;em&gt;Scully&lt;/em&gt; static site generator
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;You probably heard of the JAMStack. It's a new way of building websites and apps via static site generators that deliver better performance and higher security. There have been tools for many platforms, but surprisingly not yet for Angular. These times are finally over. With this blog post, I want to show you how you can easily create an Angular blogging app by to pre-render your complete app.&lt;/strong&gt;&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;On &lt;em&gt;Dec 16, 2019&lt;/em&gt; the static site generator &lt;em&gt;Scully&lt;/em&gt; for Angular &lt;a href="https://www.youtube.com/watch?v=Sh37rIUL-d4" rel="noopener noreferrer"&gt;was presented&lt;/a&gt;.&lt;br&gt;
&lt;em&gt;Scully&lt;/em&gt; automatically detects all app routes and creates static sites out of it that are ready to ship for production.&lt;br&gt;
This blog post is based on versions of Angular and Scully:&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"@angular/core": "~13.0.0",
"@angular/cli": "~13.0.3",
"@scullyio/init": "^2.0.5",
"@scullyio/ng-lib": "^2.0.0",
"@scullyio/scully": "^2.0.0",
"@scullyio/scully-plugin-puppeteer": "^2.0.0",
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;



&lt;h2&gt;
  
  
  About Scully
&lt;/h2&gt;

&lt;p&gt;Scully is a static site generator (SSG) for Angular apps.&lt;br&gt;
It analyses a compiled Angular app and detects all the routes of the app.&lt;br&gt;
It will then call every route it found, visit the page in the browser, renders the page and finally put the static rendered page to the file system.&lt;br&gt;
This process is also known as &lt;strong&gt;pre-rendering&lt;/strong&gt; – but with a new approach.&lt;br&gt;
The result compiled and pre-rendered app ready for shipping to your web server.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Good to know:&lt;/strong&gt; &lt;em&gt;Scully&lt;/em&gt; does not use &lt;a href="https://angular.io/guide/universal" rel="noopener noreferrer"&gt;Angular Universal&lt;/a&gt; for the pre-rendering.&lt;br&gt;
It uses a Chromium browser to visit and check all routes it found.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;All pre-rendered pages contain just plain HTML and CSS.&lt;br&gt;
In fact, when deploying it, a user will be able to instantly access all routes and see the content with almost no delay.&lt;br&gt;
The resulting sites are very small static sites (just a few KBs) so that even the access from a mobile device with a very low bandwidth is pretty fast.&lt;br&gt;
It's significantly faster compared to the hundreds of KBs that you are downloading when calling a “normal” Angular app on initial load.&lt;/p&gt;

&lt;p&gt;But that’s not all: Once the pre-rendered page is shipped to the user, &lt;em&gt;Scully&lt;/em&gt; loads and bootstraps the “real” Angular app  in the background on top of the existing view.&lt;br&gt;
In fact &lt;em&gt;Scully&lt;/em&gt; will unite two great things:&lt;br&gt;
The power of pre-rendering and very fast access to sites and the power of a fully functional Single Page Application (SPA) written in Angular.&lt;/p&gt;
&lt;h2&gt;
  
  
  Get started
&lt;/h2&gt;

&lt;p&gt;The first thing we have to do is to set up our Angular app.&lt;br&gt;
As &lt;em&gt;Scully&lt;/em&gt; detects the content from the routes, we need to configure the Angular router as well.&lt;br&gt;
Therefore, we add the appropriate flag &lt;code&gt;--routing&lt;/code&gt; (we can also choose this option when the CLI prompts us).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx &lt;span class="nt"&gt;-p&lt;/span&gt; @angular/cli ng new scully-blog &lt;span class="nt"&gt;--routing&lt;/span&gt; &lt;span class="c"&gt;# create an angular workspace&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;scully-blog  &lt;span class="c"&gt;# navigate into the project&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next step is to set up our static site generator &lt;em&gt;Scully&lt;/em&gt;.&lt;br&gt;
Therefore, we are using the provided Angular schematic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ng add @scullyio/init  &lt;span class="c"&gt;# add Scully to the project&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Et voilà here it is: We now have a very minimalistic Angular app that uses the power of &lt;em&gt;Scully&lt;/em&gt; to automatically find all app routes, visit them and generate static pages out of them.&lt;br&gt;
It's ready for us to preview.&lt;br&gt;
Let's try it out by building our site and running &lt;em&gt;Scully&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run build     &lt;span class="c"&gt;# build our Angular app&lt;/span&gt;
npx scully        &lt;span class="c"&gt;# let Scully run over our app and build it&lt;/span&gt;
npx scully serve  &lt;span class="c"&gt;# serve the scully results&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Scully&lt;/em&gt; will run only once by default. To let &lt;em&gt;Scully&lt;/em&gt; run and watch for file changes, just add the &lt;code&gt;--watch&lt;/code&gt; option (&lt;code&gt;npx scully --watch&lt;/code&gt;).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After &lt;em&gt;Scully&lt;/em&gt; has checked our app, it will add the generated static assets to our &lt;code&gt;dist/static&lt;/code&gt; directory by default.&lt;br&gt;
Let's quickly compare the result generated from &lt;em&gt;Scully&lt;/em&gt; with the result from the initial Angular build (&lt;code&gt;dist/scully-blog&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dist/
┣ scully-blog/
┃ ┣ assets/
┃ ┣ ...
┃ ┗ styles.ef46db3751d8e999.css
┗ static/
  ┣ assets/
  ┃ ┗ scully-routes.json
  ┣ ...
  ┗ styles.ef46db3751d8e999.css
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we take a look at it, except of the file &lt;code&gt;scully-routes.json&lt;/code&gt;, that contains the configured routes used by &lt;em&gt;Scully&lt;/em&gt;, we don't see any differences between the two builds.&lt;br&gt;
This is because currently we only have the root route configured, and no further content was created.&lt;/p&gt;

&lt;p&gt;Nonetheless, when running &lt;code&gt;npx scully serve&lt;/code&gt; or &lt;code&gt;npx scully --watch&lt;/code&gt; we can check out the result by visiting the following URL: &lt;code&gt;localhost:1668&lt;/code&gt;.&lt;br&gt;
This server serves the static generated pages from the &lt;code&gt;dist/static&lt;/code&gt; directory like a normal web server (e.g. &lt;em&gt;nginx&lt;/em&gt; or &lt;em&gt;apache&lt;/em&gt;).&lt;/p&gt;
&lt;h2&gt;
  
  
  The &lt;code&gt;ScullyLibModule&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;You may have realized, that after running the &lt;em&gt;Scully&lt;/em&gt; schematic, the &lt;code&gt;ScullyLibModule&lt;/code&gt; has been added to your &lt;code&gt;AppComponent&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&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;ScullyLibModule&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;@scullyio/ng-lib&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;NgModule&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="nx"&gt;ScullyLibModule&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppModule&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 module is used by &lt;em&gt;Scully&lt;/em&gt; to hook into the angular router and to determine once the page &lt;em&gt;Scully&lt;/em&gt; tries to enter is fully loaded and ready to be rendered by using the &lt;code&gt;IdleMonitorService&lt;/code&gt; from &lt;em&gt;Scully&lt;/em&gt; internally.&lt;br&gt;
If we remove the import of the module, &lt;em&gt;Scully&lt;/em&gt; will still work, but it takes much longer to render your site as it will use a timeout for accessing the pages.&lt;br&gt;
So in that case even if a page has been fully loaded, &lt;em&gt;Scully&lt;/em&gt; would wait until the timer is expired.&lt;/p&gt;
&lt;h2&gt;
  
  
  Turn it into a blog
&lt;/h2&gt;

&lt;p&gt;Let’s go a bit further and turn our site into a simple blog that will render our blog posts from separate Markdown documents.&lt;br&gt;
&lt;em&gt;Scully&lt;/em&gt; brings this feature out of the box, and it’s very easy to set it up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ng g @scullyio/init:blog                      &lt;span class="c"&gt;# setup up the `BlogModule` and related sources&lt;/span&gt;
ng g @scullyio/init:post &lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"First post"&lt;/span&gt;  &lt;span class="c"&gt;# create a new blog post&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After these two steps we can see that &lt;em&gt;Scully&lt;/em&gt; has now added the &lt;code&gt;blog&lt;/code&gt; directory to our project root.&lt;br&gt;
Here we can find the markdown files for creating the blog posts — one file for each post.&lt;br&gt;
We now have two files there: The initially created example file from &lt;em&gt;Scully&lt;/em&gt; and this one we created with &lt;code&gt;ng g @scullyio/init:post&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Let's go further
&lt;/h2&gt;

&lt;p&gt;Now that we've got Scully installed and working, let's modify our Angular app to look more like an actual blog, and not just like the default Angular app.&lt;br&gt;
Therefore, we want to get rid of the Angular auto generated content in the &lt;code&gt;AppComponent&lt;/code&gt; first.&lt;br&gt;
We can simply delete all the content of &lt;code&gt;app.component.html&lt;/code&gt; except of the &lt;code&gt;router-outlet&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;router-outlet&amp;gt;&amp;lt;/router-outlet&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s run the build again and have a look at the results.&lt;br&gt;
Scully assumes by default the route configuration hasn't changed meanwhile, and it can happen that it's not detecting the new bog entry we just created.&lt;br&gt;
To be sure it will re-scan the routes, we will pass through the parameter &lt;code&gt;--scan&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run build       &lt;span class="c"&gt;# Angular build&lt;/span&gt;
npx scully &lt;span class="nt"&gt;--scan&lt;/span&gt;   &lt;span class="c"&gt;# generate static build and force checking new routes&lt;/span&gt;
npx scully serve    &lt;span class="c"&gt;# serve the scully results&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When checking out our &lt;code&gt;dist/static&lt;/code&gt; directory we can see that there are new subdirectories for the routes of our static blogging sites.&lt;br&gt;
But what's that: When we will check the directory &lt;code&gt;dist/static/blog/&lt;/code&gt;, we see somewhat like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;blog/
┣ ___UNPUBLISHED___k9pg4tmo_2DDScsUiieFlld4R2FwvnJHEBJXcgulw
  ┗ index.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This feels strange, doesn't it?&lt;br&gt;
But Checking the content of the file &lt;code&gt;index.html&lt;/code&gt; inside will tell us it contains actually the content of the just created blog post.&lt;br&gt;
This is by intention: This &lt;em&gt;Scully&lt;/em&gt; schematic created the markdown file with a meta flag called &lt;code&gt;published&lt;/code&gt; that is by default set to &lt;code&gt;false&lt;/code&gt;.&lt;br&gt;
The internally used renderer plugin from &lt;em&gt;Scully&lt;/em&gt; will handle this flag, and it creates an unguessable name for the route.&lt;br&gt;
This allows us to create blog post drafts that we can already publish and share by using the link for example to let someone else review the article.&lt;br&gt;
You can also use this route if you don't care about the route name.&lt;br&gt;
But normally you would just like to change the metadata in the Markdown file to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;published&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After this, run the build process again and the files &lt;code&gt;index.html&lt;/code&gt; in &lt;code&gt;dist/static/blog/&amp;lt;post-name&amp;gt;/&lt;/code&gt; contain now our static pages ready to be served.&lt;br&gt;
When we are visiting the route path &lt;code&gt;/blog/first-post&lt;/code&gt; we can see the content of our markdown source file &lt;code&gt;blog/first-post.md&lt;/code&gt; is rendered as HTML.&lt;/p&gt;

&lt;p&gt;If you want to prove that the page is actually really pre-rendered, just disable JavaScript by using your Chrome Developer Tools.&lt;br&gt;
You can reload the page and see that the content is still displayed.&lt;br&gt;
Awesome, isn't it?&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%2Fk9n.dev%2Fassets%2Fimages%2Fblog%2Fscully%2Fscully-pre-rendered-js-disabled.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fk9n.dev%2Fassets%2Fimages%2Fblog%2Fscully%2Fscully-pre-rendered-js-disabled.png" alt="a simple blog created with Scully"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When JavaScript is enabled, &lt;em&gt;Scully&lt;/em&gt; configures your static sites in that way, that you will see initially the static content.&lt;br&gt;
In the background it will bootstrap your Angular app, and refresh the content with it.&lt;br&gt;
You won't see anything flickering.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hold on a minute! 😳&lt;/p&gt;

&lt;p&gt;You may have realized: We haven’t written one line of code manually yet, and we have already a fully functional blogging site that’s server site rendered. Isn’t that cool?&lt;br&gt;
Setting up an Angular based blog has never been easier.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Good to know:&lt;/strong&gt; &lt;em&gt;Scully&lt;/em&gt; also detects new routes we are adding manually to our app, and it will create static sites for all those pages.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Use the &lt;code&gt;ScullyRoutesService&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;We want to take the next step.&lt;br&gt;
Now we want to list an overview of all existing blog posts we have and link to their sites in our &lt;code&gt;AppComponent&lt;/code&gt;.&lt;br&gt;
Therefore, we can easily inject the &lt;code&gt;ScullyRoutesService&lt;/code&gt;.&lt;br&gt;
It will return us a list of all routes &lt;em&gt;Scully&lt;/em&gt; found with the parsed information as a &lt;code&gt;ScullyRoute&lt;/code&gt; array within the &lt;code&gt;available$&lt;/code&gt; observable.&lt;br&gt;
We can easily inject the service and display the information as a list in our &lt;code&gt;AppComponent&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ScullyRoutesService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ScullyRoute&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;@scullyio/ng-lib&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;Observable&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;rxjs&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;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;templateUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./app.component.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;styleUrls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./app.component.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;links$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Observable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ScullyRoute&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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;scully&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;available$&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;scully&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ScullyRoutesService&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;To display the results, we can simply use &lt;code&gt;ngFor&lt;/code&gt; with the &lt;code&gt;async&lt;/code&gt; pipe and list the results.&lt;br&gt;
A &lt;code&gt;ScullyRoute&lt;/code&gt; will give us the routing path inside the &lt;code&gt;route&lt;/code&gt; key and all other markdown metadata inside their appropriate key names.&lt;br&gt;
So we can extend for example our markdown metadata block with more keys (e.g. &lt;code&gt;thumbnail: assets/thumb.jpg&lt;/code&gt;) and we can access them via those (&lt;code&gt;blog.thumbnail&lt;/code&gt; in our case).&lt;br&gt;
We can extend &lt;code&gt;app.component.html&lt;/code&gt; like this:&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;ul&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;*ngFor=&lt;/span&gt;&lt;span class="s"&gt;"let link of links$ | async"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;[routerLink]=&lt;/span&gt;&lt;span class="s"&gt;"link.route"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{ link.title }}&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;hr&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;router-outlet&amp;gt;&amp;lt;/router-outlet&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will give us a fully routed blog page:&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%2Fk9n.dev%2Fassets%2Fimages%2Fblog%2Fscully%2Fscully-blog.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%2Fk9n.dev%2Fassets%2Fimages%2Fblog%2Fscully%2Fscully-blog.gif" alt="a simple blog created with scully"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;ScullyRoutesService&lt;/code&gt; contains all the available routes in your app.&lt;br&gt;
In fact, any route that we add to our Angular app will be detected by &lt;em&gt;Scully&lt;/em&gt; and made available via the &lt;code&gt;ScullyRoutesService.available$&lt;/code&gt; observable.&lt;br&gt;
To list only blog posts from the &lt;code&gt;blog&lt;/code&gt; route and directory we can just filter the result:&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="cm"&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;map&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Observable&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;rxjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="cm"&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;AppComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;links$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Observable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ScullyRoute&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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;scully&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;available$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;routeList&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;return&lt;/span&gt; &lt;span class="nx"&gt;routeList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="na"&gt;route&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ScullyRoute&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/blog/`&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="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;scully&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ScullyRoutesService&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;Wow! That was easy, wasn’t it?&lt;br&gt;
Now you just need to add a bit of styling and content and your blog is ready for getting visited.&lt;/p&gt;
&lt;h2&gt;
  
  
  Fetch dynamic information from an API
&lt;/h2&gt;

&lt;p&gt;As you may have realized: &lt;em&gt;Scully&lt;/em&gt; needs a data source to fetch all dynamic routes in an app.&lt;br&gt;
In case of our blog example &lt;em&gt;Scully&lt;/em&gt; uses the &lt;code&gt;:slug&lt;/code&gt; router parameter as a placeholder.&lt;br&gt;
Scully will fill this placeholder with appropriate content to visit and pre-render the site.&lt;br&gt;
The content for the placeholder comes in our blog example from the files in the &lt;code&gt;/blog&lt;/code&gt; directory.&lt;br&gt;
This has been configured from the schematics we ran before in the file &lt;code&gt;scully.scully-blog.config.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ScullyConfig&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;@scullyio/scully&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/** this loads the default render plugin, remove when switching to something else. */&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@scullyio/scully-plugin-puppeteer&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;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ScullyConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;projectRoot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;projectName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scully-blog&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;outDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./dist/static&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/blog/:slug&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;contentFolder&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;folder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./blog&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="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I would like to show a second example.&lt;br&gt;
Imagine we want to display information about books from an external API.&lt;br&gt;
So our app needs another route called &lt;code&gt;/books/:isbn&lt;/code&gt;.&lt;br&gt;
To visit this route and pre-render it, we need a way to fill the &lt;code&gt;isbn&lt;/code&gt; parameter.&lt;br&gt;
Luckily &lt;em&gt;Scully&lt;/em&gt; helps us with this too.&lt;br&gt;
We can configure &lt;a href="https://scully.io/docs/plugins#router-plugin" rel="noopener noreferrer"&gt;&lt;em&gt;Router Plugin&lt;/em&gt;&lt;/a&gt; that will call an API, fetch the data from it and pluck the &lt;code&gt;isbn&lt;/code&gt; from the array of results to fill it in the router parameter.&lt;/p&gt;

&lt;p&gt;In the following example we will use the public service &lt;a href="https://api3.angular-buch.com" rel="noopener noreferrer"&gt;BookMonkey API&lt;/a&gt; (we provide this service for the readers of our &lt;a href="https://angular-buch.com/" rel="noopener noreferrer"&gt;German Angular book&lt;/a&gt;) as an API to fetch a list of books:&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="cm"&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;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ScullyConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="cm"&gt;/* ... */&lt;/span&gt;
  &lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cm"&gt;/* ... */&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/books/:isbn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;type&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;json&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;isbn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;url&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;https://api3.angular-buch.com/books&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;property&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;isbn&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="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result from the API will have this shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Angular"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"subtitle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Grundlagen, fortgeschrittene Themen und Best Practices – mit NativeScript und NgRx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"isbn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"9783864906466"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Angular"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"subtitle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Grundlagen, fortgeschrittene Techniken und Best Practices mit TypeScript - ab Angular 4, inklusive NativeScript und Redux"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"isbn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"9783864903571"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After &lt;em&gt;Scully&lt;/em&gt; plucks the ISBN, it will just iterate over the final array: &lt;code&gt;['9783864906466', '9783864903571']&lt;/code&gt;.&lt;br&gt;
In fact, when running &lt;em&gt;Scully&lt;/em&gt; using &lt;code&gt;npx scully&lt;/code&gt;, it will visit the following routes, &lt;strong&gt;after we have configured the route &lt;code&gt;/books/:isbn&lt;/code&gt; in the Angular router&lt;/strong&gt; (otherwise non-used routes will be skipped).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/books/9783864906466
/books/9783864903571
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can see the result in the log:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;enable reload on port 2667
 ☺   new Angular build imported
 ☺   Started servers in background
--------------------------------------------------
Watching blog for change.
--------------------------------------------------
 ☺   new Angular build imported
Finding all routes in application.
Using stored unhandled routes
Pull in data to create additional routes.
Finding files in folder "/&amp;lt;path&amp;gt;/blog"
Route list created in files:
  "/&amp;lt;path&amp;gt;/src/assets/scully-routes.json",
  "/&amp;lt;path&amp;gt;/dist/static/assets/scully-routes.json",
  "/&amp;lt;path&amp;gt;/dist/scully-blog/assets/scully-routes.json"

Route "/books/9783864903571" rendered into file: "/&amp;lt;path&amp;gt;/dist/static/books/9783864903571/index.html"
Route "/books/9783864906466" rendered into file: "/&amp;lt;path&amp;gt;/dist/static/books/9783864906466/index.html"
Route "/blog/12-27-2019-blog" rendered into file: "/&amp;lt;path&amp;gt;/dist/static/blog/12-27-2019-blog/index.html"
Route "/blog/first-post" rendered into file: "/&amp;lt;path&amp;gt;/dist/static/blog/first-post/index.html"
Route "/" rendered into file: "/&amp;lt;path&amp;gt;/dist/static/index.html"

Generating took 3.3 seconds for 7 pages:
  That is 2.12 pages per second,
  or 473 milliseconds for each page.

  Finding routes in the angular app took 0 milliseconds
  Pulling in route-data took 26 milliseconds
  Rendering the pages took 2.58 seconds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is great. We have efficiently pre-rendered normal dynamic content!&lt;br&gt;
And that was it for today.&lt;br&gt;
With the shown examples, it's possible to create a full-fledged website with Scully.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Did you know that &lt;strong&gt;this blogpost&lt;/strong&gt; and the overall website you are right now reading has also been created using &lt;em&gt;Scully&lt;/em&gt;?&lt;br&gt;
Feel free to check out the sources at:&lt;br&gt;
&lt;a href="https://github.com/d-koppenhagen/k9n.dev" rel="noopener noreferrer"&gt;github.com/d-koppenhagen/k9n.dev&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you want to follow all the development steps in detail, check out my provided Github repository&lt;br&gt;
&lt;a href="https://github.com/d-koppenhagen/scully-blog-example" rel="noopener noreferrer"&gt;scully-blog-example&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;Scully is an awesome tool if you need a pre-rendered Angular SPA where all routes can be accessed immediately without loading the whole app at once.&lt;br&gt;
This is a great benefit for users as they don’t need to wait until the bunch of JavaScript has been downloaded to their devices.&lt;br&gt;
Visitors and &lt;strong&gt;search engines&lt;/strong&gt; have instantly access to the sites' information.&lt;br&gt;
Furthermore, &lt;em&gt;Scully&lt;/em&gt; offers a way to create very easily a blog and renders all posts written in Markdown.&lt;br&gt;
It will handle and pre-render dynamic routes by fetching API data from placeholders and visiting every route filled by this placeholder.&lt;/p&gt;

&lt;p&gt;Compared to "classic" pre-rending by using &lt;a href="https://angular.io/guide/universal" rel="noopener noreferrer"&gt;Angular Universal&lt;/a&gt;, &lt;em&gt;Scully&lt;/em&gt; is much easier to use, and it doesn't require you to write a specific flavor of Angular.&lt;br&gt;
Also, &lt;em&gt;Scully&lt;/em&gt; can easily pre-render hybrid Angular apps or Angular apps with plugins like jQuery in comparison to Angular Universal.&lt;br&gt;
If you want to compare &lt;em&gt;Scully&lt;/em&gt; with Angular Universal in detail, check out the blog post from Sam Vloeberghs: &lt;a href="https://samvloeberghs.be/posts/scully-or-angular-universal-what-is-the-difference" rel="noopener noreferrer"&gt;Scully or Angular Universal, what is the difference?&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want to dig a bit deeper into the features &lt;em&gt;Scully&lt;/em&gt; offers, check out my &lt;a href="https://k9n.dev/blog/2020-03-dig-deeper-into-scully-ssg" rel="noopener noreferrer"&gt;second article&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thank you&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Special thanks go to &lt;a href="https://twitter.com/aaronfrost" rel="noopener noreferrer"&gt;Aaron Frost (Frosty ⛄️)&lt;/a&gt; from the &lt;em&gt;Scully&lt;/em&gt; core team, &lt;a href="https://twitter.com/fmalcher01" rel="noopener noreferrer"&gt;Ferdinand Malcher&lt;/a&gt; and &lt;a href="https://twitter.com/JohannesHoppe" rel="noopener noreferrer"&gt;Johannes Hoppe&lt;/a&gt; for revising this article.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
