My BlogI am writing about stuff.2022-12-18T00:00:00Zhttps://example.com/Your Nameme@example.comCombining Angular Directives with the Directive Composition API2022-12-18T00:00:00Zhttps://example.com/posts/2022/12/combining-directives-with-directive-composition/<p>The Directive Composition API brings with it a couple benefits:</p>
<ul>
<li>reduce code duplication</li>
<li>combine directives to form composite directives</li>
</ul>
<p>It's the second benefit that this post focuses on.</p>
<p>If you are unfamiliar with the Directive Composition API, you can <a href="https://example.com/posts/2022/12/angular-directive-composition/">read my introduction</a>.</p>
<h2>The Scenario</h2>
<p>Across our app, we have a set of forms field components. To assist with consistency and code re-use, we've created the following directives to apply to those fields:</p>
<ul>
<li><code>InvalidFieldDirective</code>: Styles the field in the event it's invalid.</li>
<li><code>RequiredFieldDirective</code>: Adds an "*" and styles the field to note it's required.</li>
<li><code>TooltipDirective</code>: Displays a tooltip.</li>
</ul>
<p>Sometimes we need some of those, but on other occasions we need them all.</p>
<p>This is one example of where the Directive Composition API can come in handy.</p>
<p>Using the Directive Composition API we can create a directive that combines all those directives.</p>
<h2>How?</h2>
<h3>Making the Component Standalone</h3>
<p>First of all, we need to make sure that each of the directives are <a href="https://angular.io/guide/standalone-components">standalone</a>.</p>
<p>To do this, first add the <code>standalone</code> property in the directive's decorator and set it to <code>true</code>:</p>
<pre class="language-typescript"><code class="language-typescript"><span class="token decorator"><span class="token at operator">@</span><span class="token function">Directive</span></span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> selector<span class="token operator">:</span> <span class="token string">'[appMyDirective]'</span><span class="token punctuation">,</span><br /> standalone<span class="token operator">:</span> <span class="token boolean">true</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span></code></pre>
<p>You then need to remove your directive from a module's <code>declarations</code> array if it features in one.</p>
<h3>Creating your Composite Directive</h3>
<p>The act of creating a composite directive is actually rather simple.</p>
<p>Create a directive that will eventually combine the other directives:</p>
<pre class="language-typescript"><code class="language-typescript"><span class="token decorator"><span class="token at operator">@</span><span class="token function">Directive</span></span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> selector<span class="token operator">:</span> <span class="token string">"[appStandardFormLabel]"</span><span class="token punctuation">,</span><br /> standalone<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><br /><span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">StandardFormLabelDirective</span> <span class="token punctuation">{</span><span class="token punctuation">}</span></code></pre>
<p>We can now add the <code>hostDirectives</code> property to our directive to include the directives we want it to apply:</p>
<pre class="language-typescript"><code class="language-typescript"><span class="token decorator"><span class="token at operator">@</span><span class="token function">Directive</span></span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> selector<span class="token operator">:</span> <span class="token string">"[appStandardFormLabel]"</span><span class="token punctuation">,</span><br /> standalone<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span><br /> hostDirectives<span class="token operator">:</span> <span class="token punctuation">[</span><br /> InvalidFieldDirective<span class="token punctuation">,</span><br /> RequiredFieldDirective<span class="token punctuation">,</span><br /> TooltipDirective<span class="token punctuation">,</span><br /> <span class="token punctuation">]</span><span class="token punctuation">,</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><br /><span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">StandardFormLabelDirective</span> <span class="token punctuation">{</span><span class="token punctuation">}</span></code></pre>
<p>Now that we have our composite directive, we can put it to use:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>app-form-label</span> <span class="token attr-name">app-standard-form-label</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>app-form-label</span><span class="token punctuation">></span></span></code></pre>
<p>We have a problem though: some of our directives have inputs that are required:</p>
<p>LIST THE DIRECTIVES AND THEIR INPUTS HERE</p>
<p>We can solve this by specifying the inputs for each of the directives:</p>
<p>EXAMPLE</p>
<p>We then pass the inputs in as we would any other directive:</p>
<p>EXAMPLE</p>
VS Code: Skipping current selection when using cmd/ctrl+d2022-12-10T00:00:00Zhttps://example.com/posts/2022/12/skipping-current-selection-when-using-cmd-d/<p>One of my favorite features in VS Code is being able to select the next occurrence of the selected word by pressing <code>cmd+d</code> (<code>ctrl+d</code> for Windows). This then allows me to quickly modify each instance of that word.</p>
<h2>The Problem</h2>
<p>For a long time though I frequently encountered the same problem: it would select an instance of the word that I didn't wish to alter. Often this is because the word would feature in a different variable or function.</p>
<p>ANNOTATED IMAGE</p>
<h2>The Solution</h2>
<p>You can deselect the selected word by using <code>cmd + k</code> (<code>ctrl + k</code>).</p>
<h3>Demonstration</h3>
<p>ANIMATED GIF GOES HERE</p>
<p>Bare with, this one isn't easy to demonstrate in a blog post.</p>
<p>For the sake of this contrived demo, let us ignore the fact F2 functionality exists to rename a variable...</p>
<p>Assume we have the following code:</p>
<pre class="language-typescript"><code class="language-typescript"><span class="token keyword">const</span> <span class="token function-variable function">initialize</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br /> <span class="token keyword">const</span> config <span class="token operator">=</span> <span class="token function">getConfig</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token function">applyConfig</span><span class="token punctuation">(</span>config<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>Our goal is to rename the <code>config</code> variable.</p>
<h4>Failed Attempt</h4>
<p>On our first attempt we perform the following. It doesn't go well:</p>
<p>ANIMATED GIF</p>
<ul>
<li></li>
</ul>
<h4>Successful Attempt</h4>
<p>We undo our changes and make another attempt, this time using <code>cmd + k</code>:</p>
<p>ANIMATED GIF</p>
<hr />
<p>ORIGINAL ATTEMPT</p>
<p>We select the first instance of <code>config</code> and press <code>cmd + d</code> with the aim of also selecting the <code>config</code> being passed into <code>applyConfig()</code>.</p>
<p>Rather than that <code>config</code> being selected, the "Config" in <code>getConfig</code> is selected.</p>
<p>We can bypass that by pressing <code>cmd + k</code>.</p>
<p>One issue we encounter here is the visual state of VS Code doesn't change at this point. It's not clear that it's been bypassed.</p>
<p>We press <code>cmd + d</code> again. This time the "Config" inside <code>applyConfig</code> is selected.</p>
<p>Again, press <code>cmd + k</code> to bypass it.</p>
<p>For the final time, we press <code>cmd + d</code>, which selects the "config" being passed into <code>applyConfig</code>. We <em>are</em> after this one.</p>
<p>You are now in a position where you can type and only replace the two instances of "config".</p>
An Introduction to the Angular Directive Composition API2022-12-07T00:00:00Zhttps://example.com/posts/2022/12/angular-directive-composition/<p>Angular 15 introduced new functionality with the <a href="https://angular.io/guide/directive-composition-api">Directive Composition API</a> that has got the Angular community in a bit of a tiz of excitement. I was curious to find out what all the fuss was about.</p>
<p>The new API introduces a new way to apply directives to components.</p>
<h2>Benefits</h2>
<p>With the new approach it'll enable you to:</p>
<ul>
<li>reduce code duplication</li>
<li>combine directives to form composite directives</li>
</ul>
<h2>Putting the new API to use</h2>
<h3>Basic Example</h3>
<p>Before we see the new code in action, lets first look at a basic example where we take a directive applied in a traditional way, and instead apply it using the Directive Composition API.</p>
<p>Traditionally you would add a directive as follows:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>my-component</span> <span class="token attr-name">my-directive</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>my-component</span><span class="token punctuation">></span></span></code></pre>
<p>With the new API, rather than apply it via the HTML, you instead apply it within the component's decorator:</p>
<pre class="language-typescript"><code class="language-typescript"><span class="token decorator"><span class="token at operator">@</span><span class="token function">Component</span></span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> selector<span class="token operator">:</span> <span class="token string">"my-component"</span><span class="token punctuation">,</span><br /> template<span class="token operator">:</span> <span class="token string">"my-component.html"</span><span class="token punctuation">,</span><br /> hostDirectives<span class="token operator">:</span> <span class="token punctuation">[</span>MyDirective<span class="token punctuation">]</span><span class="token punctuation">,</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><br /><span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">MyComponent</span> <span class="token punctuation">{</span><span class="token punctuation">}</span></code></pre>
<h3>Practical Example</h3>
<p>Now let's look at a slightly more practical example, and introduce some new concepts along the way.</p>
<p>We want to add a directive called <code>TooltipDirective</code>.</p>
<p>This directive will display a tooltip on the component it's applied to.</p>
<p>First of all, we specify the directive using the <code>hostDirectives</code> property:</p>
<pre class="language-typescript"><code class="language-typescript"><span class="token decorator"><span class="token at operator">@</span><span class="token function">Component</span></span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> selector<span class="token operator">:</span> <span class="token string">"my-component"</span><span class="token punctuation">,</span><br /> template<span class="token operator">:</span> <span class="token string">"my-component.html"</span><span class="token punctuation">,</span><br /> hostDirectives<span class="token operator">:</span> <span class="token punctuation">[</span>TooltipDirective<span class="token punctuation">]</span><span class="token punctuation">,</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><br /><span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">MyComponent</span> <span class="token punctuation">{</span><span class="token punctuation">}</span></code></pre>
<p>This doesn't achieve much though. Our tooltip directive needs to know what text to display for a tooltip.</p>
<h2>Accepting Inputs</h2>
<p>To do this our directive exposes an input named <code>text</code>.</p>
<p>Using the traditional approach, we would apply it as follows:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>my-component</span> <span class="token attr-name">appTooltip</span> <span class="token attr-name">[text]</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token punctuation">'</span>My tooltip text'<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>my-component</span><span class="token punctuation">></span></span></code></pre>
<p>In order to achieve the same with the Directive Composition API, we instead specify an <code>inputs</code> array:</p>
<pre class="language-typescript"><code class="language-typescript"><span class="token decorator"><span class="token at operator">@</span><span class="token function">Component</span></span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> selector<span class="token operator">:</span> <span class="token string">'my-component'</span><span class="token punctuation">,</span><br /> template<span class="token operator">:</span> <span class="token string">'my-component.html'</span><span class="token punctuation">,</span><br /> hostDirectives<span class="token operator">:</span> <span class="token punctuation">[</span><br /> directive<span class="token operator">:</span> TooltipDirective<span class="token punctuation">,</span><br /> inputs<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">'text'</span><span class="token punctuation">]</span><span class="token punctuation">,</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">]</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><br /><span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">MyComponent</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span></code></pre>
<p>What the <code>inputs</code> property does is essentially say "if an attribute named <code>[text]</code> is applied to the host element, associate that with the <code>text</code> input on the <code>TooltipDirective</code>.</p>
<p>So as with our traditional example we can pass in the tooltip text using the <code>[text]</code> attribute:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>my-component</span> <span class="token attr-name">[text]</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token punctuation">'</span>My tooltip text'<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>my-component</span><span class="token punctuation">></span></span></code></pre>
<h2>Input Aliases</h2>
<p>In the context of the <code>TooltipDirective</code> an input named <code>Text</code> makes sense. It doesn't need to be named <code>TooltipText</code> as it is on the <code>TooltipDirective</code>.</p>
<p>But when you see the <code>[Text]</code> attribute applied to the host component, <code>[Text]</code> doesn't tell us much. We don't really know how that attribute is used.</p>
<p>To make our code more readable, we can use an alias to represent our <code>Text</code> input:</p>
<pre class="language-typescript"><code class="language-typescript"><span class="token decorator"><span class="token at operator">@</span><span class="token function">Component</span></span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> selector<span class="token operator">:</span> <span class="token string">'my-component'</span><span class="token punctuation">,</span><br /> template<span class="token operator">:</span> <span class="token string">'my-component.html'</span><span class="token punctuation">,</span><br /> hostDirectives<span class="token operator">:</span> <span class="token punctuation">[</span><br /> directive<span class="token operator">:</span> TooltipDirective<span class="token punctuation">,</span><br /> inputs<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">'text:tooltipText'</span><span class="token punctuation">]</span><span class="token punctuation">,</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">]</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><br /><span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">MyComponent</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span></code></pre>
<p>With the above code we are saying "expose the input <code>text</code> using the attribute <code>tooltipText</code>". Now when we pass in the attribute, the code provides a clear indication of how it is used:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>my-component</span> <span class="token attr-name">[tooltipText]</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token punctuation">'</span>My tooltip text'<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>my-component</span><span class="token punctuation">></span></span></code></pre>
<h2>Exposing Outputs</h2>
<p>As well as simply displaying a tooltip, <code>TooltipDirective</code> also provides a "Want to know more...?" link. It provides the user of the directive with a way to provide additional information when the user clicks the 'Want to know more...?" text.</p>
<p>But because we don't know what context the directive is going to be used in, the directive itself doesn't simply open a link. What if for example we want a modal displayed instead? Therefore the directive provides an <a href="https://angular.io/api/core/Directive#outputs">Output</a> we can subscribe to. That output is triggered/emitted whenever the user clicks the "Want to know more...?" link.</p>
<p>To use the output, we take a similar approach to how we handled inputs, this time using the <code>outputs</code> array.</p>
<p>In the following example we expose an output named <code>moreInfoClick</code>.</p>
<pre class="language-typescript"><code class="language-typescript"><span class="token decorator"><span class="token at operator">@</span><span class="token function">Component</span></span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> selector<span class="token operator">:</span> <span class="token string">'my-component'</span><span class="token punctuation">,</span><br /> template<span class="token operator">:</span> <span class="token string">'my-component.html'</span><span class="token punctuation">,</span><br /> hostDirectives<span class="token operator">:</span> <span class="token punctuation">[</span><br /> directive<span class="token operator">:</span> TooltipDirective<span class="token punctuation">,</span><br /> inputs<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">'text:tooltipText'</span><span class="token punctuation">]</span><span class="token punctuation">,</span><br /> outputs<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">'moreInfoClick'</span><span class="token punctuation">]</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">]</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><br /><span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">MyComponent</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span></code></pre>
<p>We can then subscribe to the <code>moreInfoClick</code> output as we would any other output:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>my-component</span><br /> <span class="token attr-name">[tooltipText]</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token punctuation">'</span>My tooltip text'<span class="token punctuation">"</span></span><br /> <span class="token attr-name">(moreInfoClick)</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>showMoreInfo()<span class="token punctuation">"</span></span><br /><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>my-component</span><span class="token punctuation">></span></span></code></pre>
<p>We can then write the <code>showMoreInfo()</code> method to open a link, display a modal or potentially something else.</p>
<p>Just like inputs, outputs support aliases:</p>
<pre class="language-typescript"><code class="language-typescript"><span class="token decorator"><span class="token at operator">@</span><span class="token function">Component</span></span><span class="token punctuation">(</span><span class="token punctuation">{</span><br /> selector<span class="token operator">:</span> <span class="token string">'my-component'</span><span class="token punctuation">,</span><br /> template<span class="token operator">:</span> <span class="token string">'my-component.html'</span><span class="token punctuation">,</span><br /> hostDirectives<span class="token operator">:</span> <span class="token punctuation">[</span><br /> directive<span class="token operator">:</span> TooltipDirective<span class="token punctuation">,</span><br /> inputs<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">'text:tooltipText'</span><span class="token punctuation">]</span><span class="token punctuation">,</span><br /> outputs<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">'moreInfoClick:moreInfoOnTooltipClick'</span><span class="token punctuation">]</span><br /> <span class="token punctuation">}</span><br /> <span class="token punctuation">]</span><br /><span class="token punctuation">}</span><span class="token punctuation">)</span><br /><span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">MyComponent</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span></code></pre>
<h2>Limitations</h2>
<p>The new API does come with some limitations.</p>
<ul>
<li>The directives must be <a href="https://angular.io/guide/standalone-components">stand-alone</a>.</li>
<li>It's not possible to add directives at runtime.</li>
</ul>
<h2>Research Links</h2>
<p>TODO - Maybe include in a front-matter property (array of objects) and then expose later via a custom plugin...</p>