<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[AngularWave - Medium]]></title>
        <description><![CDATA[Alex, Roman and Nikita are bringing you interesting articles about Angular and related ecosystem. Follow us on Twitter for more awesome content. - Medium]]></description>
        <link>https://medium.com/angularwave?source=rss----ee024cab95cb---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>AngularWave - Medium</title>
            <link>https://medium.com/angularwave?source=rss----ee024cab95cb---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 25 Apr 2026 08:39:30 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/angularwave" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[What’s New in Taiga UI v5: A Modern Angular UI Kit]]></title>
            <link>https://medium.com/angularwave/whats-new-in-taiga-ui-v5-a-modern-angular-ui-kit-fef85dde3fc7?source=rss----ee024cab95cb---4</link>
            <guid isPermaLink="false">https://medium.com/p/fef85dde3fc7</guid>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[front-end-development]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[open-source]]></category>
            <category><![CDATA[angular]]></category>
            <dc:creator><![CDATA[Nikita Barsukov]]></dc:creator>
            <pubDate>Thu, 02 Apr 2026 14:25:50 GMT</pubDate>
            <atom:updated>2026-04-02T14:25:49.120Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/780/1*kRjTf_ethf_l4b5JdwU2fg.jpeg" /></figure><p><a href="https://taiga-ui.dev">Taiga UI</a> is an Open Source Angular UI library that helps developers build modern, reliable user interfaces. We recently shipped the <strong>fifth major release</strong>, bringing a ton of architectural improvements and new capabilities. In this article, we’ll take a deep dive into the highlights of version 5.0.0 and explain why you should plan your upgrade as soon as possible.</p><h3>Fresh Minimum: Angular 19+</h3><p>In this new major version of our libraries, we raised the minimum supported Angular version to 19 and above. This means we can take advantage of all the new features and improvements added since Angular 16 (the minimum supported version in the previous Taiga UI major). And the paradigm shift toward a signal-based style has changed a lot in our codebase.</p><h4>Signal Inputs</h4><p>All our hundreds of components and directives said goodbye to classic properties decorated with @Input() and switched to their signal-based counterpart — <a href="https://angular.dev/guide/components/inputs">input</a>. Previously, our code had a lot of getters that internally used a private class method decorated with <a href="https://taiga-ui.dev/v4/utils/pure">@tuiPure</a> for memoization. Here&#39;s a simplified example of that approach:</p><pre>@Input({required: true})<br>fileName: string;<br><br>// Used in the template<br>// (potentially recalculated on every re-render)<br>get type(): string {<br>   return this.getType(this.file);<br>}<br><br>@tuiPure<br>private getType(file: string): string {<br>   const dot = file.lastIndexOf(&#39;.&#39;);<br><br>   return dot &gt; 0 ? file.slice(dot) : &#39;&#39;;<br>}</pre><p>Now, with signal-based <a href="https://angular.dev/guide/signals#computed-signals">computed</a>, getters are no longer needed, and we can write cleaner, more readable code without extra optimizations. computed takes care of memoization automatically.</p><pre>file = input.required&lt;string&gt;();<br><br>type = computed(() =&gt; {<br>   const dot = file.name.lastIndexOf(&#39;.&#39;);<br><br>   return dot &gt; 0 ? file.name.slice(dot) : &#39;&#39;;<br>});</pre><p>We’re sending the @tuiPure decorator off into honorable retirement: you were a good friend and helper for many years — thank you!</p><h4>Signal viewChild(-ren) / contentChild(-ren)</h4><p>In Taiga components, we frequently used the <a href="https://v16.angular.io/api/core/ViewChild">@ViewChild(‑ren)</a> and <a href="https://v16.angular.io/api/core/ContentChild">@ContentChild(‑ren)</a> decorators. You decorate a property in a component — and the requested DOM element, directive or provider gets automatically assigned to it. A significant limitation of this approach was that these elements only became available in the AfterViewInit and AfterContentInit lifecycle hooks. As a result, constructs like this could pop up in our codebase:</p><pre>@Component(...)<br>export class AnyComponent {<br>   private contentReady$ = new ReplaySubject&lt;boolean&gt;(1);<br><br>   children$ = this.contentReady$.pipe(<br>       switchMap(() =&gt; this.childrenQuery.changes),<br>   )<br><br>   @ContentChildren(&#39;ref&#39;)<br>   childrenQuery!: QueryList&lt;any&gt;;<br><br>   ngAfterContentInit() {<br>       this.contentReady$.next(true);<br>   }<br>}</pre><p>The contentReady$ stream notifies all its observers that it&#39;s safe to proceed. The trick of creating such a stream made the code slightly more declarative compared to directly manipulating all observers inside a hook. But it still produced its fair share of boilerplate.</p><p>Signal-based viewChild(-ren) and contentChild(-ren) are a real game-changer. You can practically forget about the AfterViewInit and AfterContentInit hooks, because signals created via viewChild and contentChild know when to update on their own, and all their subscribers automatically recalculate reactively. All those lines from the old approach are replaced by a single built-in one-liner.</p><h4>Signals Over RxJS</h4><p>Even though our team deeply respects RxJS and is confident it’ll be around for a long time, we genuinely fell in love with signals. We decided to make them the priority in our public API — not just because of their growing popularity in the Angular community, but because for many reactive tasks they’re a much better fit than RxJS, letting us provide better support and write more optimal, understandable code.</p><p>Here are just a few examples of changes from the new major version — the updated <a href="https://github.com/taiga-family/taiga-ui/pull/12397">TUI_NUMBER_FORMAT</a> and <a href="https://github.com/taiga-family/taiga-ui/pull/12373">TUI_DATE_FORMAT</a>.</p><pre>// Before<br>inject(TUI_NUMBER_FORMAT) // Observable&lt;TuiNumberFormatSettings&gt;<br>inject(TUI_DATE_FORMAT) // Observable&lt;TuiDateFormatSettings&gt;<br><br>// After <br>inject(TUI_NUMBER_FORMAT) // Signal&lt;TuiNumberFormatSettings&gt;<br>inject(TUI_DATE_FORMAT) // Signal&lt;TuiDateFormatSettings&gt;</pre><p>To sum up the thought about the dawn of the “signal era” in the Taiga codebase: we don’t resist the new trends set by the Angular team. And most importantly, we don’t prevent our users from seamlessly harnessing the full power of signals when building applications with Taiga UI!</p><p>We’ve already seen the benefits of the signal-based style firsthand. Less boilerplate, better performance, dramatically fewer Change Detection bugs, and improved support for Zoneless applications!</p><h4>New Esbuild + Vite Build Engine</h4><p>Another important change driven by raising the minimum Angular version is the switch to the <a href="https://angular.dev/tools/cli/build-system-migration">modern Esbuild + Vite build engine</a>. This move not only improved our own Developer Experience (build time during local development was significantly reduced), but also allowed us to catch bugs in YOUR Esbuild applications (the default for all new Angular apps) before we even release our libraries.</p><p>And these aren’t just words — we’ve already fixed several esbuild incompatibility bugs related to circular dependencies (<a href="https://github.com/taiga-family/taiga-ui/pull/12435">#12435</a> and <a href="https://github.com/taiga-family/taiga-ui/pull/12438">#12438</a>), as well as CSS selector specificity issues (<a href="https://github.com/taiga-family/taiga-ui/pull/12544">#12544</a>, <a href="https://github.com/taiga-family/taiga-ui/pull/12543">#12543</a>, <a href="https://github.com/taiga-family/taiga-ui/pull/12553">#12553</a>).</p><h4>Control Flow</h4><p>Before Control Flow appeared, the Taiga UI team had already been publishing the structural directive <a href="https://taiga-ui.dev/v4/directives/let">*tuiLet</a> for many years, which allowed you to declare a local variable in a template. The local variable declaration approach was extremely popular. It has now been replaced by the new built-in @let syntax.</p><p>The Taiga *tuiLet directive is now gracefully retiring, making way for the built-in @let syntax — which, as a nice bonus, requires zero imports!</p><h3>Shiny New Component Showcase</h3><p>We’ve significantly reworked the design and content of our documentation. It now features improved navigation and a more intuitive interface.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*q1B5QTClNgiB__zHLfNxFQ.png" /></figure><p>I’d also like to remind you that the engine behind our documentation is a reusable solution published as the npm package <a href="https://github.com/taiga-family/taiga-ui/blob/main/projects/addon-doc/README.md">@taiga-ui/addon-doc</a>. It’s already actively used not only for Taiga UI docs, but also in other Taiga Family products: <a href="https://maskito.dev">Maskito</a>, <a href="https://taiga-family.github.io/editor">Taiga Editor</a>, <a href="https://taiga-family.github.io/ng-morph">Ng Morph</a>, and <a href="https://taiga-family.github.io/ng-draw-flow">Ng Draw Flow</a>. This package keeps evolving and gaining new features (for example, a brand-new Table of Contents component was added in this release). You can always use @taiga-ui/addon-doc to build documentation for your own library!</p><h3>Browser Bump</h3><p>Here’s a little secret: the favorite part of every major release for Taiga UI maintainers is bumping the minimum supported browser versions. It unlocks new capabilities and features that we happily use in our libraries to write even cleaner and more reliable code.</p><p>Already in Taiga UI v4, we set a course for adding RTL support to our libraries. That’s right — we expanded our audience to include Middle Eastern and Central Asian products! With the new browser support, we got access to an even wider list of logical CSS properties, such as <a href="https://developer.mozilla.org/docs/Web/CSS/Reference/Properties/inset#browser_compatibility">inset</a>, <a href="https://developer.mozilla.org/docs/Web/CSS/Reference/Properties/padding-inline#browser_compatibility">padding-inline</a>, <a href="https://developer.mozilla.org/docs/Web/CSS/Reference/Properties/margin-inline#browser_compatibility">margin-inline</a>, and many others.</p><p>RTL support has become even easier to maintain, and the code is more robust (since it’s now handled by native <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Logical_properties_and_values">logical CSS properties</a> instead of our old fallbacks and workarounds).</p><p>We also got access to <a href="https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/BigInt">BigInt</a>. It feels nice to finally have access to all JavaScript data types — at least by 2026! :D We immediately put this to use and added BigInt support to our @maskito/kit library in the <a href="https://maskito.dev/kit/number">Number</a> mask and in our Taiga <a href="https://taiga-ui.dev/components/input-number#big-int-as-form-control-value">InputNumber</a> component.</p><h3>Angular Animations — Goodbye!</h3><p>Starting with Angular 20.2, the @angular/animations package has been marked as deprecated. And the official Angular documentation now <a href="https://angular.dev/guide/animations/migration">reads as follows</a>:</p><blockquote>You can replace all animations based on @angular/animations with plain CSS or JS animation libraries. Removing @angular/animations from your application can significantly reduce the size of your JavaScript bundle. Native CSS animations generally offer superior performance, as they can benefit from hardware acceleration.</blockquote><p>And we responded promptly. In Taiga UI v5, we completely dropped the @angular/animations package: all existing animations have been rewritten using pure native CSS, and @angular/animations has been entirely removed from the dependency list of our packages! No more mandatory provideAnimations() just to get Taiga UI up and running.</p><p>The trickiest part was natively rewriting the logic around the former <a href="https://v19.angular.dev/guide/animations/transition-and-triggers#animate-entering-and-leaving-a-view">:leave</a> mechanism. Currently, there’s no convenient CSS alternative for animating an element leaving the DOM without timers and boilerplate code. But my colleague found an <a href="https://www.angularspace.com/how-to-get-rid-of-angular-animations-right-now">elegant solution</a> to this problem as well. The @taiga-ui/cdk library got a new <a href="https://taiga-ui.dev/directives/animated">Animated</a> directive. Just slap this directive on any DOM element — when it appears or disappears, the element gets a CSS class tui-enter / tui-leave, and you can define any CSS animations for that class. All without code boilerplate and in the true Angular way!</p><p>It’s also great that we’re moving in the same direction as the Angular team. The Animated directive neatly anticipated the future syntax that the Angular team introduced very recently in their <a href="https://blog.angular.dev/announcing-angular-v21-57946c34f14b">v21 announcement</a> — <a href="https://angular.dev/guide/animations">animate.enter and animate.leave</a>. Migrating from our solution to the built-in Angular syntax will be as simple as it gets — just replace the tuiAnimated directive on an element with animate.enter=&quot;tui-enter&quot; and animate.leave=&quot;tui-leave&quot; (and keep the CSS files unchanged).</p><blockquote>A nice bonus: our Animated directive has been cherry-picked into the fourth major version of Taiga, so you can start gradually moving away from Angular animations in your app right now — even before you begin upgrading your Taiga UI version, and even if your app&#39;s Angular version is significantly older than 20.2. And when you upgrade Taiga to v5, @angular/animations will simply disappear from your node_modules.</blockquote><h3>Simplified Taiga UI Setup for Your Project</h3><p>Inspired by the modern Angular naming conventions for DI utility functions (<a href="https://angular.dev/api/router/provideRouter">provideRouter</a>, <a href="https://angular.dev/api/ssr/provideServerRendering">provideServerRendering</a>, <a href="https://angular.dev/api/core/provideZoneChangeDetection">provideZoneChangeDetection</a>, etc.), we decided to simplify the process of adding Taiga UI to your project. Now, to set up the DI tree, all you need is a single call to <a href="https://taiga-ui.dev/getting-started/Manual#provide-config">provideTaiga()</a>, which automatically provides all the necessary dependencies and initial configurations for our components to work.</p><p>For you, it’s just one function call, but under the hood it takes care of <a href="https://github.com/taiga-family/utils/tree/main/projects/font-watcher">automatic font scaling</a>, dark/light theme setup, and other internal configurations needed for Taiga UI components to function correctly!</p><h3>New Text Fields Are Now a Stable API</h3><p>We kicked off the large-scale text field refactoring back in the previous major version. At that time, we moved all our old control versions into the @taiga-ui/legacy package (to ensure a smooth migration path for users to the new public API) and started rewriting each component from scratch using fresh design specs and modern Angular features (decomposition via host directives + signals).</p><p>The task turned out to be quite challenging and extremely labor-intensive, but the results will genuinely make you happy. The new text fields offer a declarative way to customize through HTML markup and a uniform public API across all types of controls. Once you understand the customization concepts for one type of text field, you can easily customize any other control — without even looking at the docs!</p><p>Another key feature of the new text fields is their openness. Everything you need is exposed, so you can bring even the wildest ideas to life. Just take a look at this InputDate example:</p><pre>&lt;tui-textfield&gt;<br>  &lt;label tuiLabel&gt;Choose a date&lt;/label&gt;<br>  &lt;input tuiInputDate [formControl]=&quot;control&quot; /&gt;<br><br>  &lt;ng-container *tuiDropdown&gt;<br>    &lt;tui-calendar [markerHandler]=&quot;markerHandler&quot; /&gt;<br><br>    &lt;button<br>      tuiButton<br>      (click)=&quot;...&quot;<br>    &gt;<br>      Today<br>    &lt;/button&gt;<br>  &lt;/ng-container&gt;<br>&lt;/tui-textfield&gt;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*c-GRuYBc0uDP8S-jxIBlEA.png" /></figure><p>First, all the input parameters of the &lt;tui-calendar /&gt; component are fully at our disposal — no prop drilling like in the previous version of this control! Second, we can freely add any elements (even interactive ones!) to the calendar dropdown — for example, a &quot;Today&quot; button with a custom click handler!</p><p>And this applies to every type of text field (and there are over 15 of them!). With the release of the fifth major version, our large-scale text field refactoring is officially complete. Their public API is fully stabilized, and the previous generation of these controls has been permanently removed from the @taiga-ui/legacy package.</p><blockquote>For those who prefer gradual changes in life and code, we cherry-picked all important fixes for the new text fields into the fourth major version, making the migration to the new major as smooth as possible. Before upgrading to Taiga v5, you can iteratively update your codebase, mixing old and new text fields, so the upcoming migration is as comfortable and painless as it can be.</blockquote><h3>Improved Icon Component</h3><p>In the <a href="https://medium.com/angularwave/introducing-taiga-ui-v4-df6c7c62330f#2434">previous major release announcement</a>, I mentioned that for icons we moved away from the old approach of using <a href="https://developer.mozilla.org/ru/docs/Web/SVG/Element/svg">&lt;svg /&gt;</a> with the <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/use">&lt;use /&gt;</a> tag in favor of CSS masks.</p><p>Back then, the new approach was implemented in the <a href="https://taiga-ui.dev/components/icon">Icon</a> component and brought numerous benefits: the ability to store icons on a CDN and render them without DOM manipulations or a sanitizer, automatic browser caching, and a simplified way to scale icons.</p><p>But we didn’t stop there and continued to develop this further. Starting with the fifth major version, this component can do significantly more:</p><ul><li>You can now control not only the icon size but also the stroke width of the icon’s content. This capability is demonstrated really well in this interactive <a href="https://taiga-ui.dev/components/icon#parameters">documentation example</a>.</li><li>The updated component supports not only classic single-color icons but also multi-color and font-based icons! They’re managed through the @tui, @img, and @font prefixes, and the component figures out under the hood how to render the icon based on its type: via CSS mask, background-image, or font + content on the :before pseudo-element. You can see it in action in <a href="https://taiga-ui.dev/components/icon#basic">this example</a>.</li></ul><h3>And Much More!</h3><ul><li>Portals have been completely rewritten and streamlined into a single grid-based container. It’s now even easier to <a href="https://taiga-ui.dev/components/dialog#customization">customize dialogs</a>, simpler to manage <a href="https://taiga-ui.dev/components/notification">notifications</a> and their subtypes (<a href="https://taiga-ui.dev/components/notification/API?block=end&amp;inline=start">screen position</a>, <a href="https://taiga-ui.dev/components/toast#service">number of simultaneous instances</a>), and easier to create your own <a href="https://taiga-ui.dev/portals">custom portal entities</a> through new abstract classes and services.</li><li>A new <a href="https://taiga-ui.dev/components/popout">Popout</a> service that simplifies displaying custom content in <a href="https://developer.mozilla.org/docs/Web/API/Picture-in-Picture_API">Picture-in-Picture</a> mode or opening a piece of your application in an entirely new tab.</li><li>We stabilized the use of the <a href="https://developer.mozilla.org/docs/Web/API/CloseWatcher">CloseWatcher</a> web API in Taiga dialogs. Now, when a dialog is open on an Android device and the user taps the browser’s “Back” button, the dialog simply closes — instead of navigating to the previous route behind the open dialog.</li><li>A revamped <a href="https://taiga-ui.dev/components/accordion">Accordion</a>. We’re continuing the trend (that accompanies every Taiga major version) of minimal DOM element nesting in our components, which should make it easier for you to customize them and set native attributes like ARIA attributes or id — and now the accordion has joined the ranks of the “lucky ones.”</li><li><a href="https://github.com/taiga-family/utils/tree/main/projects/font-watcher">Automatic font scaling</a> is now enabled by default. Many users rely on iOS/Android system accessibility settings to increase the font size across all interfaces on their mobile devices — including web apps. Taiga components can read this system setting and adapt, making your interfaces comfortable for users with visual impairments.</li><li>We dropped the use of our custom decorators — you’re free to build your application with any value of the TypeScript <a href="https://www.typescriptlang.org/tsconfig/#experimentalDecorators">experimentalDecorators</a> flag without worrying that Taiga UI components will stop working.</li><li>We’re continuing to actively develop <a href="https://taiga-ui.dev/ai-support">AI tooling</a> support for working with Taiga UI components. Our <a href="https://github.com/taiga-family/taiga-ui-mcp">MCP server</a> now supports the new major version as well. We’re also closely watching the development of <a href="https://github.com/webmachinelearning/webmcp">WebMCP</a> to integrate its support into our documentation when the time comes.</li></ul><p>And a huge number of other improvements and new features that you can find in the <a href="https://github.com/taiga-family/taiga-ui/releases/tag/v5.0.0">release notes</a>.</p><h3>How to Upgrade?</h3><p>We did our best to make the migration to the new version as smooth as possible. To that end, we’ve prepared a bunch of migration scripts that will automatically analyze your code and fix most breaking changes — all with a single console command! And anything that was too complex to fix automatically via AST manipulations has been annotated with comments right in the code — complete with hints and links so you can easily finish the migration on your own or with the help of AI agents.</p><p>Detailed instructions for running the migration schematics and the necessary preparation steps are available in our <a href="https://taiga-ui.dev/migration-guide">upgrade guide</a>.</p><p>If you run into any issues or bugs during the upgrade, don’t hesitate to open an <a href="https://github.com/taiga-family/taiga-ui/issues/new/choose">issue</a> or ask a question in our <a href="https://t.me/taiga_ui/8242">Telegram community</a>. The Taiga team can’t wait for your feedback!</p><h3>See You Soon!</h3><p>To wrap up, let me just give you a little sneak peek at our future plans.</p><p>Very soon you’ll see revamped date picker components — our design team has already delivered fresh design specs with a more modern look for the calendars. During the rework, we plan to address the accumulated feature requests around their customization.</p><p>We’re also keeping a close eye on the development of <a href="https://angular.dev/essentials/signal-forms">signal-based forms</a>. As of this writing, the new API is still rough around the edges and continues to undergo changes. As soon as it fully stabilizes, we’ll make sure Taiga controls support them.</p><p>Finally, one of our big goals for this year is to keep pushing the accessibility of our components forward! All Taiga components traditionally support keyboard navigation but accessibility is a broader topic and we plan to dedicate even more attention to it this year.</p><p>See you in upcoming articles and releases!</p><p>And a reminder: the easiest way to say thank you is to give us a star on <a href="https://github.com/taiga-family/taiga-ui">GitHub</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=fef85dde3fc7" width="1" height="1" alt=""><hr><p><a href="https://medium.com/angularwave/whats-new-in-taiga-ui-v5-a-modern-angular-ui-kit-fef85dde3fc7">What’s New in Taiga UI v5: A Modern Angular UI Kit</a> was originally published in <a href="https://medium.com/angularwave">AngularWave</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Custom scrollbar in Angular]]></title>
            <link>https://medium.com/angularwave/custom-scrollbar-in-angular-93e23d47bf62?source=rss----ee024cab95cb---4</link>
            <guid isPermaLink="false">https://medium.com/p/93e23d47bf62</guid>
            <category><![CDATA[scroll]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[scrollbar]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[angular]]></category>
            <dc:creator><![CDATA[Alexander Inkin]]></dc:creator>
            <pubDate>Thu, 26 Mar 2026 09:22:47 GMT</pubDate>
            <atom:updated>2026-03-26T09:22:49.087Z</atom:updated>
            <content:encoded><![CDATA[<p>Now that Edge joined Chromium browsers happy family only Firefox lacks CSS scrollbars. These are great news but besides Firefox there are a lot of limitations in CSS solution. Just look at what kind of <a href="https://codepen.io/waterplea/pen/dVMopv">black magic</a> we have to resort to for a simple fade effect. For complete control we still have to rely on JavaScript. Let’s see how we can do it properly with Angular component.</p><figure><img alt="Scrollbar in action" src="https://cdn-images-1.medium.com/max/1024/0*KhTwWw_EM8POWQiM" /></figure><h3>CSS magic</h3><p>Of course, how would we do without it? First, we need to hide native scrollbars. We can do this in every browser these days.</p><blockquote>There is a standard rule <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/scrollbar-width">scrollbar-width</a>, which only Firefox support at the moment. For older Edge and IE version there’s similar -ms-overflow-style. We will not support IE though, because we are going to need position: sticky. Chrome and Safari can use ::-webkit-scrollbar pseudo selector.</blockquote><p>Second, to keep native scrolling behavior we need to have our component be scrollable container itself. This means instead of a scrolling wrapper we must come up with some sort of local position: fixed for thumbs. Absolute position will not work because thumbs would scroll with content.</p><p>We can achieve this with a clever combination of position: sticky for thumbs and display: flex for component. We are going to need two containers inside:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/abd5e23f78037abc38a6176fb42d328b/href">https://medium.com/media/abd5e23f78037abc38a6176fb42d328b/href</a></iframe><p>We will begin by isolating <strong>stacking context</strong>.</p><blockquote>Not everybody knows about <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context"><strong>stacking context</strong></a>, that’s why you can often run into z-index war with values like 1000, 1001, 9999. I encourage you to educate yourself in this subject, it would help you a ton!</blockquote><p>To keep everything inside content from overlapping the thumbs we would add the following style to the wrapper: position: relative, z-index: 0. It creates new stacking context and nothing from within would be able to overlap elements hanging above outside.</p><p>Bars need z-index: 1, to move them above content and a minimum width of <strong>100%</strong>. Now we have the following picture:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*0bFS_IV88Y9LAONj" /><figcaption>DOM elements layout</figcaption></figure><p>Bars have taken up all the space, shifting content to the right. Scrolling works fine and bars behave as if they were fixed inside component. Now we need to add margin-right: -100%, and they would «make room» for all the content.</p><blockquote>We can achieve this without flex using floats but bars wrapper will not be able to take up whole height if it is set implicitly (max-height, flex: 1 and so on).</blockquote><h3>Angular</h3><p>If you have read my other articles you know that I’m a huge declarative approach evangelist. This time won’t be an exception. Our code will be tidy as usual. We will only implement vertical scroll, horizontal is exactly the same. Let’s add thumb to the template:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e8aa0cd34c8365fe28c0de4e1119de9a/href">https://medium.com/media/e8aa0cd34c8365fe28c0de4e1119de9a/href</a></iframe><p>We position it with getters:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2b5fa4233dbd9a29333f49cb7ffad913/href">https://medium.com/media/2b5fa4233dbd9a29333f49cb7ffad913/href</a></iframe><p>Now all we need is to listen to scroll events to trigger change detection — our component works with wheel or trackpad. But we need to be able to drag thumbs. This logic is better suited for a directive. However, to keep everything transparent we will have it in our component for this tutorial.</p><blockquote>Dragging starts with mousedown event on a thumb, continues with mousemove on the document and ends with mouseup on the document.</blockquote><p>Let’s add event handlers on the thumb:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/5699186b98409a5fc2c38b11db16ed74/href">https://medium.com/media/5699186b98409a5fc2c38b11db16ed74/href</a></iframe><p>Inside component we add handlers that compute scroll position and listen to mouseup:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2e27d571f47f734f26e733d1b875ffaa/href">https://medium.com/media/2e27d571f47f734f26e733d1b875ffaa/href</a></iframe><p>Now thumbs dragging works too.</p><h3>Angular magic</h3><p>Seasoned Angular developer might have noticed that this solution has very bad performance. For every instance of a scrollbar we listen to all mousemove events on the whole document and trigger change detection on every single one of them. This won’t do. Thankfully, Angular provides a way to manage event handling which <a href="https://indepth.dev/supercharge-event-management-in-your-angular-application/">I wrote about before</a>. With <a href="https://github.com/TinkoffCreditSystems/ng-event-plugins">@tinkoff/ng-event-plugins</a> library we can get rid of unnecessary change detection runs. To do so we will add a modifier .silent to the event name and @shouldCall decorator to the handler:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/62c7647e0bc3b49fe5a8cf5a73e282a1/href">https://medium.com/media/62c7647e0bc3b49fe5a8cf5a73e282a1/href</a></iframe><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8bb301537b2f8c868f185ba7f3d93b43/href">https://medium.com/media/8bb301537b2f8c868f185ba7f3d93b43/href</a></iframe><blockquote>Until Angular 10 we have to couple @shouldCall with @HostListener(‘init.xxx’, [‘$event’]). We need this to trigger change detection, while markDirty is private. Read about it in detail in <a href="https://indepth.dev/supercharge-event-management-in-your-angular-application/">aforementioned article</a>.</blockquote><p>To check if we need to call the handler we can use this simple function:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b7117d0807f293f6adc0b6202b4d7f68/href">https://medium.com/media/b7117d0807f293f6adc0b6202b4d7f68/href</a></iframe><p>Now event handlers and change detection will be triggered only if we are dragging the thumb. Our scrollbar is ready to use. Full scrollbar demo is <a href="https://stackblitz.com/edit/angular-scrollbar-component">available on Stackblitz.</a></p><h3>Directive</h3><p>As I said before, we can isolate drag logic into directive. Originally I was happy with what we had, but comments from colleagues got me thinking. It would make our code more declarative and simple. We can even get away with just one Output made of single RxJs chain using fromEvent. I will not go into details here, just look at the code:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ebff2ca2f0d6fb44e71ca89200fa3392/href">https://medium.com/media/ebff2ca2f0d6fb44e71ca89200fa3392/href</a></iframe><p>Calculations we moved to separate functions, full code is available on <a href="https://stackblitz.com/edit/angular-scrollbar-component-directive?file=src%2Fapp%2Fscrollbar%2Fdraggable.directive.ts">updated Stackblitz here</a>.</p><p>As an improvement, we can also watch for component resize to recalculate thumbs size and position. It’s a great case for a ResizeObserver which you can use in Angular with ease if you <a href="https://medium.com/its-tinkoff/service-as-observable-29ce7fa9085b">read this article</a>. Or head straight to our library <a href="https://github.com/ng-web-apis/resize-observer">@ng-web-apis/resize-observer</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=93e23d47bf62" width="1" height="1" alt=""><hr><p><a href="https://medium.com/angularwave/custom-scrollbar-in-angular-93e23d47bf62">Custom scrollbar in Angular</a> was originally published in <a href="https://medium.com/angularwave">AngularWave</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Natively colored SVG icons]]></title>
            <link>https://medium.com/angularwave/natively-colored-svg-icons-e08ae1ea7d01?source=rss----ee024cab95cb---4</link>
            <guid isPermaLink="false">https://medium.com/p/e08ae1ea7d01</guid>
            <category><![CDATA[angular]]></category>
            <category><![CDATA[svg]]></category>
            <category><![CDATA[html]]></category>
            <category><![CDATA[css]]></category>
            <category><![CDATA[javascript]]></category>
            <dc:creator><![CDATA[Alexander Inkin]]></dc:creator>
            <pubDate>Thu, 26 Mar 2026 09:22:26 GMT</pubDate>
            <atom:updated>2026-03-26T09:22:28.127Z</atom:updated>
            <content:encoded><![CDATA[<p>When you need to be able to color your SVGs with CSS, what do you do? You don’t have much options here. Typically, you would either use an icon font or you can request icons manually and inline them in HTML. Icon font has to be optimized, otherwise users would load all icons upfront. Inlining causes many heavy DOM operations if you have a lot of icons on a page. It is detrimental to performance and could be dangerous.</p><blockquote>To safely inline SVGs you need to sanitize them. Built-in Angular sanitizer, for example, does not support SVGs. It clears them into empty strings. You can use <strong>DOMPurify</strong> as a trusted solution and our library <a href="https://github.com/TinkoffCreditSystems/ng-dompurify"><strong>ng-dompurify</strong></a> if you need it in an Angular app.</blockquote><p>Let’s look at another option available in modern browsers — SVG tag <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/use">USE</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*52zxaB_xlv-81m6FwtSr3g.png" /></figure><h3>What use is USE to us?</h3><p>This tag is intended for reusing symbols and entire SVGs across the document. But in modern browsers (sorry IE) it can even reach for external sources!</p><blockquote>External SVGs must be on the same origin, so CDNs will not work, unfortunately. <a href="https://github.com/w3c/svgwg/issues/707">Yet</a>.</blockquote><p>This provides a native way to inline SVGs in Shadow DOM, kinda like IMG tag with src, but also styleable with CSS. And it works with cache too! We need to prepare our icons a little bit though, let’s see what we need to do.</p><figure><img alt="Altering source code of our SVGs" src="https://cdn-images-1.medium.com/max/1024/1*hk0jU1sMsrKQ4KAiHqUM8g.png" /></figure><p>First, we have to create a symbol in each icon and move viewBox data in it.</p><p>Next, we need to set fill (or stroke) color to currentColor so we can then use CSS color to control it. We can also set SVG rules like fill and stroke on other elements to inherit. It would allow us to have multicolor icons, see example below.</p><p>Once our icons are prepared we can dump them in our assets folder and use them!</p><figure><img alt="Adding SVG icon with USE tag" src="https://cdn-images-1.medium.com/max/1024/1*r0E0ILjZkrUxR9qAwFCGIA.png" /></figure><h3>Named icons component for Angular</h3><p>Writing full path and reaching for symbol every time is tedious. Let’s create an Angular component that would work with icons by their names. With Dependency Injection it is very easy.</p><p>We need a token to provide a path to all our icons and a simple component. It would resolve href based on the name and given path. We can even target native SVG with selector. This way we do not need to control size on component’s side.</p><blockquote>Keep in mind that Safari below 12.1 only supports deprecated xlink:href so it’s better to have both.</blockquote><p>We will have stroke and fill transparent. This allows us to control multiple colors with CSS.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jKM4DltoIXI_TGOK85qP9g.png" /></figure><p>Let’s give our component a shot:</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fstackblitz.com%2Fedit%2Fangular-colored-svg%3Fembed%3D1&amp;display_name=StackBlitz&amp;url=https%3A%2F%2Fstackblitz.com%2Fedit%2Fangular-colored-svg&amp;image=https%3A%2F%2Fc.staticblitz.com%2Fassets%2Ficon-1d2450aadcf984376236c23cd2ab0ba231ddd1d85c9b72990ed2ede5b4974d1b.png&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=stackblitz" width="745" height="400" frameborder="0" scrolling="no"><a href="https://medium.com/media/17d8297fdb179c7fc06d80b0365073d8/href">https://medium.com/media/17d8297fdb179c7fc06d80b0365073d8/href</a></iframe><h3>Conclusion</h3><p>This approach’s limitations are no IE support and same origin rule. If you can live with these it might be an easy alternative to other solutions. You do not have to bundle your icons in your app or manually load them. You can rely on cache to speed things up and no DOM operations makes it much faster than inlining full icons. Happy coloring!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e08ae1ea7d01" width="1" height="1" alt=""><hr><p><a href="https://medium.com/angularwave/natively-colored-svg-icons-e08ae1ea7d01">Natively colored SVG icons</a> was originally published in <a href="https://medium.com/angularwave">AngularWave</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[5 tips to boost your Angular skills]]></title>
            <link>https://medium.com/angularwave/5-tips-to-boost-your-angular-skills-ae665c10816f?source=rss----ee024cab95cb---4</link>
            <guid isPermaLink="false">https://medium.com/p/ae665c10816f</guid>
            <category><![CDATA[declarative-programming]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[angular]]></category>
            <category><![CDATA[rxjs]]></category>
            <category><![CDATA[dependency-injection]]></category>
            <dc:creator><![CDATA[Alexander Inkin]]></dc:creator>
            <pubDate>Thu, 26 Mar 2026 09:21:49 GMT</pubDate>
            <atom:updated>2026-03-26T09:21:50.900Z</atom:updated>
            <content:encoded><![CDATA[<p>This summer me and <a href="https://twitter.com/marsibarsi">Roman</a> started a series of tweets with helpful tips and tricks about Angular. It was met well by the community so I decided to write an article follow-up. Here are 5 generalized advises I want to give to Angular developers across the globe. These advises are backed by some concrete examples pulled from our Twitter activity. They can help you improve your developer skills or give you some practical tricks at the very least.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bQ9xLFwtHkBQe_SNLPaa_A.png" /></figure><h3>1. Know how change detection works in Angular</h3><p>There are many great articles going deep into change detection mechanism. Like <a href="https://indepth.dev/everything-you-need-to-know-about-change-detection-in-angular/">this one</a>. So let’s just recap the basics quickly and get to the tips.</p><h4>Recap</h4><p>Angular has two change detection modes: Default and OnPush. First one triggers change detection for every tick happened anywhere in the app. Second only marks view for checking if an event has happened in this view or if input data has changed.</p><h4>Default vs OnPush</h4><p>There’s really no reason for you to use Default. All you need to do is write your code the way framework expects it to and you won’t get into trouble with OnPush. This means, you should <strong>never mutate your data</strong>. If your input arrays or objects change immutably, OnPush would pick this up and refresh the view.</p><p>When you subscribe to events with @HostListener Angular has got you covered as well. But what if you are working with RxJS streams? You can always inject ChangeDetectorRef and do markForCheck() when you need it. But a declarative option would be to end up with async pipe in your template. It would trigger change detection on each emit.</p><p>You might have seen this pattern:</p><pre>&lt;div *ngIf=&quot;stream$ | async as result&quot;&gt;<br>  ...<br>&lt;/div&gt;</pre><p>But what do you do when you need <strong>falsy</strong> results too? You can strip condition logic from ngIf and create a simple structural directive. It would be used only to add context to the view:</p><h3></h3><p>💡#AngularTip for the day! Like declaring async results with *ngIf=&quot;stream$ | async as result&quot; but need to support falsy values? Here&#39;s a simple #angular directive that does just that! 👍https://t.co/4DR9pdZsIc pic.twitter.com/E9rjPwnnAn</p><h4>NgZone</h4><p>If you cannot fully switch to OnPush there are still things you could optimize. You can inject NgZone and do performance sensitive actions in .runOutsideAngular(). This way there will be no extra ticks in the change detection mechanism. Even components with Default strategy will not perform change detection cycle. You should do this for events that trigger rapidly like mousemove, scroll. To do it declaratively with RxJS streams you can create two operators. One for leaving the zone and another to return to it if you need to trigger change detection:</p><h3></h3><p>💡#AngularTip for the day! Manage NgZone in your #RxJS streams to avoid extra ticks in you #Angular app with these 2 simple operators🧑‍💻Code: https://t.co/GwoCJYLTYp pic.twitter.com/1SdcJQrwQl</p><blockquote>Another option you can have with @HostListener decorator is custom event manager plugin. We released an open-source library called <a href="https://github.com/TinkoffCreditSystems/ng-event-plugins">ng-event-plugins</a>. It allows you to filter out unnecessary change detection cycles. Read more about it in <a href="https://indepth.dev/supercharge-event-management-in-your-angular-application/">this article</a>.</blockquote><h3>2. Learn RxJS. Seriously!</h3><p>RxJS is a very powerful tool. We all use it to some extent when working with Angular but really mastering it can help you <strong>a lot</strong>. From very simple streams that allow you to reload your component…</p><h3></h3><p>💡#AngularTip for the day! Create a simple #RxJS stream to quickly reload your #Angular component♻️ pic.twitter.com/e7OaTNgL2A</p><p>…to complex operators combination. Like this example recognizing various types of scroll:</p><h3></h3><p>💡#AngularTip for the day! Learn how to differentiate regular scroll from momentum scroll in your #Angular app on mobile with #RxJS📱Live demo: https://t.co/5egs6gXexp pic.twitter.com/3eQQcWCzg5</p><p>Just take a look how easy it is to create a sticky header that disappears when you scroll down. A little bit of CSS and pretty basic RxJS:</p><h3></h3><p>💡#AngularTip for the day! Create an #Angular directive for sticky header that disappears when you scroll down👍Live demo:https://t.co/wli6vAnf9GWINDOW token from our library:https://t.co/kgfN68QtIyUnsubscribe with destroy service from this tweet: https://t.co/7fdQgJTFLB pic.twitter.com/Xlc2je9Kny</p><p>I cannot stress enough how important RxJS knowledge is to an Angular developer long term. While it’s simple on the first look, RxJS requires a little bit of paradigm shift. You need to start thinking in terms of streams. But once you do, your code would become more declarative and easier to maintain.</p><p>There is not much I can suggest here other than practice. If you see a case that you can solve with RxJS — try to do so. Avoid side-effects and nested subscribes. Keep your streams organized and watch out for memory leaks (read further).</p><h3>3. Max out TypeScript</h3><p>We all use TypeScript in our Angular applications. But to get the most of it we need to push it to the limits. I rarely see projects with enabled strict: true. This is something you totally should do. It will save you from lots of cannot read property of nulls and undefined is not a functions.</p><h4>Generics</h4><p>In TypeScript we can use generics when the type we work with is unclear. Combination of generics, overloads and type narrowing can make a pretty solid API. You almost never will have to typecast. Take a look at this example of strongly typed RxJS method fromEvent:</p><h3></h3><p>💡#AngularTip for the day! Tired of typecasting event target or defining event type for #RxJS method &#39;fromEvent&#39;? Add custom type and a typed wrapper function!🎯Live example: https://t.co/Uo003HLZ0x#Angular #TypeScript #100DaysOfCode #developer pic.twitter.com/CbdQj424I3</p><p>With it you can be sure that target in the event has the same type as the element you listen the event on. And event has properly narrowed type.</p><blockquote>Generics based APIs have the benefit of being data-model agnostic. This means people can use it without being forced to a particular interface</blockquote><p>There are articles dealing with things like type inference, advanced types, unions. I suggest you educate yourself in the subject, it would help you make robust code in the long run. My one last advice here is <strong>never use </strong><strong>any</strong>. You can almost always replace it with generic or unknown which is a safer version of any.</p><h4>Decorators</h4><p>Don’t forget about other TypeScript features, such as decorators. When used well, they can really improve your code. There are cases when type is correct, but logically value is unacceptable. Like when you have a number input for quantity — you cannot really have negative or decimal values. But according to TypeScript they are correct. You can protect your components for such invalid inputs with assertion decorator:</p><h3></h3><p>💡#AngularTip for the day! Protect your #angular components from illegal but properly typed inputs with assertion #TypeScript decorator🦺Code: https://t.co/8P4SANVnjx pic.twitter.com/pwJVGgFevt</p><p>Did you know that if you decorate your abstract classes you do not have to pass arguments to super()? Angular will handle it for you:</p><h3></h3><p>💡#AngularTip for the day! If you decorate your abstract class, #Angular will resolve constructor parameters and you do not need to add constructor to concrete implementation!🏗️ pic.twitter.com/hUwz671iPF</p><p>Another good case for custom decorator is some reusable processing logic. For example, in our <a href="https://github.com/ng-web-apis/audio">Web Audio API library for Angular</a> we turn declarative input bindings to imperative native commands with a strongly typed decorator:</p><p><a href="https://github.com/ng-web-apis/audio/blob/master/projects/audio/src/decorators/audio-param.ts">ng-web-apis/audio</a></p><blockquote>You can read about this library in detail <a href="https://www.newline.co/@waterplea/writing-retrowave-in-angular--20856fee">here</a></blockquote><h3>4. Angular’s DI is king! Use it. Then use it more!</h3><p>Dependency Injection is part of the reason Angular is such a powerful framework. Arguably it is <strong>THE </strong>reason. Too often it is not used to its full potential.</p><blockquote>Head over to this <a href="https://angular.institute/di">dedicated article about DI</a> we wrote to deepen you knowledge about it</blockquote><h4>RxJS</h4><p>When it comes to practical advises, I mentioned before to watch out for memory leaks in your RxJS streams. Mainly this means: <strong>if you manually subscribe to a stream you need to unsubscribe yourself</strong>. An idiomatic solution in Angular would be to encapsulate this logic into a service:</p><h3></h3><p>💡#AngularTip for the day! Create a simple #RxJs Observable service to encapsulate destruction logic of your #Angular components and directives🔧 pic.twitter.com/iD8uLvl9x3</p><p>You can also create shared streams and add them to DI tree. There’s no reason to have separate requestAnimationFrame based Observables across the app. Create a token for it and reuse. You can also add zone operators discussed above to it:</p><h3></h3><p>💡#AngularTip for the day! Create a shared requestAnimationFrame-based #RxJS Observable to use across your #Angular app⏱️ℹ️ or just use our opensource library of tokens for native APIs: https://t.co/kgfN6984A6 pic.twitter.com/Drj7YL9RHd</p><h4>Tokens</h4><p>DI is also a good way to make your components more abstract. If you do not rely on global objects, such as window or navigator — you are ready for Angular Universal which renders you app on the server side. As you know, node.js does not have DOM and many of the global objects we are used to. It is also much easier to test such code because dependencies are effortlessly mocked. It is not difficult at all to tokenize these objects. We can use factories when we define injection token and top level injector is available to us. Using built-in DOCUMENT it takes just a few lines to create WINDOW token:</p><h3></h3><p>💡#AngularTip for the day! You can use &#39;inject&#39; method from &#39;@angular / core&#39; in the context of a factory function to reach for type-safe dependencies for #angular injectable. Here&#39;s how easy it is to tokenize global window!💪Read more: https://t.co/abP7GNgsie pic.twitter.com/5Y7eVBX2xg</p><blockquote>To save time, use this <a href="https://github.com/ng-web-apis/common">open-source library</a> where we already created some of those <em>tokens. There’s also its</em> <a href="https://github.com/ng-web-apis/universal">Angular Universal counterpart</a> with mocks. Feel free to <a href="https://github.com/ng-web-apis/common/issues">request other tokens</a> to be added!</blockquote><p>Tokens and factories are very powerful tools. Combined with hierarchical nature of dependency injection they can help you make your app very modular. Read more about clever use of providers in this article.</p><h3>5. Throw emperor down the abyss. Like Vader.</h3><p>Angular with its template bindings and decorators clearly pushes us to write declarative code. I’ve mentioned this approach above. I will not get into discussion of benefits it has over imperative code here. Instead I’ll give you a solid advice: <strong>write declarative code</strong>. When you get used to it, you’d appreciate it.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/500/1*3YtSRRgM05DXHZNun1HN8A.gif" /><figcaption>Imperative approach is not how we do things ‘round here</figcaption></figure><h4>Getters</h4><p>What does it mean to write declarative code? First of all, try to use ngOnChanges as rare as you can. It’s a poorly typed side-effect which is really only needed if you must perform an action upon multiple inputs change. On a single one a <em>setter</em> is a more streamlined solution. And if instead of action you wanted to update some internal state, think if you can remove that manual state and replace it with calculated <em>getter</em>.</p><p>Performance and getters is a subject for a separate article which I hope to get to writing soon. For now, just take a rule of thumb to not create new arrays or objects in getters. If you need to calculate a new object, use memoization techniques like pipes, for example.</p><h3></h3><p>💡#AngularTip for the day! Sparkle come #CSS variables magic over your #Angular app and create isolated themes with 1 line of code!🎨Live demo: https://t.co/Brm3Lr4Zq6🚨Be careful though! Angular style binding does not sanitize content! pic.twitter.com/MzZkxmprFY</p><p>Here’s a good case for a <strong>setter</strong> which I’ve omitted for the brevity of the example (but was reminded of by an attentive follower).</p><h4>Template reference variables</h4><p>Instead of manually requesting elements for the view, Angular has @ViewChild decorator. Quite often though, you don’t really need it if you can enclose logic in the template:</p><pre>&lt;input #input&gt;<br>&lt;button (click)=&quot;onClick(input)&quot;&gt;Focus&lt;/button&gt;</pre><p>Here we pass template reference variable directly to the method where we need it. This makes our component code clear. Think of it as sort of a <em>closure</em> in a template. But what do we do if we need a DOM element of an underlying component? We could have @ViewChild(MyComponent, {read: ElementRef} but we can do this without decorator if we create a directive with exportAs:</p><h3></h3><p>💡#AngularTip for the day! Ever needed to get #HTML element behind your #Angular component with a template reference variable? Write a simple directive to expose it!🔍 pic.twitter.com/XfFtcQsGXT</p><h4>Dynamic content</h4><p>People often use ComponentFactoryResolver to create dynamic components imperatively. Why, if there is ngComponentOutlet directive? Because this way you have access to the instance and can pass some data to it. A proper way to solve this issue is, once again, <strong>dependency injection</strong>. ngComponentOutlet allows you to pass custom Injector which you can create with data provided through token.</p><p>In fact, to create dynamic content you have <em>interpolation</em>, <em>templates</em> and <em>components</em>. And there is not much difference between these approaches. You have <strong>context</strong> and your have a <strong>representation</strong>:</p><h3></h3><p>💡#AngularTip for the day! When creating customizable #Angular components, think in terms of content and context, not in terms of templates, functions, literals. Check out my #opensource project that acts as interpolation, template &amp; component outlet!👍https://t.co/piqcF8uujb pic.twitter.com/xINZsRuxjE</p><p>I’ve been using this content-agnostic approach to customizable components for a long time. We’ve released a tiny open-source library called <a href="https://github.com/TinkoffCreditSystems/ng-polymorpheus">ng-polymorpheus</a>. It doesn’t do anything really, just pass content to the proper built-in Angular tool, be it ngTemplateOutlet, ngContentOutlet or simple function interpolation. Once you get used to it, there’s no going back!</p><p><strong>This wraps it up for this article. Hope you would find these tips useful!</strong></p><blockquote>If you like this, you can check <a href="https://twitter.com/search?q=%23AngularTip%20(from%3AWaterplea%2C%20OR%20from%3Amarsibarsi)&amp;src=typed_query">all of our tips</a>. We also wrote an <strong>advanced Angular handbook</strong> which we continue to develop over at <a href="http://angular.institute/">angular.institute</a>. Happy coding!</blockquote><h4>Bonus</h4><p>A simple tip that was received unexpectedly well is Luhn algorithm in form of an Angular Validator. So if you are working on some application where users enter their credit card number, you can use this code to check its validity 😉</p><h3></h3><p>💡#AngularTip for the day! Create #Angular validator to check validity of entered card number with Luhn algorithm💳 pic.twitter.com/QdPPpCqwdK</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ae665c10816f" width="1" height="1" alt=""><hr><p><a href="https://medium.com/angularwave/5-tips-to-boost-your-angular-skills-ae665c10816f">5 tips to boost your Angular skills</a> was originally published in <a href="https://medium.com/angularwave">AngularWave</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ControlValueAccessor and contenteditable in Angular]]></title>
            <link>https://medium.com/angularwave/controlvalueaccessor-and-contenteditable-in-angular-6ebf50b7475e?source=rss----ee024cab95cb---4</link>
            <guid isPermaLink="false">https://medium.com/p/6ebf50b7475e</guid>
            <category><![CDATA[angular]]></category>
            <category><![CDATA[rich-text-editor]]></category>
            <category><![CDATA[contenteditable]]></category>
            <category><![CDATA[wysiwyg]]></category>
            <category><![CDATA[javascript]]></category>
            <dc:creator><![CDATA[Alexander Inkin]]></dc:creator>
            <pubDate>Thu, 26 Mar 2026 09:20:59 GMT</pubDate>
            <atom:updated>2026-03-26T09:21:00.723Z</atom:updated>
            <content:encoded><![CDATA[<p>Have you wondered how Angular forms work with HTML elements where user enters data?</p><p>From the beginning, it was a responsibility of ControlValueAccessor. Angular has several accessors out of the box: for checkboxes, inputs, selects. Let’s say you develop a chat application. You need to be able to change style of the text. Make it bold or underlined, add emojis and so on. You would most likely use contenteditable attribute to handle formatting.</p><p>Angular does not have an accessor for contenteditable, so if you want to use it with forms you will have to write one.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yPYMODI2jVDLaah7CKu5Qw.png" /></figure><h3>ControlValueAccessor</h3><p>Directive that we will create is going to react to contenteditable attribute. And it will implement ControlValueAccessor interface:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1c2975ba07b0d73577c5c12628c939ac/href">https://medium.com/media/1c2975ba07b0d73577c5c12628c939ac/href</a></iframe><p>To support both reactive and template forms we will provide a special InjectionToken:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/58d52e02178366a27f97f34a082507cf/href">https://medium.com/media/58d52e02178366a27f97f34a082507cf/href</a></iframe><p>We need to implement 3 required and 1 optional methods:</p><ul><li>registerOnChange — this method will receive a function during control initialization. Our directive needs to call that function with a new value when user enters something in an editable element. This would propagate value to the control.</li><li>registerOnTouched — this method will receive a function during control initialization. We need to call it when user left our element so control would become touched. It is important for validation.</li><li>writeValue — this method will be called by control to pass value to our directive. It is used to sync value when it is changed externally (setValue or ngModel variable change), and to set initial value.</li></ul><blockquote>Worth mentioning that template forms have a bug related to this method. Initial value is provided with a slight delay and writeValue is called twice, first time with null:<br><a href="https://github.com/angular/angular/issues/14988">https://github.com/angular/angular/issues/14988</a></blockquote><ul><li>setDisabledState <em>(optional)</em> — this method will be called by control when disabled state is changed. Even though it is optional it is a good idea to have it, otherwise we cannot have our control disabled.</li></ul><h3>Implementation</h3><p>To work with DOM element we need Renderer2 and element itself so let’s add them to constructor:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c75579fdd6afc3f73cc0dd74bd58ed87/href">https://medium.com/media/c75579fdd6afc3f73cc0dd74bd58ed87/href</a></iframe><p>We will store references to the methods control gives us:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/58f6d51d7ebbb663648255d8d9205664/href">https://medium.com/media/58f6d51d7ebbb663648255d8d9205664/href</a></iframe><p>disabled for contenteditable is equal to contenteditable=&quot;false&quot;. Setting value is the same as setting innerHTML for DOM element. Value change and focus loss can be tracked with events:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d07b774835a758bb83a79c9d4c4effd4/href">https://medium.com/media/d07b774835a758bb83a79c9d4c4effd4/href</a></iframe><p>That’s it. This is enough to make Angular forms work with contenteditable elements.</p><h3>Internet Explorer</h3><p>There are couple of “buts”.</p><p>First, initial control value is null and after writeValue in IE11 this is what we will see in the template, literal “null” string. To handle this we need to normalize value:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9b7a05193ca887a2c92669e9fda3fa55/href">https://medium.com/media/9b7a05193ca887a2c92669e9fda3fa55/href</a></iframe><p>Here we also take care of the following case. Say element had some HTML tags inside. If we just select everything and hit backspace — content gets replaced with a single &lt;br&gt; tag. It’s best to consider it equal to empty string so our control thinks it’s empty.</p><p>Second, Internet Explorer does not support input event for contenteditable elements. So we are gonna have to make a fallback with aMutationObserver.</p><blockquote>By the way, if you want to use MutationObserver as a declarative Angular directive or an Observable-based service you are welcome to take a look at our little library: <a href="https://github.com/ng-web-apis/mutation-observer">https://github.com/ng-web-apis/mutation-observer</a></blockquote><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/433ffca2ff73e15c39a9843a1069c3bc/href">https://medium.com/media/433ffca2ff73e15c39a9843a1069c3bc/href</a></iframe><p>Instead of checking the browser we will just turn MutationObserver off on a first input event:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/bb4ae32cf9c798bc92b9895ca038c93f/href">https://medium.com/media/bb4ae32cf9c798bc92b9895ca038c93f/href</a></iframe><p>Now our component works in IE11 and we are proud of ourselves!</p><h3>Internet Explorer! ლ(ಠ益ಠლ)</h3><p>Unfortunately, IE11 will not let go so easily. Apparently it has a bug in MutationObserver. Say contenteditable has tags, for example some &lt;b&gt;text&lt;/b&gt;. Removing text that would cause a whole tag to be removed (word text in this example), observer callback will be triggered <strong>before actual changes to the DOM!</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*kQHVxSww0GL8a54i.jpeg" /></figure><p>Unfortunately, we have to call it a loss and resort to using setTimeout:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f95b5e8f214b8e8c417776de00464c41/href">https://medium.com/media/f95b5e8f214b8e8c417776de00464c41/href</a></iframe><h3>Summary</h3><p>Considering Angular has to support IE9, 10 and 11 we can understand why they did not make such accessor built-in.</p><blockquote>We need to remember that HTML can have malicious code placed in it. We shouldn’t blindly add it do the DOM.</blockquote><p>And we cannot trust users either so we must watch out for paste and drop events. Such content has to be sanitized. A good and trusted tool for that is <a href="https://github.com/cure53/DOMPurify"><em>DOMPurify</em></a>. <a href="https://medium.com/angular-in-depth/warning-sanitizing-html-stripped-some-content-and-how-to-deal-with-it-properly-10ff77012d5a">Read here</a> how we implemented it in an Angular app or just download our package:</p><p><a href="https://github.com/TinkoffCreditSystems/ng-dompurify">TinkoffCreditSystems/ng-dompurify</a></p><p>You can also grab the accessor we’ve created in this article as a tiny package that works in Angular 4+:</p><p><a href="https://github.com/TinkoffCreditSystems/angular-contenteditable-accessor">TinkoffCreditSystems/angular-contenteditable-accessor</a></p><p>A live playground:</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fstackblitz.com%2Fedit%2Fangular2-contenteditable-value-accessor%3Fembed%3D1&amp;display_name=StackBlitz&amp;url=https%3A%2F%2Fstackblitz.com%2Fedit%2Fangular2-contenteditable-value-accessor&amp;image=https%3A%2F%2Fc.staticblitz.com%2Fassets%2Ficon-03bbb9acbba813917725387f46e5df03a95d232be240e910ce5f946a973b3f86.png&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=stackblitz" width="745" height="400" frameborder="0" scrolling="no"><a href="https://medium.com/media/3f86c91c081c5486fd31a430daffc107/href">https://medium.com/media/3f86c91c081c5486fd31a430daffc107/href</a></iframe><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6ebf50b7475e" width="1" height="1" alt=""><hr><p><a href="https://medium.com/angularwave/controlvalueaccessor-and-contenteditable-in-angular-6ebf50b7475e">ControlValueAccessor and contenteditable in Angular</a> was originally published in <a href="https://medium.com/angularwave">AngularWave</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Compliant components: Declarative approach in Angular]]></title>
            <link>https://medium.com/angularwave/compliant-components-declarative-approach-in-angular-520c2bf95522?source=rss----ee024cab95cb---4</link>
            <guid isPermaLink="false">https://medium.com/p/520c2bf95522</guid>
            <category><![CDATA[declarative-programming]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[angular]]></category>
            <dc:creator><![CDATA[Alexander Inkin]]></dc:creator>
            <pubDate>Thu, 26 Mar 2026 09:20:16 GMT</pubDate>
            <atom:updated>2026-03-26T09:20:17.315Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TdFS1xNUadq0QiqLynL9MA.png" /></figure><p>When I first heard about compliant mechanisms I was very impressed. Even though they surround us in our daily lives — we barely ever think about what they are conceptually.</p><p>These are things like backpack latches, mouse buttons, shampoo caps. In short, a compliant mechanism uses deformation to achieve its technical characteristics. In traditional engineering, flexibility is usually a disadvantage of the material. Compliant mechanisms on the contrary use it to transfer force and movement, instead of passing it through multiple moving parts as rigid body mechanisms do.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*qbWLT88fp7KYbmsL" /><figcaption>Comparing compliant pliers to traditional rigid body one</figcaption></figure><p>In such a system movement is very easy to predict. Because this is a single item, rather than a set of parts with hinges, springs, and joints all contributing to friction, wearing, and backlash.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Stu8wWatrCmCTYZ8" /><figcaption>Movement is precise</figcaption></figure><p>This precision is understandable. The movement goes through flexible connections that cannot contract or expand. So it’s always auto-corrected and guided by the whole item.</p><blockquote>I find it to be a perfect analogy to the declarative approach in programming, as opposed to imperative in which commands manually alter the state. Just like hinges and joints pass force.</blockquote><p>I would like to share my view on writing Angular code. It’s sort of a philosophy or habit I developed over the years of working on a big UI library.</p><h3>Compliant components</h3><p>Angular always assumes some level of declarative code. Data bindings, event handling, and the Observable model — these all influence our style. Simple components are literally written with statements. We write what is what and how it depends on the environment instead of a set of instructions altering the state. But with increasing complexity, components might lose this quality. They turn into a series of calls, subscriptions, and stored data managing behavior. Syncing all this becomes a struggle.</p><p>The imperative approach states “if a car is a firetruck, paint it red”. The declarative way would be “firetrucks are red”. In Angular, this is effectively <em>a getter</em>. In a closer look, often the state for the component is the inputs and the rest is all calculable. To better grasp this idea, let’s create a few components utilizing this approach.</p><h4>LineChart</h4><p>This component is pretty straightforward at its core. We provide an array of tuples and we need to make an SVG path over those points on a 2D plane. What we want is to organize the component code so it doesn’t have imperative state manipulation. We can hook it to native SVG to have less nesting:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7fed8e8ebf8aa8ff6c39cd39e897e889/href">https://medium.com/media/7fed8e8ebf8aa8ff6c39cd39e897e889/href</a></iframe><p>Besides actual data, we need to outline the area to display. This could be the viewBox itself. But it is much more user friendly to enter its properties separately and calculate it with a getter:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/5b72f03292f7f96b24fdd3102ce87784/href">https://medium.com/media/5b72f03292f7f96b24fdd3102ce87784/href</a></iframe><p>To make our component spicier let’s add smoothing input as well. The template would consist of a single path element:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/dd2f8ee048a501ccb6d2a5d79dfca50d/href">https://medium.com/media/dd2f8ee048a501ccb6d2a5d79dfca50d/href</a></iframe><p>We need to calculate the d attribute. One option is to have a setter on data input. But later we might want to add other related elements like fill under the line or hints for points. Instead, let’s create it with a getter as well so we will not have to manage it ourselves:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1b7650692ac03c9053923f8faed89ede/href">https://medium.com/media/1b7650692ac03c9053923f8faed89ede/href</a></iframe><p>That’s it. The actual draw functions to make the path are irrelevant and can be googled. But read further for a live demo of all the components we are about to create complete with source code.</p><h4>Media directive</h4><p>The next thing I’d like to explore is a bit more complex. We want to be able to control media elements such as audio and video tags. And we want to do it with minimal imperative calls. To do so let’s create a directive. There are 3 things to control: current time, volume and play/pause state. They can also be changed through native controls so it’s going to be a two-way binding:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8072fc630b3cc6d3af4a1838ba2cabaa/href">https://medium.com/media/8072fc630b3cc6d3af4a1838ba2cabaa/href</a></iframe><p>See that @HostBinding on volume? It’s enough to get it working. But when it comes to currentTime — it changes on its own when the media is playing. So such binding would get into a messy loop updating back and forth. Instead, we need to upgrade it to setter and watch for not setting the same value again:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6b0268e3f1538018e8d23e865ceb4d19/href">https://medium.com/media/6b0268e3f1538018e8d23e865ceb4d19/href</a></iframe><p>We will also turn paused input to a getter/setter pair:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7ad3e078804e1fd7f2e741fde085a96e/href">https://medium.com/media/7ad3e078804e1fd7f2e741fde085a96e/href</a></iframe><p>Making a video player component with such a directive is a breeze:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c8befdccd52d10c64a422121c3d2f1a3/href">https://medium.com/media/c8befdccd52d10c64a422121c3d2f1a3/href</a></iframe><p>With ng-content users can provide sources like they would with native video tag. And the code for the player component is shamelessly short:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/81267c4a4cbdb26b5af4914ca0612225/href">https://medium.com/media/81267c4a4cbdb26b5af4914ca0612225/href</a></iframe><h4>Combo Box</h4><p>Now that we’ve got the vibe of declarative programming, let’s get into the meat and grit of things. Combo Box is a much more complex example, but don’t worry! We will not have a single function with more than one line! Well, maybe one.</p><blockquote>During this part of the article, I would also rely on declarative preventDefault. It uses the <a href="https://github.com/TinkoffCreditSystems/ng-event-plugins">ng-event-plugins</a> library that I wrote about in detail in <a href="https://indepth.dev/supercharge-event-management-in-your-angular-application">this article</a>.</blockquote><p>First, let’s establish the template. We will not get into creating custom controls for Angular as that is quite another topic. Instead, we will wrap it around a native input to allow users full control over it:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f33d71b0da12d308a42079b9b9c8b9d8/href">https://medium.com/media/f33d71b0da12d308a42079b9b9c8b9d8/href</a></iframe><p>The inner template will use a label to focus the input when the user clicks on the arrow. Yes, this technique is a bit dirty. Users will not be able to add an accessible label by simply wrapping our component. But, it suffices for the sake of simplicity this time.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8f162da41520157ad8f65b2e3977e02e/href">https://medium.com/media/8f162da41520157ad8f65b2e3977e02e/href</a></iframe><p>Preventing default on mousedown events is necessary so focus never leaves the input. This component has a single @Input — an array of strings as suggestions. And this time it would have a single internal state we would manage. You might think it’s open property to show the dropdown but it’s not. It’s a currently focused suggestion index in the dropdown. We would leverage NaN as an indicator for no selected item to stick within the number type. And open is a getter to see if there is a suggestion currently selected:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8ea3856a8e981cd79dae15f82130c983/href">https://medium.com/media/8ea3856a8e981cd79dae15f82130c983/href</a></iframe><p>We need to narrow suggestions to those that fit the user input. We can add NgControl as @ContentChild and access its value. We will then use it to filter the given array:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a6cb80d353b8bf5b63eeed7392b0958b/href">https://medium.com/media/a6cb80d353b8bf5b63eeed7392b0958b/href</a></iframe><p>Now we can add another getter for the currently selected index. We will clamp it to the length of the filtered suggestion:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7fa4fcb65307ade84909c43f2970d11f/href">https://medium.com/media/7fa4fcb65307ade84909c43f2970d11f/href</a></iframe><p>This new getter is safe to use as it will always be within the limit of available items. Now let’s add the event handlers we had in the template. We need to toggle dropdown on an arrow click, select an item from the list, and update the current index on hover:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ab36b1a6f1df29984cbe1bcdeab0c9c9/href">https://medium.com/media/ab36b1a6f1df29984cbe1bcdeab0c9c9/href</a></iframe><p>Next we need to handle keyboard navigation. It consists of using arrows to open dropdown and pick suggestions with the enter key. And also show the list as the user types:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9685b3329921960884ffbd2ccdcf74e1/href">https://medium.com/media/9685b3329921960884ffbd2ccdcf74e1/href</a></iframe><p>And that pretty much sums it up. This component is now fully operational. We wrote it in a declarative fashion only keeping track of a single state ourselves. We don’t have imperative commands like “open dropdown”. Instead we described the behavior of our component, relative to its states: control value, an input list of suggestions and a currently active one. That’s why it is called a <strong>declarative approach</strong>.</p><blockquote>In reality you would also want to make it accessible. You can add ARIA attributes such as aria-activedescendant. This way screen readers and other assistive technologies also keep track of the selected suggestion. Read more about the combobox pattern <a href="https://www.w3.org/TR/wai-aria-practices-1.1/#combobox">here</a> and <a href="https://www.w3.org/TR/wai-aria-1.1/#combobox">here</a>.</blockquote><p>Ever wondered why AK-47 is such a prominent weapon of the past decades? Its construction only has 8 moving parts. This makes it easy to manufacture, operate and maintain. Same applies to software architecture. The less states you have to manage, the more robust your code is. Simple design is a reliable design. And while declarative code might not seem simple at first glance — once you get used to it, you will appreciate its neatness.</p><h3>Performance</h3><p>A natural question that arises — if we keep recalculating, wouldn’t performance suffer? <strong>Of course, the </strong><strong>OnPush change detection strategy is a must</strong>. And frankly, I have never seen a case where the Default strategy was justified. Except for a field error component. As Angular forms <a href="https://github.com/angular/angular/issues/10887">still do not have a touched stream</a> at the time of this writing.</p><p>To assess performance we need to pay attention to what we do in our getters. A string concatenation like we had with viewBox has a speed of about 1 billion operations per second. 300 millions on a mediocre Android phone. So it’s not a concern. Same goes for simple math operations. Things get interesting when we get to arrays and objects. Iterating over 100 items array to find an item takes 15 million ops/sec on PC and is ten times slower on a smartphone. Immutable operations that create new instances of arrays are even more taxing. Filtering a 100 items array is about 3 million ops/sec on PC and humble 300k on an Android device. Working with immutable objects is similar due to underlying JavaScript mechanics. You can check performance yourself <a href="https://jsbench.me/mykdykggt4/1">here</a>. This means to make compliant components a thing we need to optimize them.</p><p>Let’s add a simple memoization technique so we do not do unnecessary recalculations. We can create a decorator for pure methods. It would remember passed arguments and the last result. If arguments stayed the same — it would return the result it remembers.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/18b6ebe9ecaf7ce7a831c68780bceb9b/href">https://medium.com/media/18b6ebe9ecaf7ce7a831c68780bceb9b/href</a></iframe><p>This way we can refactor our code to the following pattern on a getter and a pure method couples:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/17793e5232136bb8de0e7d154b4a3fa6/href">https://medium.com/media/17793e5232136bb8de0e7d154b4a3fa6/href</a></iframe><p>Let’s benchmark our approach. We will test against plain declarative approach and imperative updates with ngOnChanges:</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fstackblitz.com%2Fedit%2Fcompliant-components-performance-ivy%3Fembed%3D1&amp;display_name=StackBlitz&amp;url=https%3A%2F%2Fstackblitz.com%2Fedit%2Fcompliant-components-performance-ivy&amp;image=https%3A%2F%2Fc.staticblitz.com%2Fassets%2Ficon-03bbb9acbba813917725387f46e5df03a95d232be240e910ce5f946a973b3f86.png&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=stackblitz" width="745" height="400" frameborder="0" scrolling="no"><a href="https://medium.com/media/19143ee8054536019b53acfc13e97bba/href">https://medium.com/media/19143ee8054536019b53acfc13e97bba/href</a></iframe><p>In this StackBlitz we have a list of 1000 components and a button to trigger change detection in all of them. You can select imperative which is effectively a dry run. Inputs do not change and nothing is happening on a change detection cycle. Declarative components have several getters. A logical one to check if a value is bigger than threshold. A string concatenation. A math getter with @HostBinding to a class. An array iterator. An array generator and an object generator. Keep in mind that all this is multiplied by 1000 because this is what each component has. And the last column has @Pure decorator on array and object operations. Here are the results averaging over a 100 change detection runs for each column:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*EELuuz7KYOeUlAFu" /></figure><p>Here are results on a smartphone:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*tvofAzDuSRlyDIXb" /></figure><p>Difference on a PC is within the margin of error while on a medium-tier Android device it is about 10%. One can look at it and say it’s 10% slower. But there’s another way to look at it. Even on the weak machine with several thousands of getters at the same run it stays within a single 60FPS frame. And it is only 1.5 milliseconds slower than basically nothing — a dry run by Angular. Typically, the real performance hog is DOM manipulations. They are the heaviest operations of an average app. Remember class binding with a getter from above benchmark? Great thing about browsers is they do not alter DOM if value stays the same, be it class binding, style or attribute. You should organize your view tree properly, use memoization techniques and OnPush. This way declarative approach will not be a performance bottleneck.</p><h3>Summary</h3><p>Components written this way are robust and flexible. They might require a little bit of a paradigm shift to get used to. But once you start thinking like this, you will see what a pleasure it is to write and maintain such code. It does take attention from you to watch out for pitfalls. Otherwise performance would suffer. But from my experience it’s so worth it, it has changed my overall attitude towards the front-end. I’ve added a new chapter to our paid advanced Angular handbook over at <a href="https://angular.institute/">angular.institute</a>. It expands upon this concept with practical tips and tricks, head over there if you want to learn more. Described examples can be played with here:</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fstackblitz.com%2Fedit%2Fangular-declarative-components%3Fembed%3D1&amp;display_name=StackBlitz&amp;url=https%3A%2F%2Fstackblitz.com%2Fedit%2Fangular-declarative-components&amp;image=https%3A%2F%2Fc.staticblitz.com%2Fassets%2Ficon-03bbb9acbba813917725387f46e5df03a95d232be240e910ce5f946a973b3f86.png&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=stackblitz" width="745" height="400" frameborder="0" scrolling="no"><a href="https://medium.com/media/d6b932d2556ecc26d1fffea9ef9fd018/href">https://medium.com/media/d6b932d2556ecc26d1fffea9ef9fd018/href</a></iframe><p>I work as the lead developer of a proprietary UI kit at Tinkoff. The whole library is built on principles outlined here. Right now, it is in the process of being open-sourced. A foundation package already available on <a href="https://github.com/TinkoffCreditSystems/taiga-ui">GitHub</a> and npm. It has that pure decorator and a lot of other neat low level tools to help you build awesome Angular applications. We will sure cover those in upcoming articles so stay tuned for more!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=520c2bf95522" width="1" height="1" alt=""><hr><p><a href="https://medium.com/angularwave/compliant-components-declarative-approach-in-angular-520c2bf95522">Compliant components: Declarative approach in Angular</a> was originally published in <a href="https://medium.com/angularwave">AngularWave</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Writing Retrowave in Angular]]></title>
            <link>https://medium.com/angularwave/writing-retrowave-in-angular-4e1ff80798a8?source=rss----ee024cab95cb---4</link>
            <guid isPermaLink="false">https://medium.com/p/4e1ff80798a8</guid>
            <category><![CDATA[web-audio-api]]></category>
            <category><![CDATA[angular]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[audio]]></category>
            <dc:creator><![CDATA[Alexander Inkin]]></dc:creator>
            <pubDate>Thu, 26 Mar 2026 09:19:12 GMT</pubDate>
            <atom:updated>2026-03-26T09:19:13.796Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Z_teopu_vSscYwk4.png" /></figure><p>Web Audio API has been around for a while now and there are lots of great articles about it. So I will not go into details regarding the API. What I will tell you is Web Audio can be Angular’s best friend if you introduce it well. So let’s do this.</p><h3>Basics</h3><p>In <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API">Web Audio API</a> you create a graph of audio nodes that process the sound passing through them. They can change volume, introduce delay or distort the signal. Browsers have special <a href="https://developer.mozilla.org/en-US/docs/Web/API/AudioNode">AudioNodes</a> with various parameters to handle this. Initially, one would create them with factory functions of <a href="https://developer.mozilla.org/en-US/docs/Web/API/AudioContext">AudioContext</a>:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e3a8f0972344e6f00e83f04804d67ae0/href">https://medium.com/media/e3a8f0972344e6f00e83f04804d67ae0/href</a></iframe><p>But since then they became proper constructors which means you can extend them. This allows us to elegantly and declaratively use Web Audio API in Angular.</p><h3>Directives</h3><p>Angular directives are classes and they can extend existing native classes. Typical feedback loop to create echo effect with Web Audio looks like this:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7849395b7d52544d89137f80c6f37ba4/href">https://medium.com/media/7849395b7d52544d89137f80c6f37ba4/href</a></iframe><p>We can see that vanilla code is purely imperative. We create objects, set parameters, manually assemble the graph using connect method. In the example above we use HTML audio tag. When user presses play he would hear echo on his audio file. We will replicate this case using directives. <a href="https://developer.mozilla.org/en-US/docs/Web/API/AudioContext">AudioContext</a> will be delivered through Dependency Injection.</p><p>Both <a href="https://developer.mozilla.org/en-US/docs/Web/API/GainNode">GainNode</a> and <a href="https://developer.mozilla.org/en-US/docs/Web/API/DelayNode">DelayNode</a> have only one parameter each — gain and delay time. That is not just a number, it is an <a href="https://developer.mozilla.org/en-US/docs/Web/API/AudioParam">AudioParam</a>. We will see what that means a bit later.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e3995e5a23ba740ea3bd905358adda9a/href">https://medium.com/media/e3995e5a23ba740ea3bd905358adda9a/href</a></iframe><blockquote>Note that all <a href="https://developer.mozilla.org/en-US/docs/Web/API/AudioNode">AudioNodes</a> have 3 parameters: <a href="https://developer.mozilla.org/en-US/docs/Web/API/AudioNode/channelCount">channelCount</a>, <a href="https://developer.mozilla.org/en-US/docs/Web/API/AudioNode/channelCountMode">channelCountMode</a> and <a href="https://developer.mozilla.org/en-US/docs/Web/API/AudioNode/channelInterpretation">channelInterpretation</a>. Thanks to inputs property from @Directive decorator we can just list them and they would work properly without a single line of code.</blockquote><p>To declaratively link our nodes into graph we will add AUDIO_NODE token. All our directives will provide it.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ca15f75ce7506f9d737c53fa009fb29d/href">https://medium.com/media/ca15f75ce7506f9d737c53fa009fb29d/href</a></iframe><p>Directives take closest node from DI and connect with it. We’ve also added exportAs — it allows us to grab node with <a href="https://angular.io/guide/template-syntax#ref-vars">template reference variables</a>. Now we can build graph with template:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d1d05202d1bf83004f64e5acce7864fd/href">https://medium.com/media/d1d05202d1bf83004f64e5acce7864fd/href</a></iframe><p>We will end a branch and direct sound to the speakers with waAudioDestinationNode:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7c887896fc069f29038b7743303fa940/href">https://medium.com/media/7c887896fc069f29038b7743303fa940/href</a></iframe><p>To be able to create loops like in the echo example above Dependency Injection is not enough. We will make a special directive. It would allow us to pass node as input to connect to it:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/38beaf69c74a520415e2e4917f86feac/href">https://medium.com/media/38beaf69c74a520415e2e4917f86feac/href</a></iframe><p>Both those directives extend <a href="https://developer.mozilla.org/en-US/docs/Web/API/GainNode">GainNode</a> which creates an extra node in the graph. It allows us to disconnect it in ngOnDestroy easily. We do not need to remember everything that is connected to our directive. We can just disconnect this from everything at once.</p><h3>Source nodes</h3><p>The last directive we need to complete our example is a bit different. It’s a source node and it’s always at the top of our graph. We will put a directive on audio tag and it will turn it into <a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaElementAudioSourceNode">MediaElementAudioSourceNode</a> for us:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a8ae4d841d21368a2c24185749647776/href">https://medium.com/media/a8ae4d841d21368a2c24185749647776/href</a></iframe><p>Now let’s create the echo example with our directives:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d72d7878655273cc31e5d7abc528e34f/href">https://medium.com/media/d72d7878655273cc31e5d7abc528e34f/href</a></iframe><p>There are lots of different nodes in Web Audio API but all of them can be implemented using similar approach. Two other important source nodes are <a href="https://developer.mozilla.org/en-US/docs/Web/API/OscillatorNode">OscillatorNode</a> and <a href="https://developer.mozilla.org/en-US/docs/Web/API/AudioBufferSourceNode">AudioBufferSourceNode</a>. Often we do not want to add anything into DOM. And there is no need to provide audio file controls to the user. In that case <a href="https://developer.mozilla.org/en-US/docs/Web/API/AudioBufferSourceNode">AudioBufferSourceNode</a> is a better option than audio tag. Only inconvenience is — it works with <a href="https://developer.mozilla.org/en-US/docs/Web/API/AudioBuffer">AudioBuffer</a> unlike audio tag which takes a link to an audio asset. We can create a service to mitigate that:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d9dad8ea31510991e3ca44fdecf9381a/href">https://medium.com/media/d9dad8ea31510991e3ca44fdecf9381a/href</a></iframe><p>Now we can create a directive that works both with AudioBuffer and audio asset URL:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e5b2aca2f5d23b5ccdfd3099b4ef1c18/href">https://medium.com/media/e5b2aca2f5d23b5ccdfd3099b4ef1c18/href</a></iframe><blockquote>Worth mentioning that we also added autoplay attribute like one in audio tag so that sound can start playing immediately.</blockquote><h3>AudioParam</h3><p>Audio nodes have a special kind of properties — <a href="https://developer.mozilla.org/en-US/docs/Web/API/AudioParam">AudioParam</a>. For example gain in GainNode. That&#39;s why we used setter for it. Such property value can be automated. You can set it to change linearly, exponentially or even over an array of values in a given time. We need some sort of handler which would allow us to take care of this for all such inputs of our directives. Decorator is a good option for this case:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/dead2300a72170d5eb1c644ee1098a31/href">https://medium.com/media/dead2300a72170d5eb1c644ee1098a31/href</a></iframe><p>Decorator would pass processing to a dedicated function:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/66185659c9c78f0f3aa4a40980fc78c2/href">https://medium.com/media/66185659c9c78f0f3aa4a40980fc78c2/href</a></iframe><p>Strong types will not allow us to accidentally use it for a non-existent parameter. So what would AudioParamInput type look like? Besides number it would include an automation object:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/63c15aabc728c0f6d09ec59bfce307ee/href">https://medium.com/media/63c15aabc728c0f6d09ec59bfce307ee/href</a></iframe><p>processAudioParam function translates those objects into native API commands. It&#39;s pretty boring so I will just describe the principle. If current value is 0 and we want it to linearly change to 1 in a second we would pass {value: 1, duration: 1, mode: ‘linear’}. For complex automation we would also need to support an array of such objects.</p><p>We would typically pass an automation object with short duration instead of plane number. It prevents audible clicking artifacts when parameter changes abruptly. But it&#39;s not convenient to do it manually all the time. Let&#39;s create a pipe that would take target value, duration and optional mode as arguments:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/29f15cb7de03e61a2d8b241b023aba5e/href">https://medium.com/media/29f15cb7de03e61a2d8b241b023aba5e/href</a></iframe><p>Besides, <a href="https://developer.mozilla.org/en-US/docs/Web/API/AudioParam">AudioParam</a> can be automated by connecting an oscillator to it. Usually a frequency lower than 1 is used and it is called an LFO — Low Frequency Oscillator. It can create movement in sound. In example below it adds texture to otherwise static chords. It modulates frequency of a filter they pass through. To connect oscillator to a parameter we can use our waOutput directive. We can access node thanks to exportAs:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/161cd0e437f0a9d219cd1c41fa52b1bc/href">https://medium.com/media/161cd0e437f0a9d219cd1c41fa52b1bc/href</a></iframe><h3>Retrowave time</h3><p>Web Audio API can be used for different things. From real time processing of a voice for a podcast to math computations, Fourier transforms and more. Let’s compose a short music piece using our directives:</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fstackblitz.com%2Fedit%2Fangular-web-audio%3Fembed%3D1&amp;display_name=StackBlitz&amp;url=https%3A%2F%2Fstackblitz.com%2Fedit%2Fangular-web-audio&amp;image=https%3A%2F%2Fc.staticblitz.com%2Fassets%2Ficon-03bbb9acbba813917725387f46e5df03a95d232be240e910ce5f946a973b3f86.png&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=stackblitz" width="745" height="400" frameborder="0" scrolling="no"><a href="https://medium.com/media/c7dd9f0968cad938387b66642a891fa5/href">https://medium.com/media/c7dd9f0968cad938387b66642a891fa5/href</a></iframe><p>We will start with simple task — straight drum beat. To count beats we will create a stream and add it to DI:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1d46ae2bb928b792d481f90381b920fc/href">https://medium.com/media/1d46ae2bb928b792d481f90381b920fc/href</a></iframe><p>We have 4 beats per measure. Let’s map our stream:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b3a09f3d996d0ae2f6a4e600a8671faa/href">https://medium.com/media/b3a09f3d996d0ae2f6a4e600a8671faa/href</a></iframe><p>Now it gives us true in the beginning and false in the middle of each bar. We would use it to play audio samples:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/dca43e0d2d58bbcb5c0ed69840f49a63/href">https://medium.com/media/dca43e0d2d58bbcb5c0ed69840f49a63/href</a></iframe><p>Now let’s add melody. We will use numbers to indicate notes where 69 means middle A note. Function that translates this number to frequency can be easily found on Wikipedia. Here’s our tune:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6bd4a5605ce289a483d730d3683b86c1/href">https://medium.com/media/6bd4a5605ce289a483d730d3683b86c1/href</a></iframe><p>Our component will play right frequency for each note each beat:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/93a600f30f8145b6ca5f47c967ec7e55/href">https://medium.com/media/93a600f30f8145b6ca5f47c967ec7e55/href</a></iframe><p>And inside its template we will have a real synthesizer! But first we need another pipe. It would automate volume with ADSR-envelope. That means “Attack, Decay, Sustain, Release” and here’s how it looks:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/0*2InzZeURH0Sy31MM.png" /></figure><p>In our case we need for the sound to quickly start and then fade away. Pipe is rather simple:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ac295599d541460397638d9fb5e09326/href">https://medium.com/media/ac295599d541460397638d9fb5e09326/href</a></iframe><p>Now we will use it for our synth tune:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/05b96d8eb25eb0b3ff543106573f143f/href">https://medium.com/media/05b96d8eb25eb0b3ff543106573f143f/href</a></iframe><p>Let’s figure out what’s going on here. We have two oscillators. First one is just a sine wave passed through ADSR pipe. Second one is same echo loop we’ve seen, except this time it passes through <a href="https://developer.mozilla.org/en-US/docs/Web/API/ConvolverNode">ConvolverNode</a>. It creates room acoustics using impulse response. It&#39;s a big an interesting subject of its own, but it is outside this article&#39;s scope. All other tracks in our song are made similarly. Nodes are connected to each other, parameters are automated with LFOs or change smoothly via our pipes.</p><h3>Conclusion</h3><p>I only went over a small portion of this subject, simplifying corner cases. We’ve made a complete conversion of Web Audio API into declarative Angular open-source library <a href="https://github.com/ng-web-apis/audio">@ng-web-apis/audio</a>. It covers all the nodes and features.</p><blockquote>If you compare Web Audio to canvas and imperative commands to draw on it, then this library is a lot like SVG.</blockquote><p>If you want to know, what can be created with this approach, check out my little pet project — synthwave karaoke game Jamigo.app:</p><p><a href="https://jamigo.app/">Jamigo.app</a></p><p>This library is a part of a bigger project called <strong>Web APIs for Angular</strong> — an initiative with a goal of creating lightweight, high quality wrappers of native APIs for idiomatic use with Angular. So if you want to try, say, <a href="https://developer.mozilla.org/en-US/docs/Web/API/Payment_Request_API">Payment Request API</a> or play with your MIDI keyboard in browser — you are very welcome to browse <a href="https://ng-web-apis.github.io/">all our releases so far</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4e1ff80798a8" width="1" height="1" alt=""><hr><p><a href="https://medium.com/angularwave/writing-retrowave-in-angular-4e1ff80798a8">Writing Retrowave in Angular</a> was originally published in <a href="https://medium.com/angularwave">AngularWave</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Jam on your MIDI keyboard in Angular]]></title>
            <link>https://medium.com/angularwave/jam-on-your-midi-keyboard-in-angular-2a2c3f91ad75?source=rss----ee024cab95cb---4</link>
            <guid isPermaLink="false">https://medium.com/p/2a2c3f91ad75</guid>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[midi]]></category>
            <category><![CDATA[audio]]></category>
            <category><![CDATA[music]]></category>
            <category><![CDATA[angular]]></category>
            <dc:creator><![CDATA[Alexander Inkin]]></dc:creator>
            <pubDate>Thu, 26 Mar 2026 09:18:42 GMT</pubDate>
            <atom:updated>2026-03-26T09:18:44.222Z</atom:updated>
            <content:encoded><![CDATA[<p>Web MIDI API is an interesting beast. Even though it has been around for a while now, it is still only supported by Chrome. But that’s not gonna stop us from creating a playable synthesizer in Angular. It is time we bring Web Audio API to the next level!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Zksjl0wGL1cHr7Jw.png" /></figure><p>Previously, we spoke about <a href="https://medium.com/its-tinkoff/writing-retrowave-in-angular-4e1ff80798a8">declarative use of Web Audio API in Angular</a>. Programming music is fun and all but how about actually playing it? There is a standard called MIDI. It’s a messaging protocol for electronic instruments data exchange developed back in the 80s. And <strong>Chrome supports it natively</strong>. It means if you have a synthesizer or a MIDI keyboard — you can hook it up to a computer and read what’s played in the browser. You can even control other hardware from your machine. Let’s learn how to do it the Angular way.</p><h3>Web MIDI API</h3><p>There’s not a lot of documentation about it other than the specs. You request access to MIDI devices from navigator and you receive all MIDI inputs and outputs in a Promise. Those inputs and outputs (also called ports) act like event targets. Communication is performed through MIDIMessageEvents which carry Uint8Array data. Each message is 3 bytes at most. The first one is called a <em>status byte</em>. Every number has a particular role like key press or pitch bend. Second and third integers are called <em>data bytes</em>. In the case of key press second byte tells us what key is pressed and the third one is the velocity (how loud note is played). Full spec is available on the <a href="https://www.midi.org/specifications-old/item/table-2-expanded-messages-list-status-bytes">official MIDI website</a>. In Angular we handle events with Observables so first step to adopt Web MIDI API would be to convert it to RxJs.</p><h3>Dependency Injection</h3><p>To subscribe to events we first need to get MIDIAccess object to reach all inputs. As mentioned before, we request it from navigator and we get a Promise as a response. Thankfully, RxJs works with Promises too. We can create an Injection Token using NAVIGATOR from <a href="https://github.com/ng-web-apis/common">@ng-web-apis/common</a> package. This way we are not using global objects directly:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c22ad59c1db22fcefdf47acd51d07255/href">https://medium.com/media/c22ad59c1db22fcefdf47acd51d07255/href</a></iframe><p>Now that we have it, we can subscribe to all MIDI events. We can make Observable two ways:</p><ol><li>Create a service that extends Observable like we did in <a href="https://indepth.dev/what-makes-a-good-angular-library/">Intersection Observer API</a></li><li>Create a token with a factory that would map this Promise to Observable of events</li></ol><p>Since in this case there is not much set up required a token would suffice. With a bit of extra code to handle Promise rejection, subscribing to all events would look like this:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9125e3a736fde5b829d70c5d2d5f6187/href">https://medium.com/media/9125e3a736fde5b829d70c5d2d5f6187/href</a></iframe><p>We can extract particular MIDI port from MIDIAccess in case we want to, say, send an outgoing message. Let&#39;s add another token and a prepared provider to do this with ease:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/dd6999de2aebe1e1402061d6c32ead64/href">https://medium.com/media/dd6999de2aebe1e1402061d6c32ead64/href</a></iframe><blockquote>Did you know you don’t need to spread Provider[] when you use it in metadata? providers section of @Directive supports multidimensional array so you can use it as is!</blockquote><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/498dbc708a7fb373f73290cb9ed9e55d/href">https://medium.com/media/498dbc708a7fb373f73290cb9ed9e55d/href</a></iframe><blockquote>We can add similar helpers to get outputs by name and same goes for inputs</blockquote><h3>Operators</h3><p>To work with our stream we need to add some custom operators. After all, we shouldn’t always analyze raw event data to understand what we’re dealing with.</p><p>Operators can be roughly broken into two categories: <strong>monotype</strong> and <strong>mapping</strong>. With the first group, we can filter out the stream to events of interest. For example, only listen to played notes or volume sliders. Second group would alter elements for us, like dropping the rest of the event and only deliver data array.</p><p>Here’s how we can listen to messages only from a given channel (out of 16):</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e94470a6980da344a7e44c7b3de383b2/href">https://medium.com/media/e94470a6980da344a7e44c7b3de383b2/href</a></iframe><p>The status byte is organized in groups of 16: 128–143 are <strong>noteOff</strong> messages for each of the 16 channels, 144–159 are <strong>noteOn,</strong> etc. So if we divide status byte by 16 and get the reminder — we end up with that message’s channel.</p><p>If we only care about played notes, we can write the following operator:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ebe08cb27b932e38c2b166706ff549e4/href">https://medium.com/media/ebe08cb27b932e38c2b166706ff549e4/href</a></iframe><blockquote>Some MIDI controllers send explicit <strong>noteOff</strong> messages when you let go of a key. But some send <strong>noteOn</strong> with 0 velocity instead. This operator normalizes behavior to <strong>noteOn</strong> only messages. It shifts the status byte of all <strong>noteOff</strong> messages by 16 and sets the last byte to 0 effectively turning them into <strong>noteOn</strong> messages of 0 velocity.</blockquote><p>Now we can chain such operators to get a stream we need:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f268bc82db10d9315281a8bac911e254/href">https://medium.com/media/f268bc82db10d9315281a8bac911e254/href</a></iframe><p>Time put all this to work!</p><h3>Playable synth</h3><p>With a little help from our <a href="https://github.com/ng-web-apis/audio">Web Audio API library</a> discussed in my <a href="https://www.newline.co/@waterplea/writing-retrowave-in-angular--20856fee">previous article</a>, we can create a nice sounding synth with just a few directives. Then we need to feed it played notes from the stream we’ve assembled.</p><p>We will use the last code example as a starting point. To have polyphonic synthesizer we need to keep track of all played notes so we will add scan to our chain:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2c0a6f5beb2ba7d16b220563336fa619/href">https://medium.com/media/2c0a6f5beb2ba7d16b220563336fa619/href</a></iframe><p>To alter the volume of held keys and to not end sound abruptly when we let go we will create a proper ADSR-pipe (the previous article had a simplified version):</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/397b8eaf194a358b76cb1605f66bc429/href">https://medium.com/media/397b8eaf194a358b76cb1605f66bc429/href</a></iframe><blockquote>Now when we press key volume would linearly change from 0 to the level we played it in <strong>attack</strong> time. Then it would fall to <strong>sustain</strong> level in <strong>decay</strong> time. And when we let go it would go down to 0 in <strong>release</strong> time.</blockquote><p>With this pipe we can throw in a fine synth in the template:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b25ddde99e29c41326bf67c229d524b5/href">https://medium.com/media/b25ddde99e29c41326bf67c229d524b5/href</a></iframe><p>We iterate over accumulated notes with built-in keyvalue pipe tracking items by played key. Then we have 2 oscillators playing those frequencies. And at the end — a reverberation effect with ConvolverNode. Pretty basic setup and not a lot of code, but it gives us a playable instrument with rich sound. You can go ahead and give it a try in our interactive demo below.</p><blockquote>MIDI access will not be allowed in an iframe, so to play your instrument head to dedicated link: <a href="https://angular-midi.stackblitz.io/">https://angular-midi.stackblitz.io/</a></blockquote><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fstackblitz.com%2Fedit%2Fangular-midi%3Fembed%3D1&amp;display_name=StackBlitz&amp;url=https%3A%2F%2Fstackblitz.com%2Fedit%2Fangular-midi&amp;image=https%3A%2F%2Fc.staticblitz.com%2Fassets%2Ficon-03bbb9acbba813917725387f46e5df03a95d232be240e910ce5f946a973b3f86.png&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=stackblitz" width="745" height="400" frameborder="0" scrolling="no"><a href="https://medium.com/media/fc9398667829b3d4e3e9d19210b4a249/href">https://medium.com/media/fc9398667829b3d4e3e9d19210b4a249/href</a></iframe><blockquote>I included mouse interaction for those who do not have a MIDI controller connected</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*U4d2KxWbcPAsYGce.jpg" /></figure><h3>Conclusion</h3><p>In Angular, we are used to working with events using RxJs. And Web MIDI API is not much different from regular events. With some architectural decisions and tokens, we managed to use MIDI in an Angular app. The solution we created is available as <a href="https://github.com/ng-web-apis/midi">@ng-web-apis/midi</a> open-source package. It focuses mostly on receiving events. If you see something that is missing like a helper function or another operator — feel free to <a href="https://github.com/ng-web-apis/midi/issues">open an issue</a>.</p><p>This library is a part of a bigger project called <strong>Web APIs for Angular</strong> — an initiative to create lightweight, high-quality wrappers of native APIs for idiomatic use with Angular. So if you want to try <a href="https://indepth.dev/declarative-internet-shopping-with-payment-request-api-and-angular">Payment Request API</a> or need a declarative <a href="https://indepth.dev/what-makes-a-good-angular-library">Intersection Observer</a> — you are very welcome to browse <a href="https://ng-web-apis.github.io">all our releases so far</a>.</p><p>If you want to know, what can be created with this approach, check out my little pet project — synthwave karaoke game Jamigo.app:</p><p><a href="https://jamigo.app">Jamigo.app</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2a2c3f91ad75" width="1" height="1" alt=""><hr><p><a href="https://medium.com/angularwave/jam-on-your-midi-keyboard-in-angular-2a2c3f91ad75">Jam on your MIDI keyboard in Angular</a> was originally published in <a href="https://medium.com/angularwave">AngularWave</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Demystifying Taiga UI root component: portals pattern in Angular]]></title>
            <link>https://medium.com/angularwave/demystifying-taiga-ui-root-component-portals-pattern-in-angular-e9463bc2b584?source=rss----ee024cab95cb---4</link>
            <guid isPermaLink="false">https://medium.com/p/e9463bc2b584</guid>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[css]]></category>
            <category><![CDATA[html]]></category>
            <category><![CDATA[angular]]></category>
            <category><![CDATA[open-source]]></category>
            <dc:creator><![CDATA[Alexander Inkin]]></dc:creator>
            <pubDate>Thu, 26 Mar 2026 09:17:17 GMT</pubDate>
            <atom:updated>2026-03-26T09:17:18.997Z</atom:updated>
            <content:encoded><![CDATA[<p>Just before new year, <a href="https://twitter.com/marsibarsi">Roman</a>, my colleague, <a href="https://indepth.dev/posts/1413/taiga-ui">announced</a> our new Angular UI kit library <a href="https://github.com/TinkoffCreditSystems/taiga-ui">Taiga UI</a>. If you go through <a href="https://taiga-ui.dev/getting-started">Getting started</a> steps, you will see that you need to wrap your app with the tui-root component. Let&#39;s see what it does and explore what portals are and how and why we use them.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rQU6xgnLKZGF5hzMjEWJPg.png" /></figure><h3>What is a portal?</h3><p>Imagine you have a select component. It has a drop-down block with suggestions. If we keep it at the same position in DOM as the hosting component, we will run into all sorts of trouble. Items pop through and containers can chop off content:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/774/0*AH_XAnzaV_3CMfvE" /></figure><p>Verticality issues are often solved with z-index, effectively starting World War Z in your app. It&#39;s not uncommon to see values like 100, 10000, 10001. But even if you manage to get it right — overflow: hidden would still get you there. So what can we do? Instead of having drop-down near its host we can show it in a dedicated container on top of everything. Then your application content can live in its own <a href="https://developer.mozilla.org/ru/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context">isolated context</a> eliminating z-index problems. This container is exactly what a portal is. And it is what the Taiga UI root component sets up for you, among other things. Let&#39;s look at its template:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2ad78b8e0d50f580d095be73138bf898/href">https://medium.com/media/2ad78b8e0d50f580d095be73138bf898/href</a></iframe><h3>Generic and dedicated portals</h3><p>Both tui-dialog-host and tui-portal-host are portals in their nature. But they work differently. Let&#39;s explore the second one first. Taiga UI uses it mostly to display drop-downs. But it&#39;s a generic container. It is controlled by a very simple service:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3a540fe67d28489b353c859c530d01fb/href">https://medium.com/media/3a540fe67d28489b353c859c530d01fb/href</a></iframe><p>And the component itself is rather straightforward. All it does is show templates and dynamic components on top of everything. No other logic is included (except a little position: fixed helper for iOS). It means that positioning, closing and the rest is handled by portal items on their own. It&#39;s a good idea to have a generic portal for special cases. Like a fixed «Scroll to top» button displayed above content or anything else you, as a library user might need.</p><h3>Drop-downs</h3><p>If we were to architect a drop-down — we would need to come up with a positioning solution. We have several options here:</p><ol><li>Position drop-down once and prevent scrolling while it’s open. This is what material does by default.</li><li>Position once and close if scrolling occurred. That’s how native drop-downs behave.</li><li>Follow host position when it changes</li></ol><p>We went with the third option. It’s not that trivial as it turned out. You cannot really get two positions in sync, even with requestAnimationFrame. Because once you query the host position — it triggers a layout recalculation. So by the time the next frame comes and drop-down is positioned — the host already changes location a little bit. This causes visible jumps, even on fast machines. We got around that by using absolute positioning, rather than fixed. Because the portal container wraps the entire page, position values stay the same during scroll. If the host is in a fixed container, though, it would still jump. But we can detect that when we open the drop-down and use fixed positioning for it as well.</p><p>And then there’s this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*HSQhuGXS12kVbNtR" /></figure><p>If the host leaves the visible area — we need to close the drop-down. That’s a job for Obscured service. It detects when the host is fully obscured by anything and closes drop-down in that case.</p><h3>Dialogs</h3><p>For dedicated portals study we can take a look at dialogs. Toast notifications and hints are very similar but there are some interesting topics to discuss with modals.</p><p>This is how dialog host looks like:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/aeef88159774cd629ad6501a03161717/href">https://medium.com/media/aeef88159774cd629ad6501a03161717/href</a></iframe><p>Instead of being a generic host it has an ngFor loop over particular items. This allows us to bundle some logic in, like focus trap and page scroll blocking. There is also a clever use of dependency injection here, allowing dialogs to be design and data model agnostic. Host collects observables with dialogs through a dedicated multi token, merges these streams and shows the result. That way you can have multiple designs for dialogs in the same app. Taiga UI has two built-in designs — base and mobile. But you can easily add your own. Let&#39;s see how.</p><p>Dialog service returns Observable. When you subscribe to it, modal popup is shown, when you terminate subscription it is closed. Dialog can also send back data through that stream. First, we design our dialog component. All that&#39;s important here, really, is that you can inject POLYMORPHEUS_CONTEXT in constructor. It would contain an object with content and observer for a particular dialog instance. You can close dialog from within by calling complete on observer and you can send back data using next method. Plus all the options you will provide to the service that we will create by extending an abstract class:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/78b2ac1de150fcb37b21c23c109ed269/href">https://medium.com/media/78b2ac1de150fcb37b21c23c109ed269/href</a></iframe><p>In it we provide default config and a component to use and we’re all set.</p><blockquote>Dialogs, like everything in Taiga UI use <a href="https://github.com/TinkoffCreditSystems/ng-polymorpheus">ng-polymorpheus</a> for customizable content. You can read more about making interface free, flexible components with it in <a href="https://indepth.dev/posts/1314/agnostic-components-in-angular">this article</a>.</blockquote><p>Focus trapping is handled by the tuiFocusTrap directive. Since we have drop-downs later in DOM and we can have multiple dialogs open at the same time — we don&#39;t care if focus goes farther in the DOM. If it went somewhere prior to dialog though — we return focus back with a few helpers from @taiga-ui/cdk:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/4dc5300e77c91f002662ce5b2ec20692/href">https://medium.com/media/4dc5300e77c91f002662ce5b2ec20692/href</a></iframe><p>Blocking page scroll is dealt with by combination of a directive and some logic inside the root component. Root just hides scrollbars when a dialog is open, while Overscroll directive takes care of touch and wheel scroll. There’s a <a href="https://developer.mozilla.org/ru/docs/Web/CSS/overscroll-behavior">CSS rule for overscroll behavior</a>. However it’s not sufficient. It doesn’t help when dialog is small enough that it doesn’t have its own scroll. That’s why we have a directive with some additional logic stopping scroll if it will happen in some patent node.</p><h3>Bonus: what else does tui-root do?</h3><p>As far as portals go — this covers most of it. Let’s also take a quick look at what else is bundled with the root component. You saw in the template that it has tui-scroll-controls. These are custom scrollbars that control global scroll. You may have also noticed named content projections like &lt;ng-content select=&quot;tuiOverDialogs&quot;&gt;&lt;/ng-content&gt;. With those you can slide some content in-between layers of Taiga UI if you need. For example, if you run another library for toasts or dialogs and want them properly placed vertically.</p><p>It also registers several <a href="https://github.com/TinkoffCreditSystems/ng-event-plugins">event manager plugins</a> in the DI. You can read about them in a <a href="https://indepth.dev/posts/1153/supercharge-event-management-in-your-angular-application">dedicated article</a>. It is important that TuiRootModule goes after BrowserModule so they are registered at the right order. But don&#39;t worry — if you get it wrong you will see an assertion message in the console.</p><p>That wraps it up for portals and the root component. Taiga UI is open-source and you can check it out on <a href="https://github.com/TinkoffCreditSystems/taiga-ui">GitHub</a> and npm. You can also browse the <a href="https://taiga-ui.dev/">demo portal</a> with documentation and play with it using this <a href="https://stackblitz.com/edit/taiga">StackBlitz starter</a>. Stay tuned for more articles on interesting features we have!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e9463bc2b584" width="1" height="1" alt=""><hr><p><a href="https://medium.com/angularwave/demystifying-taiga-ui-root-component-portals-pattern-in-angular-e9463bc2b584">Demystifying Taiga UI root component: portals pattern in Angular</a> was originally published in <a href="https://medium.com/angularwave">AngularWave</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Global objects in Angular]]></title>
            <link>https://medium.com/angularwave/global-objects-in-angular-6c3016a51cf4?source=rss----ee024cab95cb---4</link>
            <guid isPermaLink="false">https://medium.com/p/6c3016a51cf4</guid>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[angular]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[server-side-rendering]]></category>
            <category><![CDATA[dependency-injection]]></category>
            <dc:creator><![CDATA[Alexander Inkin]]></dc:creator>
            <pubDate>Thu, 26 Mar 2026 09:14:59 GMT</pubDate>
            <atom:updated>2026-03-26T09:15:00.961Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5oXtFSWnmaBEWYmjIfUU6Q.png" /></figure><p>In JavaScript we often use entities, such as window, navigator, requestAnimationFrame or location. Some of these objects have been there forever, some are parts of the ever-growing Web APIs feature set. You might have seen Location class or DOCUMENT token used in Angular. Let&#39;s discuss why they exist and what we can learn from them to make our apps cleaner and more flexible.</p><h3>DOCUMENT</h3><p>DOCUMENT is a built-in Angular token. Here&#39;s how you can use it. Instead of this:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/0892640db534ae126e3e5a32b027e1a5/href">https://medium.com/media/0892640db534ae126e3e5a32b027e1a5/href</a></iframe><p>You can write this:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ff46840784ebdea86b47043b4e97ac65/href">https://medium.com/media/ff46840784ebdea86b47043b4e97ac65/href</a></iframe><p>In the first code snippet we accessed global variable directly. In the second example we injected it as a dependency to constructor. I’m not going to throw fancy acronyms at you or say that the first approach breaks some programming principles or patterns. To be honest, I’m not that fluent in those. Instead, I will show you exactly why token approach is better. And we will start by taking a look at where this token comes from. There’s nothing special about its declaration in the @angular/common package:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/86030318ebc5ae99f9caae154a3315b2/href">https://medium.com/media/86030318ebc5ae99f9caae154a3315b2/href</a></iframe><p>Inside @angular/platform-browser, however, we see it getting actual value <em>(code snippet simplified)</em>:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/527234add246a1960bed35a26d95bae5/href">https://medium.com/media/527234add246a1960bed35a26d95bae5/href</a></iframe><p>When you add BrowserModule to your app.browser.module.ts you register a <a href="https://github.com/angular/angular/blob/master/packages/platform-browser/src/browser.ts">bunch of different implementations</a> for built-in tokens, such as RendererFactory2, Sanitizer, EventManager and our DOCUMENT. Why is it like that? Because Angular is multiplatform framework. Or rather platform agnostic. It works with abstractions and utilizes dependency injection mechanism heavily to be able to work in browser, on server or mobile platforms. To figure it out, let&#39;s take a sneak peak at ServerModule — another bundled platform <em>(code snippet simplified)</em>:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c1a91684033c72c211873fba76f29100/href">https://medium.com/media/c1a91684033c72c211873fba76f29100/href</a></iframe><p>We see that it uses <a href="https://www.npmjs.com/package/domino">domino</a> to create an imitation of a document, based on some config it once again got from the DI. This is what you get when you, for example, use Angular Universal for server side rendering. We already see the first and most important benefit. Using DOCUMENT token would work in SSR environment whereas accessing global document will not.</p><h3>Other global entities</h3><p>So Angular team took care of document for us which is nice. But what if we want to check our browser with <a href="https://developer.mozilla.org/en-US/docs/Web/API/NavigatorID/userAgent">userAgent</a> string? To do so we would typically access navigator.userAgent. What this actually means is that we first reach for global object, window in browser environment, and then get its navigator property. So let&#39;s start by implementing WINDOW token. It is pretty easy to do with a factory that you can add to token declaration:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2a2c0424d12ad7b5e095d3ded333e670/href">https://medium.com/media/2a2c0424d12ad7b5e095d3ded333e670/href</a></iframe><p>This is enough to start using WINDOW token similar to DOCUMENT. Now we can use similar approach to create NAVIGATOR:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/258b383a9d81372cb002d4e429a4973f/href">https://medium.com/media/258b383a9d81372cb002d4e429a4973f/href</a></iframe><blockquote>We will take it one step further and create a token for USER_AGENT the same way. Why? We&#39;ll see later!</blockquote><p>Sometimes making a simple token is not enough. Angular’s Location class is basically a wrapper over native location that improves our dev experience. Since we are used to RxJS streams let&#39;s replace requestAnimationFrame with an Observable implementation:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/923296bfd053b65b7cd20e58c5fb8bc1/href">https://medium.com/media/923296bfd053b65b7cd20e58c5fb8bc1/href</a></iframe><p>We skipped PERFORMANCE token creation because it follows the same pattern. Now we have a single shared requestAnimationFrame based stream of timestamps which we can use across our app. After we replaced everything with tokens our components no longer rely on magically available items and get everything they depend on from dependency injection, which is neat.</p><h3>Server Side Rendering</h3><p>Now say in our app we want to do window.matchMedia(&#39;(prefers-color-scheme: dark)&#39;). While there is certainly <em>something</em> in our WINDOW token on the server side, it definitely does not provide full API that Window object has. If we try the above method in SSR we would probably get undefined is not a function error. One thing we can do is wrap such cases with isPlatformBrowser checks but that&#39;s boring. Advantage of DI is we can override stuff. So instead of handling these situation as special cases we can provide WINDOW in our app.server.module.ts with a type-safe mock object that will protect us from non-existent properties.</p><p>This showcases another important advantage this approach has: token values can be replaced. This makes it very easy to test components that rely on browser API, especially if you test in <a href="https://jestjs.io/">Jest</a> where native API is not available otherwise. But mocks are dull. Sometimes we can actually provide something meaningful. In SSR environment we have request object which contains user agent data. That’s why we separated it into its own token — because we can actually obtain it separately sometimes. Here’s how we can turn request into provider:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/08879707edded29b51e84958c71177a3/href">https://medium.com/media/08879707edded29b51e84958c71177a3/href</a></iframe><p>And use it in our server.ts when we are setting up Angular Universal:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/186d63e0b7343aa58f42f4ba75b500dd/href">https://medium.com/media/186d63e0b7343aa58f42f4ba75b500dd/href</a></iframe><p>Node.js also has its own implementation of Performance which we can use on the server side:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d72cd0d5f6f909dce7e0bd4de776e17a/href">https://medium.com/media/d72cd0d5f6f909dce7e0bd4de776e17a/href</a></iframe><p>In case of requestAnimationFrame we will not need Performance though. We probably do not want our Observable chain to run on the server so we can provide EMPTY:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2eb57b57bca595ee8c02ee8b98eebdc7/href">https://medium.com/media/2eb57b57bca595ee8c02ee8b98eebdc7/href</a></iframe><p>We can follow this pattern to «tokenize» all global objects that we use across our app and provide replacements for various platforms we might run it on.</p><h3>Wrapping up</h3><p>With this approach your code becomes well abstracted. Even if you do not run it on the server now, you might want to do it later and you will be ready. Besides, it is much easier to test code when things it relies upon can be substituted. We have created a tiny library of common tokens that we use:</p><p><a href="https://github.com/ng-web-apis/common">ng-web-apis/common</a></p><p>If you need something that is currently missing, feel free to create an issue! There is also a sidekick package with SSR versions of those tokens:</p><p><a href="https://github.com/ng-web-apis/universal">ng-web-apis/universal</a></p><blockquote>You can explore this <a href="https://github.com/IKatsuba/ngx-ssr/tree/master/apps/rickandmorty">Rick and Morty themed project</a> by <a href="https://twitter.com/katsuba_igor">Igor Katsuba</a> using SSR to see all this in action. If you are interested in Angular Universal in particular, <a href="https://indepth.dev/posts/1437/angular-universal-real-app-problems">read his article</a> about issues he faced and how to overcome them.</blockquote><p>Thanks to this pattern our Angular components library <a href="https://taiga-ui.dev">Taiga UI</a> was able to run on both Angular Universal and Ionic setups with no extra fuss. I hope it will be beneficial to you as well.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6c3016a51cf4" width="1" height="1" alt=""><hr><p><a href="https://medium.com/angularwave/global-objects-in-angular-6c3016a51cf4">Global objects in Angular</a> was originally published in <a href="https://medium.com/angularwave">AngularWave</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>