Free AI web copilot to create summaries, insights and extended knowledge, download it at here
4359
Abstract
l @angular/material
<span class="hljs-built_in">npm</span> install @angular/cdk
<span class="hljs-built_in">npm</span> install @angular/flex-layout</pre></div><ul><li>Import Angular theme by adding this line to <code>src/style.scss</code>:</li></ul><div id="fbc4"><pre>@import <span class="hljs-string">"~@angular/material/prebuilt-themes/indigo-pink.css"</span>;</pre></div><ul><li>Generate <code>BaseModule</code>:</li></ul><div id="01d7"><pre>ng <span class="hljs-keyword">generate</span> <span class="hljs-keyword">module</span> shared/base-<span class="hljs-keyword">module</span></pre></div><ul><li>Generate a new component in it, <code>dynamic-layout</code>:</li></ul><div id="8c70"><pre>ng <span class="hljs-keyword">generate</span> component shared/base-<span class="hljs-keyword">module</span>/dynamic-layout --<span class="hljs-keyword">export</span></pre></div><ul><li>Generate <code>ContactsModule</code>:</li></ul><div id="9975"><pre>ng <span class="hljs-keyword">generate</span> <span class="hljs-keyword">module</span> modules/contacts</pre></div><ul><li>Generate the following components in it:</li></ul><div id="c50b"><pre>ng <span class="hljs-keyword">generate</span> <span class="hljs-keyword">module</span> modules/contacts/containers/contact-container</pre></div><div id="9d1c"><pre>ng <span class="hljs-keyword">generate</span> <span class="hljs-keyword">module</span> modules/contacts/presenters/contact</pre></div><div id="a55a"><pre>ng <span class="hljs-keyword">generate</span> <span class="hljs-keyword">module</span> modules/contacts/presenters/contact-list</pre></div><div id="ba69"><pre>ng <span class="hljs-keyword">generate</span> <span class="hljs-keyword">module</span> modules/contacts/presenters/contact-update/contact-form</pre></div><div id="2e6a"><pre>ng <span class="hljs-keyword">generate</span> <span class="hljs-keyword">module</span> modules/contacts/presenters/contact-update/contact-update-dialog --<span class="hljs-keyword">type</span>=dialog</pre></div><ul><li>Update routing in <code>app-routing.module.ts</code>:</li></ul><div id="5d8f"><pre>const routes: Routes = [
{ path: <span class="hljs-string">''</span>, redirectTo: <span class="hljs-string">'contacts'</span>, pathMatch: <span class="hljs-string">'full'</span> },
{ path: <span class="hljs-string">'contacts'</span>, component: ContactContainerComponent }
];</pre></div><h1 id="92da">Dive Into Details</h1><p id="dcc3">All right, time to dive into the good stuff now. Our main presenter in <code>ContactsModule</code> is <code>ContactComponent</code>, which is the place to specify the values for two dynamic templates — “<code>list</code>” and “<code>update</code>” — using multi-slot content projection.</p><p id="a32c">The <code>dynamic-layout</code> tag wraps the content in the component, and depending on the value of <code>layoutStyle</code>, the <code>DynamicLayoutComponent</code> will manage how and where to render the update template:</p>
<figure id="a9c5">
<div>
<div>
<iframe class="gist-iframe" src="/gist/rakia/980e9b80bf2a08c8ce1ffa3a06fbec98.js" allowfullscreen="" frameborder="0" height="undefined" width="undefined">
</div>
</div>
</figure></iframe></div></div></figure><p id="abc4">The TypeScript part, <code>contact.component.ts</code>:</p>
<figure id="c5c6">
<div>
<div>
<iframe class="gist-iframe" src="/gist/rakia/52cb477784777d9d59d627bdb3510268.js" allowfullscreen="" frameborder="0" height="undefined" width="undefined">
</div>
</div>
</figure></iframe></div></div></figure><p id="9f04">As you may have noticed, <code>ContactComponent</code> is extending <code>BasePresenter</code>, a parent component that takes care of handling create/open/remove for the appropriate update view and redirects the user to the list or update pages:</p>
<figure id="accd">
<div>
<div>
<iframe class="gist-iframe" src="/gist/rakia/c5baf8c42eca90811b524588f92e2f06.js" allowfullscreen="" frameborder="0" height="undefined" width="undefined">
</div>
</div>
</figure></iframe></div></div></figure><p id="a7d4">Similar to <code>BasePres
Options
enter</code>, I have used <code>BaseContainer</code> and <code>BaseList</code> parent components and delegated the common functionalities to them, which is helpful to avoid duplicating code:</p>
<figure id="a634">
<div>
<div>
<iframe class="gist-iframe" src="/gist/rakia/8ae561aa01f8eb040d98b6f81e5ba444.js" allowfullscreen="" frameborder="0" height="undefined" width="undefined">
</div>
</div>
</figure></iframe></div></div></figure>
<figure id="f689">
<div>
<div>
<iframe class="gist-iframe" src="/gist/rakia/81429cf7a772bc655830e04f8dec4012.js" allowfullscreen="" frameborder="0" height="undefined" width="undefined">
</div>
</div>
</figure></iframe></div></div></figure><p id="dbe3">Now it’s time to reveal how I’ve designed <code>DynamicLayoutComponent</code>. The TypeScript part does not have any logic except some input fields and event emitters to communicate with the hosting component:</p>
<figure id="06b5">
<div>
<div>
<iframe class="gist-iframe" src="/gist/rakia/3fc9034d5d9c78cb0b35d17649cce7bd.js" allowfullscreen="" frameborder="0" height="undefined" width="undefined">
</div>
</div>
</figure></iframe></div></div></figure><p id="ccf0"><code>dynamic-layout.component.html</code> is our main game territory. It’s the place where I’ve added the logic to attach the right content to the right template via <code>ng-content select="[list]"</code> and <code>ng-content select="[update]"</code>. I’ve specified also the placeholders to render each template with <code>ng-container *ngTemplateOutlet="update"</code> and <code>ng-container *ngTemplateOutlet="list"</code>:</p>
<figure id="13c6">
<div>
<div>
<iframe class="gist-iframe" src="/gist/rakia/be963376ac85e14de213dd505f25abe9.js" allowfullscreen="" frameborder="0" height="undefined" width="undefined">
</div>
</div>
</figure></iframe></div></div></figure><p id="afe3">Last but not least is to show you how the parent <code>StoreService</code> looks. It’s a <code>BehaviorSubject</code>-based service that’s playing the role of a store at the same time. It’s the class that all modules’ store services are inheriting from as well:</p>
<figure id="6229">
<div>
<div>
<iframe class="gist-iframe" src="/gist/rakia/cfc8d309ea7063467e59fe522163c155.js" allowfullscreen="" frameborder="0" height="undefined" width="undefined">
</div>
</div>
</figure></iframe></div></div></figure><p id="a75b">The whole application is available in <a href="https://github.com/rakia/angular-dynamic-layout">Github here</a>. Check the app yourself and click around.</p><h1 id="4292">Final Thoughts</h1><p id="214b">Angular content projection and dynamic template are very useful features to build reusable components and achieve a clean architecture. We have seen how <code>ng-content</code> and <code>ng-template</code> directives work, and how they can improve the UX design and solve problems that we face the most with enterprise applications.</p><p id="8131">Moving many functions to a central place by benefiting from Typescript/Angular inheritance allowed me to get rid of a lot of boilerplate code and facilitate maintenance in the future. The solution that I have presented is not the only one to build the end application. Feel free to give feedback about it or share how you have resolved a similar challenge.</p><p id="c6e2">Thanks for reading.</p><p id="30fc">🧠💡 I write about engineering, technology, and leadership for a community of smart, curious people. <a href="https://rakiabensassi.substack.com/"><b>Join my free email newsletter for exclusive access</b></a><b> </b>or sign up for Medium <a href="https://rakiabensassi.medium.com/membership">here</a>.</p><p id="eee3"><i>You can check my <b>video course</b> on Udemy: <a href="https://www.udemy.com/course/identify-and-fix-javascript-memory-leaks/">How to Identify, Diagnose, and Fix Memory Leaks in Web Apps</a>.</i></p></article></body>