<?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[Stories by Nikita Barsukov on Medium]]></title>
        <description><![CDATA[Stories by Nikita Barsukov on Medium]]></description>
        <link>https://medium.com/@nsbarsukov?source=rss-8ad93c244ca3------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*OQ0dlxdCqLSORd70MEAhgw.jpeg</url>
            <title>Stories by Nikita Barsukov on Medium</title>
            <link>https://medium.com/@nsbarsukov?source=rss-8ad93c244ca3------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Thu, 25 Jun 2026 12:18:52 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@nsbarsukov/feed" 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-8ad93c244ca3------2</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:49 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[Watching Angular Evolve:
Taiga UI Kit Maintainer’s Perspective]]></title>
            <link>https://medium.com/angularwave/watching-angular-evolve-taiga-ui-kit-maintainers-perspective-97d2dd56b607?source=rss-8ad93c244ca3------2</link>
            <guid isPermaLink="false">https://medium.com/p/97d2dd56b607</guid>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[web]]></category>
            <category><![CDATA[frontend-development]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[angular]]></category>
            <dc:creator><![CDATA[Nikita Barsukov]]></dc:creator>
            <pubDate>Tue, 03 Jun 2025 07:18:57 GMT</pubDate>
            <atom:updated>2025-06-03T07:18:57.859Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4ykbsczulT2VbKsMOb-4VA.jpeg" /></figure><p>Last week, Angular hit a major milestone: version 20! So it felt like the perfect moment to rewind and see how Angular has grown and changed over the past five years — that’s ten major versions!</p><p>But instead of a typical “what’s new” rundown, I want to take you through this journey from a different angle — through the eyes of someone who’s been maintaining a massive UI library for Angular — <a href="https://taiga-ui.dev">Taiga UI</a>.</p><p>We’re going to skip over the buzzworthy features and focus on the little things that made a huge difference for us. So, buckle up — let me show you what it’s like to be in the shoes of a UI Kit developer!</p><h3>A Quick Intro (and a Few Disclaimers)</h3><p>I started working on <strong>Taiga UI</strong> in June 2021. At that point, we were still on Angular 9, using the legacy View Engine, and the library had just hit its second major release.</p><p>Since then, a lot has changed. Taiga UI has gone through big refactorings — often triggered by Angular’s own upgrades. We embraced every meaningful update that opened new doors for UI component architecture.</p><p>This post skips over some big application-focused features (typed forms, SSR, hydration, etc.). Those are awesome for app developers, but in the context of a component library? Not as impactful. And conversely, things that might seem trivial to app devs made a world of difference for us.</p><p>So let me walk you through the standout changes from each version — but from the point of view of someone who’s been deep in the trenches building and maintaining a big Angular UI library.</p><h3>Angular 10</h3><h4><strong>Strict mode</strong></h4><p>Back then, I wasn’t sure if we really needed <a href="https://v10.angular.io/guide/strict-mode">strict mode</a>. Especially noImplicitAny rule, which instantly scolds you for writing JavaScript-like code.</p><p>But looking back now? It was absolutely essential. It helped us catch those classic hello, undefined! bugs early — and trust me, when you’re shipping a component library used in dozens of million-user apps, that’s not just a nice-to-have — it’s non-negotiable.</p><p>Moreover, strict mode didn’t just apply to only .ts files. It also made template type checks more robust.</p><blockquote><strong>Killer feature of Angular 10?</strong> No doubt — strict mode.</blockquote><h3>Angular 11</h3><h4><strong>Hot Module Replacement (HMR)</strong></h4><p>Who doesn’t want to rebuild an Angular application faster during development? This was the first attempt at HMR in Angular.</p><p>Angular first introduced <a href="https://webpack.js.org/guides/hot-module-replacement">HMR</a> support back in version 11. Yep, not in 2024 with Angular 19 like some might think — but four years earlier! The excitement in the community was real. Just check out the comments on the original <a href="https://github.com/angular/angular/issues/39367">feature request</a>: there were over 10 highly upvoted messages pleading for it — stuff like “we need this feature ASAP!” and even dramatic threats like “I am leaving Angular ASAP!” 😄</p><p>Unfortunately, Angular’s first go at this didn’t quite land. Users ran into issues — like lost state and full page reloads. And so, the Angular team had to shelve the effort for better times — only in documentation of Angular 17<a href="https://v17.angular.io/guide/roadmap#improved-hot-module-replacement-support-hmr"> plans to bring HMR back</a> reappeared on the roadmap.</p><p>Not every major Angular release left a warm and fuzzy memory. This one, for instance, definitely left a bit of a bad taste.</p><h4>Webpack 5</h4><p>But there was also something definitely good in this release — support for Webpack 5.</p><p>For most devs, that meant long-awaited support for <a href="https://webpack.js.org/concepts/module-federation">Module Federation</a> in Angular microfrontends. For us at Taiga UI, it also meant we could finally load file contents as plain strings in our documentation examples without needing extra tools like <a href="https://v4.webpack.js.org/loaders/raw-loader">raw-loader</a> or other workarounds — thanks to <a href="https://webpack.js.org/guides/asset-modules">asset modules</a>.</p><h3>Angular 12</h3><h4>Ivy Everywhere</h4><p>The new <a href="https://v12.angular.io/guide/ivy">Ivy engine</a>. Its first “real-world deployment” actually began back in version 9, but it was optional then and its future was still quite uncertain. With Angular 12 and the tagline <a href="https://blog.angular.dev/angular-v12-is-now-available-32ed51fbfd49#ea47">“Moving Closer to Ivy Everywhere”</a>, Ivy officially replaced the old View Engine (the legacy engine became deprecated).</p><p>For <a href="https://github.com/taiga-family/taiga-ui">Taiga UI</a>, this marked a huge shift. Starting with our third major release, we published our libraries fully Ivy-compatible. And finally, our users could stop taking long coffee breaks waiting for <a href="https://v12.angular.io/guide/ivy#ivy-and-libraries">ngcc</a> to finish compiling.</p><p>It might seem like a small thing, but we genuinely cared about our users’ time and developer experience!</p><p>Plus, we could finally remove over 100 magical <a href="https://github.com/angular/angular/issues/26874">@dynamic</a> comments from the codebase and say goodbye to the relic that was <a href="https://v12.angular.io/guide/deprecations#entrycomponents-and-analyze_for_entry_components-no-longer-required">entryComponents</a>.</p><blockquote>Ivy wasn’t brand new in 12, but this was the moment it became available for libraries.</blockquote><h3>Angular 13</h3><h4>Farewell, Internet Explorer</h4><p>This wasn’t just a tech decision — it was a cultural shift. Once Angular dropped IE, so did we. And suddenly our codebase looked so much cleaner without all those if IE workarounds.</p><p>IE support was dead, and Taiga UI looked better than ever.</p><h3>Angular 14</h3><h4>Standalone API</h4><p>Despite all the positives Standalone API brought to the table, there was a bit of a catch. At Taiga UI, we love to decompose everything we can — really breaking everything down. What looks like one component is often a carefully composed collection of multiple components and directives. Using the old module-based API, that complexity was neatly tucked away inside a single import of a module. But once we moved to standalone components, this suddenly became a real concern.</p><p>For example, a seemingly simple component like a <a href="https://taiga-ui.dev/components/slider">slider</a> is actually a composition of several parts — in this case, one component and four directives.</p><p>Back in the days of the module-based API, that complexity was hidden from users. You just imported TuiSliderModule and Angular took only required entities. Unused parts? Tree-shaking would take care of them.</p><p>But with standalone components, this changed. Now, users needed to import every individual piece manually — which wasn’t great for DX. Tools like WebStorm started suggesting dozens of imports, and that quickly got annoying.</p><p>Our solution? We used TypeScript’s <a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions">as const assertion</a> to group all related parts into a single array. This way, the IDE would suggest importing just one named constant:</p><pre>export const TuiSlider = [<br>   TuiSliderComponent,<br>   TuiSliderThumbLabel,<br>   TuiSliderKeyStepsBase,<br>   TuiSliderKeySteps,<br>   TuiSliderReadonly,<br>] as const;</pre><p>Users only had to include TuiSlider in the imports array of their standalone component. To avoid confusion, we also adopted a naming convention — users should import entities without the Component and Directive suffixes.</p><p>This simple pattern solved our small problem and opened the door for a smooth migration to standalone components in Taiga UI.</p><h4>Protected methods in templates</h4><p>This might seem like a tiny change for application devs, but for library maintainers? It was a delicious win.</p><p>Taiga UI components have both public and internal APIs. Naturally, we really don’t want users poking around in the internals — we need freedom to refactor that stuff without breaking things. But until now, we had no choice. If we wanted to use a method in a template, it had to be public. That meant users could — and did — use those methods in unit tests and sometimes even in production code.</p><p>It tied our hands during refactors. We had to preserve backward compatibility for methods that were never meant to be used externally.</p><p>Thankfully, with this Angular upgrade, we could finally mark those methods as protected and still use them in templates. That lets us safely hide a lot of internal logic and clean up our public API surface. One small compiler change — huge DX payoff.</p><h4>Optional injectors in embedded views</h4><p>Big improvement for dropdowns and other “portal” components. Now we can pass injectors directly during initialization of TemplateRef:</p><pre>viewContainer.createEmbeddedView(templateRef, context, {<br>   injector: injector, ​​// &lt;-- new feature in Angular 14<br>})</pre><p>Now<a href="https://taiga-ui.dev/directives/active-zone"> ActiveZone</a> (a DI entity for tracking user interaction with the page) is finally inherited automatically by nested data lists and dropdowns. Previously, users had to do it manually with extra directives in the template — which often led to mistakes.</p><p><a href="https://medium.com/angularwave/tracking-user-interaction-area-6ad3ab7f0c8b">Tracking user interaction area</a></p><h4>inject()</h4><p>The new <a href="https://angular.dev/guide/di/dependency-injection#injecting-consuming-a-dependency">inject</a> utility quickly won our hearts. It offered a clean alternative to constructor-based dependency injection, opening up a fantastic way to use DI inside abstract classes — no more prop-drilling through child constructors! On top of that, it drastically improved DX, letting us build all sorts of helpers with DI baked right in.</p><h3>Angular 15</h3><h4>Directive Composition API (aka Host Directives)</h4><p>A true gem for UI Kit development!</p><p>The funny thing is, the Angular team themselves might not yet fully realize what a powerful decomposition tool they’ve built. There are only <a href="https://github.com/search?q=repo%3Aangular%2Fcomponents+hostDirectives%3A+%5B+NOT+path%3A*.spec.ts&amp;type=code">three uses</a> of this feature in the entire Angular Material repo. Compare that to Taiga UI — <a href="https://github.com/search?q=repo%3Ataiga-family%2Ftaiga-ui+hostDirectives%3A+%5B+NOT+path%3A%22projects%2Fdemo%22+NOT+path%3A*.spec.ts+&amp;type=code">over 90</a> and counting.</p><p>The use cases were so broad for us that we even<a href="https://www.angularspace.com/host-directives-decomposition-unleashed"> dedicated a full article</a> to it. Highly recommend checking it out — you might find some patterns useful for your own projects!</p><p><strong>But…</strong></p><ul><li><strong>No built-in control for managing inputs from the host</strong>. We had to create our own utility <a href="https://github.com/taiga-family/taiga-ui/blob/main/projects/cdk/utils/miscellaneous/directive-binding.ts">tuiDirectiveBinding</a> to handle it manually.</li><li><strong>Inputs and outputs aren’t exposed by default</strong>. In our experience, most host directives should expose everything unless explicitly hidden. Angular did the opposite, forcing us to forward every prop manually.</li><li><strong>Double matching errors</strong>. You’ll hit a runtime error if the same directive appears more than once on an element. There’s already a <a href="https://github.com/angular/angular/issues/57846">bug report</a> for that — highly recommend upvoting it!</li></ul><p>So while the feature isn’t flawless, it’s still a game-changer.</p><h3>Angular 16</h3><h4>Async HostBinding using signals</h4><p>Thanks to the new mechanism for converting observables into signals (via<a href="https://angular.dev/api/core/rxjs-interop/toSignal"> toSignal()</a> from @angular/core/rxjs-interop), we can now do async host bindings with ease. We used to rely on our own <a href="https://medium.com/its-tinkoff/making-hostbinding-work-with-observables-23396f3b8aea">solutions</a> from <a href="https://github.com/taiga-family/ng-event-plugins">ng-event-plugins</a> — a bit hacky and not very intuitive. But now? It’s clean and transparent:</p><pre>import {toSignal} from &#39;@angular/core/rxjs-interop&#39;;<br><br>@Directive({<br>   host: {<br>       &#39;[style.--tui-css-variable]&#39;: &#39;hostBindingValue()&#39;,<br>   },<br>})<br>export class SomeDirective {<br>   anyObservable$: Observable&lt;any&gt; = …;<br>   hostBindingValue = toSignal(this.anyObservable$);<br>}</pre><h4>Simplified controller pattern</h4><p>All those complex Change Detection hacks we used in <a href="https://angular.love/how-we-make-our-base-components-more-flexible-controllers-concept-in-angular">Taiga UI’s controller pattern</a>? Gone. Now signals trigger change detection automatically when a controller’s input changes — no more manual nudges with ChangeDetectorRef. Less manual lifecycle juggling!</p><h4>@Input({ required: true })</h4><p>Sounds useful in theory for UI Kit components, but in practice, <a href="https://v16.angular.io/guide/inputs-outputs#configuring-the-child-component-input-as-required-field">required inputs</a> didn’t really catch on at Taiga UI. At the time of writing, we’ve only used it three times across the entire codebase. Why? Because we’ve always had a habit of providing meaningful default values for nearly every input property — so there’s rarely a need to mark anything as strictly required.</p><h4>Input transforms</h4><p>Now we can write boolean directives like this (using <a href="https://angular.dev/guide/components/inputs#built-in-transformations">built-in transformations</a>):</p><pre>&lt;input tuiAutoFocus /&gt;</pre><p>And internally:</p><pre>import {Directive, Input} from &#39;@angular/core&#39;;<br>import {<br>   type BooleanInput,<br>   coerceBooleanProperty<br>} from &#39;@angular/cdk/coercion&#39;;<br><br><br>@Directive({selector: &#39;[tuiAutoFocus]&#39;})<br>export class TuiAutoFocus {<br>   @Input({<br>       alias: &#39;tuiAutoFocus&#39;,<br>       transform: coerceBooleanProperty,<br>   })<br>   public autoFocus: BooleanInput;<br>  <br>   // [...]<br>}</pre><p>It might not be groundbreaking, but it’s a super handy way to mimic the behavior of native boolean attributes.</p><h4>DestroyRef and takeUntilDestroyed</h4><p>Finally, we could say goodbye to our custom <a href="https://github.com/taiga-family/taiga-ui/blob/v3.x/projects/cdk/services/destroy.service.ts">TuiDestroyService</a> 👋</p><h3>Angular 17</h3><h4>Signal-based inputs</h4><p>This was when signals <em>really</em> became powerful.</p><p>Previously, we had to clutter component code with extra setters just to use an input property as a signal. Now, it’s all baked in.</p><h4>Lifecycle hooks? Not so much anymore</h4><p>In Taiga UI components, we’ve often relied on @ViewChild(-ren) and @ContentChild(-ren). As you know, access to those values only becomes available inside AfterViewInit or AfterContentInit, which led us to use patterns like <a href="https://github.com/taiga-family/taiga-ui/blob/a28e79b34814f312ad1ef8398ebe5070e0120ed5/projects/addon-table/components/table/tr/tr.component.ts#L63-L65">this</a>:</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>This pattern helped us make things more declarative, but still added a layer of boilerplate.</p><p>Now? Signals like viewChild and contentChild change the game. No more extra streams — signals automatically know when they’re ready and notify their dependents reactively. In most cases, we don’t even need ngOnChanges / ngAfterViewInit / ngAfterContentInit anymore.</p><p>And for those rare moments when we do need to interact with fully rendered DOM — for instance, embedding a full-featured chart using some third-party library — Angular now gives us the afterNextRender() hook.</p><p>Will lifecycle hooks disappear entirely? Probably not. But thanks to signals, you’ll be using them way less often. And it simplifies Angular for newcomers in a big way.</p><h3>Angular 18</h3><h4>Fallback content in &lt;ng-content /&gt;</h4><p><a href="https://angular.dev/guide/components/content-projection">Content projection</a> is one of the core ways to make any Angular UI Kit component flexible. Thankfully, Angular’s design around it was smart from the start — with features like select and ngProjectAs baked in. If you want to dig deeper into the power of content projection, check out <a href="https://medium.com/angularwave/components-constructors-the-power-of-ng-content-in-angular-a9bf936cb223">this article</a> by my teammate — packed with real-world examples from Taiga UI.</p><p>And the evolution didn’t stop there. Angular 18 added support for <a href="https://angular.dev/guide/components/content-projection#fallback-content">fallback content</a> in &lt;ng-content&gt;, so if nothing gets projected, you can now show a default instead. That’s a fantastic boost for building resilient, flexible components.</p><h4><strong>Form control events</strong></h4><pre>import {FormControl, Validators} from &#39;@angular/forms&#39;;<br><br><br>const control = new FormControl&lt;string&gt;(&#39;&#39;, Validators.required);<br>control.events.subscribe(event =&gt; {<br>   // ValueChangeEvent | PristineChangeEvent | TouchedChangeEvent | ...<br>});</pre><p>Finally, we can subscribe to events like when a control becomes touched. Fun fact: we’d been tracking this feature request for years. The entire Taiga UI codebase has always been built around the OnPush change detection strategy — except for one traitor: <a href="https://taiga-ui.dev/v2/components/field-error">FieldError</a>.</p><p>That single component had to stay on the Default strategy because there was no reliable way to detect the touched state. Our team lead <a href="https://github.com/angular/angular/issues/10887#issuecomment-421289798">wrote</a> back in 2018 on the feature request thread:</p><blockquote>I’m with the guys above here, with my entire UI library using OnPush and that scoundrel FieldError component spoiling the party.</blockquote><p>Well, not anymore!</p><h4>New @let syntax</h4><p>According to the Angular team <a href="https://blog.angular.dev/introducing-let-in-angular-686f9f383f0f">announcement</a>, the new @let syntax solves <a href="https://github.com/angular/angular/issues/15280">one</a> of the most upvoted issues in the Angular community.</p><p>Before this was introduced, Taiga UI shipped our own structural directive — <a href="https://taiga-ui.dev/directives/let">*tuiLet</a> — which made it possible to declare local template variables. It caught on fast: over 6,000 usages across all our company projects!</p><p>Now, the Taiga version of *tuiLet is retiring with honors, stepping aside for the new built-in feature — which works out of the box, no imports needed. 🎉</p><h3>Angular 19</h3><h4>Linked signals</h4><p>Angular’s product and DevRel lead Minko Gechev provides a good <a href="https://x.com/mgechev/status/1861837872384581947?s=46">description</a> of the new feature:</p><blockquote>You can think about it as “writable computed” in a way</blockquote><p>I aim to keep things declarative when building signals, and computed syntax helps with that a lot. But every so often, I’d run into that annoying 1% edge case where my beautifully clean computed needed to manually update with some extra if-logic.</p><p>Creating a whole separate signal dependency just for that case? Not elegant. That’s where linked signals come to the rescue — they let us handle those exceptions without cluttering up our code.</p><h4>Error on unused component imports</h4><p>A really nice little addition. After refactoring, it’s so easy to leave behind unused imports. Sure, tree-shaking will clean them up during build, but having less clutter in your code makes maintenance so much easier — and now Angular points them out for you automatically.</p><h4>HMR (again!)</h4><p>It took four years and eight major Angular versions, but we finally got solid HMR support for templates and styles! 😀</p><h3>Angular 20</h3><p>It’s hard to judge something you haven’t had much time to try yet. Sure, Angular 20 brought a bunch of great improvements — stabilizing experimental features — but two things stood out to me in the changelog as a real gem.</p><h4>Dynamic Components Levels Up</h4><p>Since Angular 14, we’ve had a powerful and handy utility called <a href="https://angular.dev/api/core/createComponent">createComponent</a> that made creating dynamic components a whole lot easier. But back then, its features were pretty limited.</p><p>With Angular 20, <a href="https://blog.angular.dev/announcing-angular-v20-b5c9c06cf301#5775">everything changed</a>!</p><p>Now it lets you specify bindings (input/output/two way binding) to dynamically created components and even apply directives (!!!). That’s a real game-changer for UI Kit development!</p><h4>Typed host bindings</h4><p>Angular docs has the following <a href="https://angular.dev/guide/components/host-elements#the-hostbinding-and-hostlistener-decorators">recommendation</a>:</p><blockquote><strong>Always prefer using the </strong>host<strong> property over </strong>@HostBinding<strong> and </strong>@HostListener<strong>.</strong> These decorators exist exclusively for backwards compatibility.</blockquote><p>The reasoning? Angular’s broader move away from decorators. The community has already seen modern alternatives to nearly every classic decorator — @Input, @Output, @ViewChild, and so on.</p><p>The problem? The host property had poor type checking, which made it far from a drop-in replacement. In practice, we ran into bugs that simply wouldn’t have existed if strong typing were in place.</p><p>Thankfully, <a href="https://github.com/angular/angular/pull/60267">Angular 20 fixes that</a>. Full type safety has arrived for host bindings, making the docs’ long-standing recommendation finally safe to follow — and making our code safer and more maintainable in the process.</p><h3>What’s Next?</h3><p>What else could shake up the daily life of an Angular UI Kit developer in the next major releases? Dropping Angular animations!</p><p>The official Angular documentation already features a page titled <a href="https://angular.dev/guide/animations/migration">“Migrating away from Angular’s Animations package”</a>, which says:</p><blockquote>Almost all the features supported by @angular/animations have simpler alternatives with native CSS. Consider removing the Angular Animations package from your application, as the package can contribute around 60 kilobytes to your JavaScript bundle. Native CSS animations offer superior performance, as they can benefit from hardware acceleration. Animations defined in the animations package lack that ability.</blockquote><p>At Taiga UI, we <a href="https://github.com/taiga-family/taiga-ui/pull/10966">jumped</a> on this trend early — even before it was widely promoted — and started migrating away from Angular animations in favor of native CSS animations.</p><p>And guess what? It really works. Native animations turn out to be concise, straightforward, and totally capable of handling all the same scenarios Angular animations once covered.</p><p>The only real challenge? Replacing the <a href="https://angular.dev/guide/animations/transition-and-triggers#animate-entering-and-leaving-a-view">:leave</a> behavior in a clean, native way. There’s no great built-in alternative for that — unless you want to mess with timer’s delays and boilerplate. But thankfully, one of my teammates came up with an elegant solution, and now we have no blockers left.</p><iframe src="https://cdn.embedly.com/widgets/media.html?type=text%2Fhtml&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;schema=twitter&amp;url=https%3A//x.com/Waterplea/status/1916082025418654130&amp;image=" width="500" height="281" frameborder="0" scrolling="no"><a href="https://medium.com/media/066ff90603163253dc9206ccac186a28/href">https://medium.com/media/066ff90603163253dc9206ccac186a28/href</a></iframe><p>Will Angular animations be deprecated in the next few major versions? Who knows — but I wouldn’t be surprised!</p><h3>Final Thoughts</h3><p>I’ll be honest with you — this entire article was one big experiment. I was curious to see what would surface if I really dug through my memory and tried to connect Angular’s changes with how they impacted our day-to-day work on a UI Kit.</p><p>It reminded me of that classic movie line: <em>“Life is like a box of chocolates. You never know what you’re gonna get.”</em> Same goes for Angular updates — you don’t always know what will turn out to be a hidden gem — or just unnecessary baggage.</p><p>But looking back gives us perspective. It helps us understand what truly matters and gives us a better shot at making smarter choices ahead.</p><p>I hope you enjoyed this walk through Angular’s evolution just as much as I did!</p><p><strong>Now your turn: </strong>what was the biggest killer feature from the past 10 Angular versions for your projects?</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=97d2dd56b607" width="1" height="1" alt=""><hr><p><a href="https://medium.com/angularwave/watching-angular-evolve-taiga-ui-kit-maintainers-perspective-97d2dd56b607">Watching Angular Evolve:
Taiga UI Kit Maintainer’s Perspective</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[CSS mask powers]]></title>
            <link>https://javascript.plainenglish.io/css-mask-powers-236a73c4714c?source=rss-8ad93c244ca3------2</link>
            <guid isPermaLink="false">https://medium.com/p/236a73c4714c</guid>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[html]]></category>
            <category><![CDATA[front-end-development]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[css]]></category>
            <dc:creator><![CDATA[Nikita Barsukov]]></dc:creator>
            <pubDate>Wed, 27 Nov 2024 16:24:44 GMT</pubDate>
            <atom:updated>2024-11-27T16:24:44.616Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/780/1*tv37WOCkXRmI0DRGupsp4A.jpeg" /></figure><p>December 2023 marked a milestone in the evolution of the CSS <a href="https://developer.mozilla.org/docs/Web/CSS/mask">mask</a> property. All major browsers, in their latest versions, now fully support it — no vendor prefixes needed anymore. This means this feature is officially here to stay and has become an essential tool for every front-end developer. It’s time for developers to take it on board and leave any doubts behind!</p><p>In this article, I’ll quickly go over the key concepts behind this property and then dive into real-world examples based on my experience working with <a href="https://github.com/taiga-family/taiga-ui">Taiga UI</a>.</p><h3><strong>What is a mask in CSS?</strong></h3><p>The term “masking” has historically been used in many areas of life, often with completely different meanings. The kind of mask we’ll be talking about here comes from the world of design. In design, image masking is a popular technique that lets you hide or cut out parts of an image in any shape you want.</p><p>Let’s break it down with an oversimplified example.</p><p>Here’s a beautiful image generated by an AI using a prompt that included the words “taiga”, “sunset,” and “winter.”</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*oZDhFCfXt_tgyM6LR4PykQ.png" /><figcaption>taiga-winter-sunset.jpeg</figcaption></figure><p>And here’s the lovely logo of our Open Source product — Taiga UI.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rUyU21K3s2HeSjGawAI3AA.png" /><figcaption><em>taiga-logo.svg</em></figcaption></figure><p>It’s important to note that in the last image every area that’s not orange is actually a transparent background. If you’d rather see for yourself, go ahead and check the <a href="https://raw.githubusercontent.com/taiga-family/taiga-ui/main/projects/demo/src/assets/images/taiga.svg">source file</a>! Keep this in mind — it’s a key detail for everything we’re about to do next.</p><p>Let’s build a super simple web app. Inside the body tag, we’ll place just one single img tag, and for the src, we’ll use the image we generated earlier with the AI:</p><pre>&lt;img src=&quot;taiga-winter-sunset.jpeg&quot; /&gt;</pre><p>And in the linked CSS file, we add the following content (don’t worry, we’ll explore each line in detail soon):</p><pre>body {<br>   background: mediumpurple;<br>}<br> <br>img {<br>   mask-image: url(taiga-logo.svg);<br>   mask-repeat: no-repeat;<br>   mask-size: auto 100%;<br>   mask-position: center;<br>}</pre><p>Here’s how it turns out:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IW7gdFOBwj5JcauodPt7bw.png" /></figure><p>Let’s start with the key property, mask-image. We applied an SVG image to it (to keep things simple in this article, I’ll sometimes mention the content passed to mask-image as just “mask”). In this example, the mask has only two types of areas: a transparent background and an orange tree-shaped logo.</p><p>The browser took all the <em>non-transparent</em> areas from the mask image — this opaque part defined the visible part of the original sunset picture. Everything else that was transparent got cut out — just like in a kids’ craft project with scissors. What’s more, notice that the parts of the sunset image that disappeared were genuinely removed — we can see the purple background we applied to the body tag.</p><p>Alright, now we’ve got the basics of mask-image. Let’s take a closer look at the other properties. Like many love stories say, “you don’t know what you’ve got till it’s gone.” So let’s test this out by removing all the properties except mask-image and see what happens:</p><pre>img {<br>   mask-image: url(taiga-logo.svg);<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0qEB5eAIZzzxWEy3wW-m8w.png" /></figure><p>We ended up with way too many trees… The issue is that we applied a tiny mask image (the original SVG logo is just 68x60 pixels) to a huge sunset picture. By default, the browser tries to fit in as many mask repeats as possible.</p><p>To control this behavior, we use the <a href="https://developer.mozilla.org/docs/Web/CSS/mask-repeat">mask-repeat</a> property. Let’s put it back:</p><pre>img {<br>   mask-image: url(taiga-logo.svg);<br>   mask-repeat: no-repeat;<br>}</pre><p>Here’s how it looks now:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SagJ7FYZDWKM7s7oHYhjTw.png" /></figure><p>The repeated trees are gone, but the size is definitely incorrect. This is where the <a href="https://developer.mozilla.org/docs/Web/CSS/mask-size">mask-size</a> property comes in. It takes two values: the width and height of the mask image. In this case, we want our logo to be displayed at full height, with the width stretching proportionally to maintain its original aspect ratio.</p><pre>img {<br> mask-image: url(taiga-logo.svg);<br> mask-repeat: no-repeat;<br> mask-size: auto 100%;<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4l2ohQ9eB09PKDrxOWAEFA.png" /></figure><p>And now our image is almost perfect, but it’s stuck to the left edge. To fix this, we use the <a href="https://developer.mozilla.org/docs/Web/CSS/mask-position">mask-position</a> property, which can take up to two arguments: horizontal and vertical offsets. If you only provide one value, it’s applied to both axes.</p><p>Finally, we’ve broken down the solution in detail!</p><p>You can experiment with the examples we just discussed in this StackBlitz example:</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fstackblitz.com%2Fedit%2Fbasic-css-mask-demo%3Fembed%3D1%26file%3Dindex.html&amp;display_name=StackBlitz&amp;url=https%3A%2F%2Fstackblitz.com%2Fedit%2Fbasic-css-mask-demo%3Ffile%3Dindex.html&amp;image=https%3A%2F%2Fsocial-img.staticblitz.com%2Fprojects%2Fbasic-css-mask-demo%2F1e090f8ebabfd2ed13e0f1da230a3034&amp;type=text%2Fhtml&amp;schema=stackblitz" width="745" height="400" frameborder="0" scrolling="no"><a href="https://medium.com/media/e0ca011206f29029dfd35c5881d9d6f7/href">https://medium.com/media/e0ca011206f29029dfd35c5881d9d6f7/href</a></iframe><p>We’ve covered the bare minimum you need to know about CSS masks. The theory described here is far from exhaustive. But if we were to dive deep into every theoretical aspect, this article would end up being no different from official documentation or many already existing tutorial articles.</p><p>The main goal of this article is entirely different — to share personal experience solving interesting challenges in UI Kit development using CSS masking. So, if you still find the syntax or core concepts of masking tricky, I’d personally recommend checking out Ahmad Shadeed’s article <a href="https://ishadeed.com/article/css-masking"><em>“CSS</em><strong>‑</strong><em>Masking”</em></a><em>.</em></p><p>Now, let’s move on to real-world examples!</p><h3>Fade</h3><p>Too much text — it’s a problem we constantly deal with on the web. Even beginner developers know how to handle it using the CSS property <a href="https://developer.mozilla.org/docs/Web/CSS/text-overflow">text<strong>‑</strong>overflow</a>. Just set it to ellipsis, and any overflowing content gets “chopped off” with a neat little … — that’s basic stuff.</p><p>But then your designer comes along and says, “No more ellipses in our design system. We want overflowing content to fade out smoothly.” Just like in the illustration below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Txd4Qaop8sc5jLITS0SKrA.gif" /></figure><p>For a block with single-line text, the problem is solved quite easily:</p><pre>.fade {<br>   mask-image: linear-gradient(to right, black 80%, transparent 90%);<br>}</pre><p>We told the browser to use a <a href="https://developer.mozilla.org/docs/Web/CSS/gradient/linear-gradient">linear gradient</a> as the mask. From left to right, the first 80% is solid black, the last 10% is fully transparent, and the 10% in between (from 80% to 90%) creates a smooth transition from solid black to fully transparent.</p><blockquote>In the current state of things, DevTools doesn’t offer convenient tools for debugging CSS masks. However, there’s a handy workaround: temporarily replace mask-image with background-image during development. This lets you visually see what the mask looks like — everything opaque stays, and everything transparent gets cut out.</blockquote><p>This solution isn’t exactly groundbreaking — it’s covered in almost every CSS masking tutorial. But let’s make things more interesting.</p><p>Now the designer comes back with a <em>new challenge</em>: they want the fading effect to work not just for single-line text, but also for multiline content. For example, the first two lines of a long text should be fully visible, the third line should fade out gradually, and everything after that should be completely hidden.</p><p>The previous solution won’t cut it here. Instead, we’ll need a multi-layer mask. Yep, <strong>every element can have more than one mask</strong>, applied as layers. The syntax is simple — just add the properties for each layer, separated by commas.</p><pre>.multi-line-fade {<br>   height: 3lh;<br>   overflow-y: hidden;<br>   mask-image:<br>       linear-gradient(black, black),<br>       linear-gradient(to right, transparent 80%, black 90%);<br>   mask-position:<br>       0 0,<br>       bottom right;<br>   mask-size:<br>       auto,<br>       100% 1lh;<br>   mask-repeat: no-repeat;<br>}</pre><p>The first two style rules ensure that the text container doesn’t exceed the height of three lines, and anything beyond that is hidden. In the mask<strong>‑</strong>image property, we’ve listed two mask layers separated by a comma.</p><ul><li><strong>The first layer</strong> is a gradient that transitions from black to black, essentially making it solid black across the entire area (simply using black wouldn’t work).</li><li><strong>The second layer</strong> is almost the same gradient we used earlier for single-line text, except the colors black and transparent are swapped (we’ll explain why later).</li></ul><p>Next, using the mask-position and mask-size properties, we specify where and how these mask layers are positioned. The first layer covers the first three lines of text, while the second layer only applies to the last visible line (the third line).</p><p>But this setup still doesn’t work yet! The first mask layer completely covers all three lines, making the second layer useless. To fix this, we need to exclude the second layer from the first layer.</p><p>This is where the <a href="https://developer.mozilla.org/docs/Web/CSS/mask-composite">mask-composite</a> property comes in. It has several possible values, but its main purpose is to define how the mask layers interact. By default (value add), the layers stack on top of each other, expanding the opaque areas. However, in our case, we need the <a href="https://developer.mozilla.org/docs/Web/CSS/mask-composite#exclude">exclude</a> value. According to the documentation, “the non-overlapping regions are combined” — making it perfect for our needs.</p><p>Here’s why the second mask layer was designed this way: its first 80% is transparent, meaning it doesn’t overlap with the first layer. The remaining part of the layer is semi-transparent, which <strong>partially</strong> overlaps with the first layer and effectively <strong>partially</strong> “excludes” that area from the final combined mask.</p><p>Here’s the final solution for handling overflow in multiline text:</p><pre>.multi-line-fade {<br>  height: 3lh;<br>  overflow-y: hidden;<br>  mask-image:<br>          linear-gradient(black, black),<br>          linear-gradient(to right, transparent 80%, black 90%);<br>  mask-position:<br>          0 0,<br>          bottom right;<br>  mask-size:<br>          auto,<br>          100% 1lh;<br>  mask-repeat: no-repeat;<br>  mask-composite: exclude;<br>}</pre><p>I’m also sharing the final solutions as a StackBlitz example for you to experiment with yourself.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fstackblitz.com%2Fedit%2Fcss-mask-fade%3Fembed%3D1%26file%3Dindex.html&amp;display_name=StackBlitz&amp;url=https%3A%2F%2Fstackblitz.com%2Fedit%2Fcss-mask-fade%3Fembed%3D1%26file%3Dindex.html&amp;image=https%3A%2F%2Fsocial-img.staticblitz.com%2Fprojects%2Fcss-mask-fade%2F1e69b3d29a07d1877adecf3efadf2627&amp;type=text%2Fhtml&amp;schema=stackblitz" width="745" height="400" frameborder="0" scrolling="no"><a href="https://medium.com/media/6132d3800ed7b8eb78a5c96610a73972/href">https://medium.com/media/6132d3800ed7b8eb78a5c96610a73972/href</a></iframe><p>And if you’re looking for inspiration with an even more complex (but much more flexible) technical solution, I invite you to check out the Taiga UI <a href="https://taiga-ui.dev/directives/fade">Fade</a> directive!</p><h3>Sensitive</h3><p>You’ve got a new task: develop a tool that can visually hide any part of content from the user. Something like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/758/1*NwQ-xGB800D-126ZdPPGIw.gif" /></figure><p>In banking apps, this feature is widely used to let users hide sensitive information (like account balances or bank details) when recording their screen or showing application’s details to someone in person. You might also have seen a similar feature in social media, used for spoiler text or images.</p><p>First, we’ll create a mask image. For this, we need a very simple SVG made up of random squares with varying levels of transparency.</p><pre>&lt;svg<br>   width=&quot;360&quot;<br>   height=&quot;48&quot;<br>   preserveAspectRatio=&quot;none&quot;<br>   fill=&quot;black&quot;<br>   xmlns=&quot;http://www.w3.org/2000/svg&quot;<br>&gt; <br>   &lt;rect opacity=&quot;0.2&quot; width=&quot;24&quot; height=&quot;24&quot;/&gt;<br>   &lt;rect opacity=&quot;0.2&quot; x=&quot;336&quot; y=&quot;24&quot; width=&quot;24&quot; height=&quot;24&quot;/&gt;<br>   &lt;rect opacity=&quot;0.35&quot; x=&quot;120&quot; y=&quot;24&quot; width=&quot;24&quot; height=&quot;24&quot;/&gt;<br>   &lt;!-- [...] --&gt;<br>   &lt;rect opacity=&quot;0.2&quot; x=&quot;264&quot; y=&quot;0&quot; width=&quot;24&quot; height=&quot;24&quot;/&gt;<br>   &lt;rect opacity=&quot;0.32&quot; x=&quot;168&quot; y=&quot;0&quot; width=&quot;24&quot; height=&quot;24&quot;/&gt;<br>&lt;/svg&gt;</pre><p>There are plenty of ways to create this kind of mask. You could generate a bunch of rect tags directly in your IDE, ask your designer to develop it in Figma, or just use <a href="https://github.com/taiga-family/taiga-ui/blob/b20459b326a917c64ad10da54d68905af5720042/projects/kit/directives/sensitive/sensitive.style.less#L15">our ready-made solution</a>.</p><p>The key is that the final mask should look something like this (and remember — the crucial point is to use varying levels of transparency, <strong>not</strong> shades of gray!).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/720/1*xMG7QeOm04pH7YOixtGa7Q.png" /></figure><p>Then nothing stops you from inlining the resulting SVG directly into your CSS property.</p><pre>.sensitive {<br>   background: currentColor;<br>   mask-image: url(&#39;data:image/svg+xml,&lt;svg width=&quot;360&quot; ...&gt;...&lt;/svg&gt;&#39;);<br>   mask-size: auto 100%;<br>}</pre><p>Don’t worry — when you use this technique in different places, the browser won’t fetch the SVG each time. It takes care of caching automatically.</p><p>Also, note that we intentionally set the property background: currentColor — this makes the semi-transparent squares of the mask match the text color they’re covering.</p><p>And once again, success! Here’s a StackBlitz example for you to experiment with.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fstackblitz.com%2Fedit%2Fcss-mask-sensitive%3Fembed%3D1%26file%3Dindex.css&amp;display_name=StackBlitz&amp;url=https%3A%2F%2Fstackblitz.com%2Fedit%2Fcss-mask-sensitive%3Fembed%3D1%26file%3Dindex.css&amp;image=https%3A%2F%2Fsocial-img.staticblitz.com%2Fprojects%2Fcss-mask-sensitive%2Fbd2b063ebaf9bc73a0d7af47dbbd9227&amp;type=text%2Fhtml&amp;schema=stackblitz" width="745" height="400" frameborder="0" scrolling="no"><a href="https://medium.com/media/26d74cb18eeea3899d16174377baaf40/href">https://medium.com/media/26d74cb18eeea3899d16174377baaf40/href</a></iframe><p>For anyone wanting more insights, feel free to dive into the source code of our Taiga UI <a href="https://taiga-ui.dev/directives/sensitive">Sensitive</a> component!</p><h3>Checkbox</h3><p>It’s time to show you how to create customizable checkboxes without adding extra HTML elements — just by harnessing the power of CSS masks.</p><blockquote>Over years of developing Taiga UI, we’ve always aimed to avoid unnecessary template nesting. Ignoring this principle makes it much harder to customize components, often leading to an overuse of public CSS variables — which isn’t the best approach.</blockquote><p>Let’s start with a native &lt;input type=”checkbox” /&gt; and disable all built-in browser styling using appearance: none. Then, we’ll customize the look of the checkbox “box”:</p><pre>input[type=&#39;checkbox&#39;] {<br>   appearance: none;<br>   cursor: pointer;<br>   width: 2rem;<br>   height: 2rem;<br>   position: relative;<br>   overflow: hidden;<br>   box-shadow: inset 0 0 0 0.125rem lightgray;<br>   border-radius: 0.5rem;<br>}</pre><p>Next, we’ll add behavior so that when the checkbox is checked, it smoothly fills with color. This is where the <a href="https://developer.mozilla.org/docs/Web/CSS/:checked">:checked</a> pseudo-class comes in handy.</p><pre>input[type=&#39;checkbox&#39;] {<br>   /* [...] A chunk of the previously described styles */<br>   background: transparent;<br>   transition: background-color 0.3s;<br>}<br> <br>input[type=&#39;checkbox&#39;]:checked {<br>   background: lavender;<br>}</pre><p>All that’s left is to add the checkmark. We’ll use the :after pseudo-element along with our CSS masking skills.</p><pre>input[type=&#39;checkbox&#39;]::after {<br> content: &#39;&#39;;<br> position: absolute;<br> inset: 0;<br> background: #333;<br> mask-image: url(&#39;data:image/svg+xml,&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 16 16&quot;&gt;&lt;path d=&quot;M14 5L7 12L3 8C3 8 4 7 5 7.5L7 9.5L11.5 5C11.5 5 13 4 14 5Z&quot;/&gt;&lt;/svg&gt;&#39;);<br> transform: scale(0);<br> transition: transform 0.3s;<br>}<br><br>input[type=&#39;checkbox&#39;]:checked::after {<br> transform: none;<br>}</pre><p>Using position: absolute + inset: 0, we positioned the :after pseudo-element to take up the full width and height of its parent. Then, we filled the entire pseudo-element with a solid black background. Using mask<strong>‑</strong>image, we cut out the shape of the checkmark with a simple inline icon. All the other lines of code are just for animating the checkmark’s appearance and disappearance smoothly.</p><p>Check out the result in action:</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fstackblitz.com%2Fedit%2Fcss-mask-checkbox%3Fembed%3D1%26file%3Dindex.css&amp;display_name=StackBlitz&amp;url=https%3A%2F%2Fstackblitz.com%2Fedit%2Fcss-mask-checkbox%3Fembed%3D1%26file%3Dindex.css&amp;image=https%3A%2F%2Fsocial-img.staticblitz.com%2Fprojects%2Fcss-mask-checkbox%2F66f793133db0779d8ced6392188b74fd&amp;type=text%2Fhtml&amp;schema=stackblitz" width="745" height="400" frameborder="0" scrolling="no"><a href="https://medium.com/media/1d4e522d360373b8b8ca2f5a269e8743/href">https://medium.com/media/1d4e522d360373b8b8ca2f5a269e8743/href</a></iframe><p>The Taiga UI implementation of the <a href="https://taiga-ui.dev/components/checkbox">Checkbox</a> component is based on the described approach but includes even more interesting techniques that go beyond the scope of this article. Be sure to take a closer look at it when you have time!</p><h3>We’ll continue this journey soon</h3><p>I hope this article has convinced you that CSS masking is a fascinating and powerful tool for any frontend developer. Mastering it can lead to some truly magical results!</p><p>This article only touches on a surface of CSS masking’s potential — you’ll find even more in our <a href="https://github.com/taiga-family/taiga-ui">Taiga UI</a> component library. But I don’t want to overwhelm you with too much information in one go (and, to be honest, I need a break to gather my thoughts for explaining the more complex examples).</p><p>If you enjoyed this article, I will gladly release a follow-up article where we’ll dive into additional CSS masking examples (like how and why we stack radial gradients for <a href="https://taiga-ui.dev/components/progress-segmented">ProgressSegmented</a>, the clever tricks behind the new <a href="https://taiga-ui.dev/components/icon">Icon</a> component, and the challenges of the complex <a href="https://taiga-ui.dev/components/switch">Switch</a> component).</p><p>Follow me to stay tuned for the next article!</p><h3>In Plain English 🚀</h3><p><em>Thank you for being a part of the </em><a href="https://plainenglish.io/"><strong><em>In Plain English</em></strong></a><em> community! Before you go:</em></p><ul><li>Be sure to <strong>clap</strong> and <strong>follow</strong> the writer ️👏<strong>️️</strong></li><li>Follow us: <a href="https://x.com/inPlainEngHQ"><strong>X</strong></a> | <a href="https://www.linkedin.com/company/inplainenglish/"><strong>LinkedIn</strong></a> | <a href="https://www.youtube.com/channel/UCtipWUghju290NWcn8jhyAw"><strong>YouTube</strong></a> | <a href="https://discord.gg/in-plain-english-709094664682340443"><strong>Discord</strong></a> | <a href="https://newsletter.plainenglish.io/"><strong>Newsletter</strong></a> | <a href="https://open.spotify.com/show/7qxylRWKhvZwMz2WuEoua0"><strong>Podcast</strong></a></li><li><a href="https://differ.blog/"><strong>Create a free AI-powered blog on Differ.</strong></a></li><li>More content at <a href="https://plainenglish.io/"><strong>PlainEnglish.io</strong></a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=236a73c4714c" width="1" height="1" alt=""><hr><p><a href="https://javascript.plainenglish.io/css-mask-powers-236a73c4714c">CSS mask powers</a> was originally published in <a href="https://javascript.plainenglish.io">JavaScript in Plain English</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Introducing Taiga UI v4]]></title>
            <link>https://medium.com/angularwave/introducing-taiga-ui-v4-df6c7c62330f?source=rss-8ad93c244ca3------2</link>
            <guid isPermaLink="false">https://medium.com/p/df6c7c62330f</guid>
            <category><![CDATA[open-source]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[front-end-development]]></category>
            <category><![CDATA[web]]></category>
            <category><![CDATA[angular]]></category>
            <dc:creator><![CDATA[Nikita Barsukov]]></dc:creator>
            <pubDate>Sat, 10 Aug 2024 11:57:39 GMT</pubDate>
            <atom:updated>2024-08-10T11:57:39.704Z</atom:updated>
            <content:encoded><![CDATA[<h3><strong>Introducing Taiga UI v4: </strong><em>Even More Components and Enhancements</em></h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vcUVr-D7Ef7i_-sLW-YjPA.png" /></figure><p>Happy to announce that we’ve released the first stable version of <a href="https://taiga-ui.dev">Taiga</a> 4.0 — our extensive component library for Angular. There are so many improvements that a single article can hardly cover them all. I’ll tell you about the most exciting ones.</p><h3>A major release — a new milestone in our journey</h3><p>In our team, we always consider major releases as a significant and responsible step: we don’t publish them too frequently, and the preparation time spans months or even seasons.</p><p>In December 2020, we had introduced our second major release, which marked the beginning of Taiga’s Open-source journey. The third major release was announced only in late summer 2022. And we started developing the fourth version of Taiga at the end of 2023, and we are announcing its first stable release only now.</p><p>Of course, between major releases, we don’t sit idle; we continue to release almost every week. For example, the second major version of Taiga UI had 99 minor releases. But the most interesting things always happen when breaking changes come…</p><h3>Bumping the minimum required Angular version</h3><p>In my previous articles, I already mentioned many times that authors of libraries for Angular face a significant <a href="https://angular.dev/tools/libraries/creating-libraries#ensuring-library-version-compatibility">challenge</a> — the need to ensure compatibility of their libraries with applications written in older versions of Angular.</p><p>Yes, no matter how much we want to or how many resources we have, we cannot immediately start using the latest version of Angular to publish our libraries. If we upgrade to the most recent version, our users won’t be able to use our products until they upgrade to the same Angular version or higher. Since hundreds of production teams use our libraries, this is a luxury we cannot afford.</p><p>That’s why we stuck with Angular 12 for the entire third major version. It wasn’t easy for us: the framework team had moved far ahead during the last four years. But fortunately, we are finally upgrading to the 16th version of the framework.</p><p><strong>What this means for you:</strong></p><ul><li>If you already use a modern version of Angular in your applications, you don’t need to worry — everything will continue to work. However, if you’re using a version less than 16, it’s time to motivate your team to allocate time for updating Angular before migrating to the latest version of Taiga UI.</li><li>You can finally forget about modules! In the new release, we’ve rewritten all our directives and components to be <a href="https://angular.dev/guide/components/importing#standalone-components">standalone</a>, which should significantly improve tree shaking. Moreover, we’ve changed the naming rules and removed unnecessary postfixes. Reading the documentation about TuiButton? Then, just add TuiButton to your component imports (instead of TuiButtonModule or TuiButtonComponent). The code is simpler and cleaner!</li></ul><p><strong>What this gives us:</strong></p><ul><li><a href="https://angular.dev/guide/directives/directive-composition-api">Host directives</a> are now available, opening up new possibilities for code decomposition and simplification. They eliminate the need for extra HTML nesting, transforming many components into just directives. This is great for your customization and will also allow us to move their styles into a separate package in the future, describe their public API through data-* attributes, and use the appearance of many simple components even without Angular. Who knows, maybe in the future, you will see Taiga components for React or even for projects written in vanilla JavaScript? Let’s dream about it together!</li><li>We’ve used <a href="https://next.angular.dev/guide/signals">signals</a> for the first time and simplified some code with their help. We will fully experience their power with the transition to Angular 17 when <a href="https://angular.dev/guide/signals/inputs">signal inputs</a>, <a href="https://angular.dev/guide/signals/model">signal versions of two-way binding</a>, and <a href="https://angular.dev/guide/signals/queries">tools</a> for working with template queries become available. But even our first experience with signals has greatly impressed us!</li></ul><h3>Bumping browser support</h3><p>Every major release, we review the minimum versions of supported browsers. This allows us to remove a lot of workarounds from the codebase that exist only for legacy browsers. For you, this means that Taiga UI libraries become lighter and more stable (because we fully rewrite some codebase using native browser features).</p><p>In the current major release, we set higher minimum <a href="https://github.com/taiga-family/taiga-ui/pull/6818">requirements</a> for many browsers. Finally, without polyfills or fallbacks, we have access to browser APIs like <a href="https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver">ResizeObserver</a>, <a href="https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API">Clipboard</a>, <a href="https://developer.mozilla.org/en-US/docs/Web/API/VisualViewport">VisualViewport</a>, <a href="https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent">pointer events</a>, and the incredibly useful <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/beforeinput_event">beforeinput</a> event for all our masked text fields.</p><p>The most recent list of supported browsers is now published as a library — <a href="https://github.com/taiga-family/linters/tree/main/projects/browserslist-config">@taiga-ui/browserslist-config</a>.</p><h3>New @taiga-ui/legacy library</h3><p>With the fourth major release, we started publishing the <a href="https://github.com/taiga-family/taiga-ui/tree/main/projects/legacy">@taiga-ui/legacy</a> package, which acts as a transitional state for many outdated components before their full removal. All entities you find inside this package in the fourth major release will be removed in the fifth one. And during the preparation for a new major release, the package will once again be filled with outdated entities.</p><p>The motivation for creating this package is to simplify the migration to a latest major version. In most cases, the package includes components that already have modern equivalents in stable packages, but their migration process is too complex to be handled by automated migrations and requires manual efforts.</p><p>We hope it would be easier for you to migrate all the necessary components first (with the help of our automatic schematic migration) and then, at your own pace, remove the rest from the @taiga-ui/legacy package. But keep in mind, these components are no longer supported, so try to switch to the modern versions as quickly as possible.</p><h3>Experiments have moved to stable packages</h3><p>Users of our packages might have noticed a section in the <a href="https://taiga-ui.dev/v3">documentation</a> called “Experimental”. This section has been expanded with more and more directives and components from the @taiga-ui/experimental package.</p><p>Our team adopted this approach from the Angular team. For example, they publish the @angular/material-experimental package, whose root <a href="https://github.com/angular/components/tree/main/src/material-experimental#angular-material-experimental">README</a> contains the following phrase:</p><blockquote>This package contains prototypes and experiments in development for Angular Material. Nothing in this package is considered stable or production ready. While the package releases with Angular Material, breaking changes may occur with any release.</blockquote><p>Our strategy follows the same principles. The new @taiga-ui/experimental package allows users to see the direction of the next major release early on. Users can even start using this package in their applications and give us feedback if they wish. The important warning thing for these brave users — to remember that updating the experimental package should be done carefully, as the public API might change slightly at any time.</p><p>Most of the content from @taiga-ui/experimental will move to stable packages in the upcoming major release. The experimental package will remain empty until we start adding new entities to it during the preparation for the next major release.</p><p>Many of the new features described in this article were once part of this experimental package, but they have now moved to various stable Taiga libraries. Let’s dive into the details of each new feature!</p><h3>Radio и Checkbox</h3><p>Let’s start with checkboxes and recall their <strong>old </strong>public API.</p><p>Do you want to get a simple small square that shows a checkmark when clicked?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8DS2DJKIlJmzKQ6ntqUS3w.png" /></figure><p>Import TuiCheckboxModule and use the following markup:</p><pre>&lt;tui-checkbox [(ngModel)]=&quot;value&quot; /&gt;</pre><p>Want the same checkbox but with text beside it, and clicking on the text also checks it?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dDka5un1wmzG6LC1OgsH7g.png" /></figure><p>Import the TuiCheckboxLabeledModule and use the following markup:</p><pre>&lt;tui-checkbox-labeled [(ngModel)]=&quot;value&quot;&gt;<br>   Click on the text too<br>&lt;/tui-checkbox-labeled&gt;</pre><p>One more example:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XqJXHiVCjibfySpfpO5TrQ.png" /></figure><p>It’s basically an enhanced version of the previous CheckboxLabeled component, but now it has an extra border and click/hover works across the entire block area.</p><p>Let’s recall the name of the module (from Taiga UI v3) — TuiRadioBlockModule, and the markup for this component now looks like this:</p><pre>&lt;tui-checkbox-block [(ngModel)]=&quot;value&quot;&gt;<br>   An option<br>&lt;/tui-checkbox-block&gt;</pre><p>To give you a clear picture, let me remind you that the same situation happened with radio buttons. There were similar modules: <a href="https://taiga-ui.dev/v3/components/radio">TuiRadioModule</a>, <a href="https://taiga-ui.dev/v3/components/radio-labeled">TuiRadioLabeledModule</a>, and <a href="https://taiga-ui.dev/v3/components/radio-block">TuiRadioBlockModule</a>, all with the same logic I just described.</p><p>And now, having fully explained the previous situation (the 3d major version of Taiga UI), let’s appreciate the elegance of the updated version of these components.</p><p>To create the most basic checkboxes and radio buttons, now import TuiCheckbox and TuiRadio:</p><pre>&lt;input<br>   tuiCheckbox<br>   type=&quot;checkbox&quot;<br>   [(ngModel)]=&quot;value&quot;<br>/&gt;<br><br>&lt;input<br>   tuiRadio<br>   type=&quot;radio&quot;<br>   [(ngModel)]=&quot;value&quot;<br>/&gt;</pre><p>Do you want to add clickable labels? You’ll love the new style since it’s very similar to the native one. Import <a href="https://taiga-ui.dev/components/label">TuiLabel</a>, and then simply wrap the inputs above with a label tag (works in the same way for both checkboxes and radio buttons):</p><pre>&lt;label tuiLabel&gt;<br>   &lt;input<br>       tuiCheckbox<br>       type=&quot;checkbox&quot;<br>       [(ngModel)]=&quot;value&quot;<br>   /&gt;<br>   Label text<br>&lt;/label&gt;</pre><p>Do you want to replicate the behavior of the previous version of TuiCheckboxBlockModule / TuiRadioBlockModule ? Now there’s a unified solution for both! Import TuiBlock, and then simply apply this directive to the label tag:</p><pre>&lt;label tuiBlock&gt;<br>   &lt;input<br>       tuiCheckbox<br>       type=&quot;checkbox&quot;<br>       [ngModel]=&quot;value&quot;<br>   /&gt;<br>   Label text<br>&lt;/label&gt;</pre><p>All examples of the new API are also relevant for the new radio buttons.</p><p>Moreover, the Label and Block directives also work with the new <a href="https://taiga-ui.dev/components/switch">Switch</a> component.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*UIwvHqdthVc0Uz2sP0r7jg.png" /></figure><pre>&lt;label tuiBlock&gt;<br>   &lt;input<br>       tuiSwitch<br>       type=&quot;checkbox&quot;<br>       [ngModel]=&quot;value&quot;<br>   /&gt;<br>   Label text<br>&lt;/label&gt;</pre><p>The new versions of Checkbox, Radio, and Switch have a consistent public API that’s easy to remember because it’s very close to the native syntax of similar built-in browser components. And no hidden DOM nesting!</p><h3>SwipeActions</h3><p>The experimental <a href="https://taiga-ui.dev/components/swipe-actions">SwipeActions</a> component has been moved to the stable @taiga-ui/addon-mobile package. Its concept is simple and familiar to almost everyone since it’s implemented in almost all popular email services (like Gmail). It helps to offer users a set of additional actions with a horizontal swipe on mobile devices. The new component handles most of the work for you, and you just need to declaratively describe the content of these actions through the template.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/852/1*UWLDQheEfWCoaWKIXoHVzw.gif" /></figure><p>Technically, you need to wrap any content in &lt;tui-swipe-actions /&gt; and place a set of icon buttons next to it, which will appear when a swipe is performed:</p><pre>&lt;tui-swipe-actions&gt;<br>   &lt;div&gt;<br>       Any content you like<br>   &lt;/div&gt;<br><br>   &lt;button<br>       appearance=&quot;destructive&quot;<br>       iconStart=&quot;@tui.trash&quot;<br>       size=&quot;s&quot;<br>       tuiIconButton<br>       tuiSwipeAction<br>   &gt;&lt;/button&gt;<br>&lt;/tui-swipe-actions&gt;</pre><h3>Sensitive</h3><p>Our main package @taiga-ui/kit now includes an interesting directive — <a href="https://taiga-ui.dev/directives/sensitive">Sensitive</a>. It allows you to visually hide any part of the content from the user. For example, this feature can be used to hide sensitive information, like balance or bank account numbers, when users record their screen or demonstrate a device screen to their friends.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/852/1*hNbMI-fDN69FnpVkjqV44Q.gif" /></figure><p>The main advantage of this directive is that it is compatible with any native tag or custom Angular component. The only limitation is that the ::after pseudo-element should not be used on the hidden content. However, this limitation can be easily bypassed with additional html-nesting.</p><h3><strong>Skeleton</strong></h3><p>The most similar counterpart to the previous feature is the directive for creating skeletons.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/852/1*--drzAT5BQbDDRhuFr2AYg.gif" /></figure><blockquote>A skeleton in web development is a temporary placeholder that takes the place of content that hasn’t been loaded yet, showing its approximate layout until the loading is complete. Visually, skeletons are usually gray with a small pulsating animation to indicate the loading state.</blockquote><p>The new <a href="https://taiga-ui.dev/directives/skeleton">Skeleton</a> directive can be applied to any native tag or Angular component of any shape or size. Then, through the magic of the CSS <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/filter">filter</a> property, the directive turns any content into skeletons.</p><h3><strong>Segmented</strong></h3><p>The new <a href="https://taiga-ui.dev/navigation/segmented">Segmented</a> component is a popular way to use buttons in app navigation. It is especially useful when trying to make the app look like a native mobile app.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/852/1*-PIpr1MWD7fv0TnkBcsqew.gif" /></figure><h3><strong>Fade</strong></h3><p>Ellipsis at the end of a line is a popular way to show text overflow. If you’re looking for a different option, there’s the new <a href="https://taiga-ui.dev/directives/fade">Fade</a> directive, which has finally moved to the stable @taiga-ui/kit package.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/852/1*WL85eNOJcJfEliNbSgDtnw.gif" /></figure><h3><strong>DropdownMobile</strong></h3><p>Although this component was duplicated in the third major branch, it was created during the active development of the new major release. So, it deserves to be mentioned in this article.</p><p>Our team has developed a new version of dropdowns for mobile devices. Often, classic desktop dropdowns feel too cramped on small mobile screens. That’s where the new <a href="https://taiga-ui.dev/directives/dropdown#mobile">TuiDropdownMobile</a> directive from the @taiga-ui/addon-mobile package comes to the rescue.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/858/1*phhwAmu83L46_iR8tRaoog.gif" /></figure><p>You just need to attach the tuiDropdownMobile directive to the components like <a href="https://taiga-ui.dev/components/select">Select</a>, <a href="https://taiga-ui.dev/components/multi-select">MultiSelect</a>, or <a href="https://taiga-ui.dev/components/combo-box">ComboBox</a>, and the directive will do the rest: on desktop, it will open the usual dropdown list, but on mobile devices, it will open the version shown in the illustration above.</p><h3><strong>@taiga-ui/layout library</strong></h3><p>Components don’t always contain a heavy amount of JavaScript code. Sometimes, a web developer just needs a g̶o̶o̶s̶e̶ ̶f̶a̶r̶m̶ good collection of ready-made templates to simplify the layout work. In the latest release, our @taiga-ui/layout package has been enhanced with useful components and directives for this purpose.</p><p>The new <a href="https://taiga-ui.dev/layout/card-medium">CardMedium</a> and <a href="https://taiga-ui.dev/layout/card-large">CardLarge</a> components will help you develop such cards with minimal effort.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*pfkIrn7JmNBCQ7DMG2dYTg.png" /></figure><p>In the article, I’ve included just one type out of many possible variations. You’ll find many more interesting examples in the documentation.</p><p>The new <a href="https://taiga-ui.dev/layout/cell">Cell</a> component is perfect for use in long lists with a lot of text content.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Q2FSfEHIcZ-2JqcrchkdtA.png" /></figure><p>Finally, the new <a href="https://taiga-ui.dev/layout/block-details">BlockDetails</a> component, paired with the existing <a href="https://taiga-ui.dev/layout/block-status">BlockStatus</a>, will help you create great status pages in your application.</p><h3><strong>Icon</strong></h3><p>Before this major release, our approach with icons involved using a combination of <a href="https://developer.mozilla.org/ru/docs/Web/SVG/Element/svg">&lt;svg /&gt;</a> and <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/use">&lt;use /&gt;</a> tags. This approach worked fine but was overly complicated code-wise and had limitations. There were so many entities and preprocessing scripts needed for the icons that explaining how it all worked could fill an entire article.</p><p>In the new major release, the @taiga-ui/core package introduced a new <a href="https://taiga-ui.dev/components/icon">Icon</a> component, which has become the main way to work with icons in all other Taiga components. This new component displays icons through a CSS <a href="https://developer.mozilla.org/docs/Web/CSS/mask">mask</a>, opening up new possibilities:</p><ul><li>This allows icons to be hosted on a CDN and displayed without any need for DOM manipulations or sanitizers.</li><li>A more straightforward approach to icon scaling. Just change the size of the component’s host, and the icon will scale automatically. The property <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/vector-effect#non-scaling-stroke">vector-effect=”non-scaling-stroke”</a> ensures that scaling happens without losing details or unnecessarily changing the line thickness.</li><li>The browser automatically takes care of icon caching.</li></ul><h3><strong>Updated InputPhoneInternational</strong></h3><p>Our <a href="https://taiga-ui.dev/components/input-phone-international">InputPhoneInternational</a> component finally stopped depending on a bunch of hard-coded constants that we never had time to update!</p><p>We no longer store information about hundreds of phone number patterns for different countries. Now, Google does this for us. Its developers maintain a library <a href="https://github.com/google/libphonenumber">libphonenumber</a> that stores phone number patterns for all countries worldwide and keeps them always up to date!</p><p>Even before we started working on Taiga’s fourth major release, we had already developed the <a href="https://maskito.dev/addons/phone">@maskito/phone</a> library. It uses a <a href="https://www.npmjs.com/package/libphonenumber-js">JavaScript port</a> of Google’s library and the full power of <a href="https://github.com/taiga-family/maskito">Maskito</a> to create a masked text field for any country’s phone number with just a few lines of code. Now, we’ve integrated this library into Taiga’s InputPhoneInternational component.</p><p>But that’s not all! Our public version of this component used to rely on low-quality raster images for country flags. Now, our <a href="https://taiga-ui.dev/pipes/flag">tuiFlag</a> pipe (it is used inside the InputPhoneInternational component) returns beautiful, high-quality vector images instead of *.png files!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*E5VVTxHYY2SgZT45ij7f3g.png" /></figure><h3><strong>Textfield</strong></h3><p>And last but not least, I will describe the most comprehensive code refactoring. It is not even finished yet and will keep going in future releases.</p><p>We redesigned the <a href="https://taiga-ui.dev/v3/components/primitive-textfield">PrimitiveTextfield</a> component, which serves as the foundation for all other Taiga’s text fields. The updated version is now called <a href="https://taiga-ui.dev/components/textfield">Textfield</a>.</p><p>The main feature of the new component is its drastically changed API style. Textfield can now be defined and customized in a very declarative way through templates, rather than through tons of input properties like before.</p><p>Here’s the most basic example:</p><pre>&lt;tui-textfield<br>   iconStart=&quot;@tui.search&quot;<br>   iconEnd=&quot;@tui.settings&quot;<br>&gt;<br>   &lt;label tuiLabel&gt;I am a label&lt;/label&gt;<br>   &lt;input<br>       placeholder=&quot;I am placeholder&quot;<br>       tuiTextfield<br>       [(ngModel)]=&quot;value&quot;<br>   /&gt;<br>   &lt;tui-icon icon=&quot;@tui.bell&quot; /&gt;<br>   &lt;tui-icon tuiTooltip=&quot;I am a hint&quot; /&gt;<br>&lt;/tui-textfield&gt;</pre><p>This code produces the following text field:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Y9naw6cUtB54DmMACwCJgg.png" /></figure><p>After refactoring of this component, we’re moving all previous generation text fields to the @taiga-ui/legacy package. We set a goal to create new alternatives for them, but with a similar API style as their base component Textfield. Some components (i.e. <a href="https://taiga-ui.dev/components/input-card">InputCard</a>) have already been rewritten in the new style, while the rest will be completed in upcoming releases.</p><h3><strong>Wrapping Up</strong></h3><p>The fourth major release is already published. The described features are just a small part of all improvements — check out the release <a href="https://github.com/taiga-family/taiga-ui/releases/tag/v4.0.0">changelog</a> for more details.</p><p>We put in a lot of effort to make updating easier for you. We wrote a bunch of schematics that automatically go through your entire project and fix most of the breaking changes.</p><p>If you’re using Angular CLI, you can run the migration scripts using this console command:</p><pre>ng update @taiga-ui/cdk</pre><p>If your project is based on Nx CLI, the command to run is:</p><pre>nx migrate @taiga-ui/cdk</pre><p>If you happened to stumble upon this article and want to give Taiga a try in your project, we’ve got a command to make getting started easier:</p><pre>ng add taiga-ui</pre><p>If you face any issues or bugs during the update, don’t hesitate to <a href="https://github.com/taiga-family/taiga-ui/issues/new/choose">create an issue</a> or ask for help in <a href="https://t.me/taiga_ui/8242">our Telegram community</a>. The Taiga team looks forward to your feedback!</p><p>And just a reminder, the easiest way to say thanks is to star us 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=df6c7c62330f" width="1" height="1" alt=""><hr><p><a href="https://medium.com/angularwave/introducing-taiga-ui-v4-df6c7c62330f">Introducing Taiga UI v4</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[Taiga UI: 2023 Results]]></title>
            <link>https://medium.com/angularwave/taiga-ui-2023-results-a3a1d0a3dd57?source=rss-8ad93c244ca3------2</link>
            <guid isPermaLink="false">https://medium.com/p/a3a1d0a3dd57</guid>
            <category><![CDATA[frontend]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[open-source]]></category>
            <category><![CDATA[angular]]></category>
            <category><![CDATA[web]]></category>
            <dc:creator><![CDATA[Nikita Barsukov]]></dc:creator>
            <pubDate>Mon, 25 Dec 2023 11:49:41 GMT</pubDate>
            <atom:updated>2023-12-26T08:22:43.306Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/768/1*6G3hLnMNqSn3jVH2W7ujVA@2x.jpeg" /></figure><p>Today is Christmas, making it a perfect time to summarize the fruits of our labor over the year. In 2023, <a href="https://taiga-ui.dev/">Taiga UI</a> project had more than fifty releases, more than two thousand merged PRs and over five hundred closed issues.</p><p>Taiga UI is a powerful set of components for Angular, which is used in dozens of Tinkoff’s products. The project has been developed in Open Source for many years, gaining an audience worldwide.</p><p>In the article, I will continue our annual habit and briefly unpack the overall achievements related to Taiga UI over the past year.</p><h3>More than 3 years in Open Source</h3><p>For many years we’ve been developing our products publicly in Open Source. It’s important for us that users can always explore the source code and make their own amendments to enhance Taiga UI. In 2023, more than 20 external contributors submitted PRs of various complexity levels: from minor documentation adjustments to fixing serious bugs or even whole large features, such as <a href="https://github.com/taiga-family/taiga-ui/pull/3806">BreakpointService</a> or <a href="https://github.com/taiga-family/taiga-ui/pull/3369">declarative dialogue routing</a>.</p><p>Over the span of three years in Open Source, the number of contributors has exceeded the milestone of one hundred and fifty people! We can’t emphasize enough how much we value their contributions and welcome any improvements ❤️</p><p>This summer, a significant change took place in the history of Taiga UI. It has turned into something more than just a UI Kit. Taiga UI is now part of a whole family of products united under the common name of Taiga Family.</p><p>We have relocated to a separate GitHub organization where we have assembled the best technical solutions used for developing Taiga UI, including <a href="https://github.com/taiga-family/ng-web-apis">ng‑web‑apis</a>, <a href="https://github.com/taiga-family/maskito">maskito</a>, <a href="https://github.com/taiga-family/ng-polymorpheus">ng‑polymorpheus</a>, <a href="https://github.com/taiga-family/ng-morph">ng‑morph</a>, <a href="https://github.com/taiga-family/ng-event-plugins">ng‑event‑plugins</a>, and others. Among these, you will surely discover something useful for your Angular application or library. For more detailed information about each product, my colleague has written the following article:</p><p><a href="https://medium.com/its-tinkoff/taiga-ui-more-than-ui-kit-8867a52a63c6">Taiga UI: more than UI kit</a></p><p>We are pleased to see the growing public awareness of Taiga UI. In 2023, we gained 17% more stars on GitHub, and the number of downloads via npm exceeded 350,000. By comparison, in 2022, this number was slightly less than 200,000.</p><h3>New mask for text fields</h3><p>As 2022 came to a close, a remarkable event set a significant task for us in 2023. For years, the <a href="https://github.com/taiga-family/taiga-ui/issues/120">task</a> of seeking an alternative to the text field masking library had been pending in the Taiga UI backlog. After many rounds of research and discussion, our team devised our own dedicated library for this task — <a href="https://github.com/taiga-family/maskito">Maskito</a>.</p><p>At the beginning of this summer, the first stable major release was published. By that time, Maskito had expanded beyond being a single library to a full-fledged collection of libraries. The new development included the framework-agnostic package @maskito/core (a standalone and already sufficient package for initiating text field masking), the optional package @maskito/kit with a set of ready-to-use and configurable masks, and separate packages for popular JavaScript frameworks like React, Angular, and Vue.</p><p>If you’re interested in learning more about the development history of Maskito and seeing all its advantages in action, I’ve written about it before. Feel free to read more by visiting:</p><p><a href="https://medium.com/its-tinkoff/maskito-is-a-new-collection-of-libraries-for-text-field-masking-f64ec71951df">Maskito is a new collection of libraries for text field masking</a></p><p>We were uncertain until the last moment whether we had managed to create a flexible and user-friendly tool that would avoid the errors of its forerunners. The positive reaction from the community allayed our fears.</p><p>On June 21st, the widely-used framework Ionic made an <a href="https://ionic.io/blog/announcing-ionic-v7-1">announcement</a> naming Maskito as the recommended masking solution in its components. And now, on the Ionic documentation page, you can always find the <a href="https://ionicframework.com/docs/api/input#input-masking">guide</a> on how to use Maskito within your Ionic application.</p><p>The community expressed interest in the new product, and by the year’s end, the cumulative number of downloads on npm had exceeded <a href="https://npm-stat.com/charts.html?package=%40maskito%2Fcore&amp;from=2023-01-01&amp;to=2023-12-31">300,000</a>. It was particularly encouraging to see that even large enterprises had adopted Maskito!</p><p>We have entirely migrated all the masked text fields from the Taiga UI library to the new mask, solving numerous legacy issues. However, we are not resting on our laurels! Maskito still has areas to grow, and we aim to evolve it further. Stay tuned for the second major release, which is just around the corner!</p><h3>InputPhoneInternational and @maskito/phone</h3><p>We have an amazing component that can be tweaked endlessly. No matter how much effort we put in or how many hours we dedicate to its design, adjustments will emerge sooner or later. It’s name is <a href="https://taiga-ui.dev/components/input-phone-international">InputPhoneInternational</a>.</p><p>The issue here isn’t about our laziness or shifting our focus elsewhere. It’s because this component is a masked text field for entering international phone numbers, and the patterns for international phone numbers have a history of changing and expanding over time.</p><p>For a long time, we stored the masks for the phone number formats of different countries as a constant in our repository. As expected, updating this constant wasn’t always done promptly, which caused quite a bit of trouble. Luckily, the primary advantage of Open Source came to our rescue — bug reports from the community. A significant number of issues like <a href="https://github.com/taiga-family/taiga-ui/issues/3102">#3102</a> were raised.</p><p>We weren’t fond of constantly having to update the formats for phone numbers and wanted to shift the responsibility onto someone else. We found the perfect candidate — Google. The company has a library, libphonenumber, which contains the patterns for phone numbers from all countries around the world, and they’re always up-to-date! We realized immediately that it was exactly what we needed.</p><p>Sadly, the process turned out to be quite complicated. Before we could begin using it, we had to deal with a huge amount of data — the format was not at all suited for easily utilizing the data to construct masks for text fields. Consequently, we developed an additional package, <a href="https://www.npmjs.com/package/@maskito/phone">@maskito/phone</a>, which built upon Maskito and the <a href="https://www.npmjs.com/package/libphonenumber-js">libphonenumber-js</a> package to offer a convenient API. It provides you with the opportunity to create a masked text field for a phone number from any country in just a couple of lines.</p><p>In September 2023, the development of the package was completed, and we made its first release. The package turned out to be convenient and flexible to use. For example, a library user can regulate the size of the bundle by choosing different <a href="https://gitlab.com/catamphetamine/libphonenumber-js#min-vs-max-vs-mobile-vs-core">sets of metadata</a> (max, min, or mobile). You can read more about all the features of the new package in <a href="https://maskito.dev/addons/phone">our documentation</a>.</p><p>Very soon, in the Taiga UI 4.0.0 release, the new package @maskito/phone will be used inside InputPhoneInternational component, permanently eliminating the concerns about updating international phone numbers. Our users will simply need to update the version of the libphonenumber-js library.</p><h3>Native mobile controls</h3><p>Towards the end of 2022, we decided to implement the ability to use the native mobile controls in some of our input components. For instance, native dropdown lists or calendars.</p><p>The first such feature introduced for the <a href="https://taiga-ui.dev/components/select">Select</a> component in October 2022. And by 2023, this feature was released across a whole list of our components: <a href="https://taiga-ui.dev/components/multi-select#native-select">MultiSelect</a>, <a href="https://taiga-ui.dev/components/input-date#native-input-date">InputDate</a>, <a href="https://taiga-ui.dev/components/input-time#native">InputTime</a>, <a href="https://taiga-ui.dev/components/input-date-time#native">InputDateTime</a>, <a href="https://taiga-ui.dev/components/input-month#native">InputMonth</a>. The public API of these new features may differ among components, but the core principle remains the same: the desktop version of the component uses stylized Taiga controls, while the mobile version invokes the native system tools. Different mobile platforms will style the controls according to their own design.</p><p>To demonstrate what the new feature looks like in practice, take a look at this example. If you open it on a desktop, you’ll see that the InputDate component, when focused, opens up a stylized Taiga calendar.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9ShEaFPqXJQe636uXArHOQ.png" /></figure><p>However, if you open the same example on an actual mobile device, clicking on the calendar icon will bring up the device’s native calendar, styled according to the operating system of the device.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*iXjaMviOUVswmzlu8bJQAg.png" /><figcaption>IOS and Android devices</figcaption></figure><p>Native controls have a number of advantages that custom analogs simply can’t match, no matter the effort. For example, native calendars and dropdown lists can extend beyond the boundaries of the browser window, which can be quite useful on smaller mobile devices. Moreover, such native controls will automatically be styled according to different operating systems, which means they integrate harmoniously with the device’s overall design and look familiar to the user.</p><p>If we set aside all the technical advantages, there is another meaningful point regarding the significance of this feature. In product development, there’s a commonly used “hack” in which a business, as a stopgap measure, postpones the development of a full-fledged iOS or Android application for their product; instead, they leverage their existing web application version, packaging it inside a native mobile app shell. This article will not delve into the merits or drawbacks of WebView. It’s important to note that smart use of this approach requires that the web version of a site should effectively mimic a native mobile application in both behavior and appearance. The use of native mobile controls can be a good solution within such a strategy, even though the component may retain its unique behavior on desktop platforms.</p><p>The instructions for implementing the feature are divided into two groups:</p><ol><li>For text fields used for entering dates, times, and months, this is done through Dependency Injection configuration:</li></ol><pre>import {tuiInputDateOptionsProvider} from &#39;@taiga-ui/kit&#39;;<br>// ...<br>providers: [tuiInputDateOptionsProvider({nativePicker: true})]</pre><p>2. For components used for selecting items from a dropdown list, you simply need to place the native &lt;select /&gt; tag inside the already familiar &lt;tui‑select /&gt; or &lt;tui‑multi‑select /&gt; tags, and enhance it with the tuiSelect attribute. Following that, the codebase will automatically handle all the necessary magic.</p><pre>&lt;tui-select [formControl]=&quot;control&quot;&gt;<br>   Character<br>   &lt;select<br>       tuiSelect<br>       [items]=&quot;items&quot;<br>   &gt;&lt;/select&gt;<br>&lt;/tui-select&gt;</pre><h3>Editor: more features</h3><p>For an extended period, the Editor component was developed within a monorepository along with other Taiga UI packages and had only a single <a href="https://taiga-ui.dev/v2/components/editor-new">page of documentation</a>. But over time, this component began to feel cramped. Moreover, it no longer seemed right to call our vast wysiwyg just a “component.” The time has come for it to leave the parent nest and embark on a journey of its own…</p><p>Since June, the Editor has been residing in a separate repository, featuring its own distinct documentation and versioning system independent from the other Taiga packages. It is now officially known as the @tinkoff/tui‑editor package.</p><p>In 2023, our editor has been equipped with new and interesting features: it now supports native <a href="https://taiga-family.github.io/tui-editor/embed/html5#video-audio">audio/video tags</a>, and inside the editor you can embed a full <a href="https://taiga-family.github.io/tui-editor/embed/youtube">YouTube player</a> or even an <a href="https://taiga-family.github.io/tui-editor/embed/iframe">iframe</a> that can be adjusted in width with any content inside. And, of course, we don’t intend to stop here; we plan to continue enhancing the editor even further!</p><h3>More components and new @taiga-ui/experimental</h3><p>Until recently, in the development of Taiga UI, we aimed to focus on constructing atomic components, making a point of avoiding complex organisms. But in today’s world, the demand for web applications has skyrocketed, particularly for their mobile versions or even modern PWAs. Creating high-quality web applications rapidly has become extremely important. In light of these new trends, we have deviated from our previous rules and have begun to prepare ready-to-use popular mini “organisms” (or, as we call them within our team, “layout”-components).</p><p>This led to the creation of the <a href="https://taiga-ui.dev/navigation/tab-bar">TabBar</a> and <a href="https://taiga-ui.dev/navigation/app-bar">AppBar</a> components, which embody the popular design pattern used in mobile applications for navigation between sections.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hrhuyXP1vHc-OYrEnwxkmg.png" /></figure><p>We also developed the <a href="https://taiga-ui.dev/layout/block-status">BlockStatus</a> component, which simplifies the creation of status pages. For instance, it’s useful for developing the infamous 404 page, present in almost every application. In the autumn of 2023, the @taiga-ui/experimental package was enriched with a variety of other useful layout components: <a href="https://taiga-ui.dev/experimental/card">Card</a>, <a href="https://taiga-ui.dev/experimental/cell">Cell</a>, <a href="https://taiga-ui.dev/experimental/title">Title</a>, and <a href="https://taiga-ui.dev/experimental/surface">Surface</a>.</p><blockquote>The @taiga-ui/experimental package contains components that do not yet have a strictly defined public API; it can be changed, disregarding semantic versioning rules. In the upcoming major release, these components will be moved to stable packages, their public API will be finalized, and they will start to follow semantic versioning.</blockquote><p>The new layout components in @taiga-ui/experimental are not the sole innovations within this package. It contains a plethora of updated and refined versions of existing components, along with some completely new features.</p><p>For example, take the updated <a href="https://taiga-ui.dev/experimental/checkbox">Checkbox</a>, <a href="https://taiga-ui.dev/experimental/radio">Radio</a> and <a href="https://taiga-ui.dev/experimental/toggle">Toggle</a> components. The new versions have become <a href="https://angular.io/guide/accessibility#augmenting-native-elements">attribute components</a> and now can be directly used with native &lt;input /&gt; elements. In the next major release, the &lt;tui-checkbox /&gt; tag will be eliminated from the templates and will be replaced by &lt;input tuiCheckbox type=&quot;checkbox&quot; /&gt;. This means that the new component version will provide full access to all native input’s attributes, resulting in a lighter component and cleaner code structure. This achievement is thanks to the CSS <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/mask">mask</a> property! Additionally, the new versions of the components can be optionally styled with just a single line of code to visually resemble the native iOS or Android system controls.</p><p>The <a href="https://taiga-ui.dev/experimental/sensitive">Sensitive</a> component has been introduced, which hides part of the content from the user’s view. For instance, in Tinkoff applications, such a feature is actively used to allow users to conceal their sensitive data — like balance amounts or bank account information — when recording their screen or showing something personally to their friends. The secret to the component’s success lies once again in the use of a CSS mask.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/758/1*NwQ-xGB800D-126ZdPPGIw.gif" /></figure><p>Using an ellipsis at the end is a common method to indicate that text has been truncated. If you’re looking for an alternative, there’s a new option — <a href="https://taiga-ui.dev/experimental/fade">Fade</a> component. Guess how this effect is achieved? Correct, once again, the implementation relies on the familiar CSS mask!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Txd4Qaop8sc5jLITS0SKrA.gif" /></figure><p>Are you weary of the constant mentions of CSS masks? This is only the tip of the iceberg! In 2023, this feature has become one of the key contributors to the @taiga-ui/experimental package. With just this single property, we’ve managed to refresh the <a href="https://taiga-ui.dev/experimental/progress-segmented">ProgressSegmented</a> component’s version by applying a mask on top of the <a href="https://taiga-ui.dev/components/progress-bar">ProgressBar</a> component.</p><blockquote>Delving deeply into the technical details of implementing so many elements with the CSS mask requires a separate article. If our team has the strength and time, and the readers show interest, then we will certainly prepare it, including all the details!</blockquote><p>The CSS-mask has also been influential in the revamped version of the <a href="https://taiga-ui.dev/experimental/button">Button</a> component. A redesign of the architecture for the new buttons has streamlined the component’s codebase without compromising on its past functionality.</p><p>The latest addition, but certainly not the least important, is the new component for handling icons — <a href="https://taiga-ui.dev/experimental/icon">Icon</a>. It renders icons through a CSS mask, introducing new opportunities:</p><ul><li>The possibility to store icons on a CDN and display them without any DOM manipulation or the need for a sanitizer;</li><li>A more straightforward approach to icon scaling. Just merely change the dimensions of the host component — the icon automatically scales, and the use of the <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/vector-effect#non-scaling-stroke">vector‑effect=&quot;non‑scaling‑stroke&quot;</a> property allows for this scaling without compromising detail or causing unwanted changes in line thickness.</li><li>The browser automatically implements icon caching.</li></ul><p>All of these improvements allow us to “retire” a significant part of the codebase, making the work with Taiga UI much more straightforward.</p><h3>The upcoming release 4.0.0, Taiga Renaissance?</h3><p>It has been over a year since the last major release of Taiga UI. We have deliberately avoided updating the Angular version for an extended period to preserve <a href="https://angular.dev/tools/libraries/creating-libraries#ensuring-library-version-compatibility">compatibility</a> with your projects that rely on older versions of the framework. We refrained from making breaking changes for a long time, allowing you to seamlessly upgrade to the latest versions of our libraries. And for an extended period, we’ve continued to support some exceedingly outdated browsers.</p><p>Maintaining this approach is challenging and at times it can be counterproductive to the quality of our libraries. For this reason, the Taiga team is actively discussing the proposition of increasing the frequency of major releases (small ones every six months to a year, instead of massive releases every one and a half years) and the aspiration to match the support for the minimum version of Angular in our libraries to the same <a href="https://angular.dev/reference/releases#actively-supported-versions">cycles</a> promised by the framework’s core team.</p><p>As we look toward the beginning of 2024, a new fourth major release of Taiga is on the horizon. We plan to update the minimum supported browser versions, embracing more contemporary browser features, which in turn will make our libraries lighter and more reliable. We will upgrade to Angular version 15, which at last will grant us the opportunity to implement <a href="https://angular.dev/guide/components/importing#standalone-components">standalone components</a> and the <a href="https://angular.dev/guide/directives/directive-composition-api">Directive Composition API</a>. We will finalize the public API of our new experimental components and incorporate them into other stable packages.</p><p>You can start preparing for the forthcoming release right now: update Angular in your application to at least version 15+, and update Taiga to the latest release. Then, our schematics will handle a significant part of the concerns for you!</p><p>If all preparations are already complete, then it’s the perfect time to make yourself a cup of cocoa with marshmallows, sit in a comfy chair opposite a decorated Christmas tree, and fully immerse in the festive holiday spirit. <a href="https://github.com/taiga-family/taiga-ui">Taiga UI</a> team wishes everyone a Merry Christmas!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a3a1d0a3dd57" width="1" height="1" alt=""><hr><p><a href="https://medium.com/angularwave/taiga-ui-2023-results-a3a1d0a3dd57">Taiga UI: 2023 Results</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[Maskito is a new collection of libraries for text field masking]]></title>
            <link>https://medium.com/its-tinkoff/maskito-is-a-new-collection-of-libraries-for-text-field-masking-f64ec71951df?source=rss-8ad93c244ca3------2</link>
            <guid isPermaLink="false">https://medium.com/p/f64ec71951df</guid>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[angular]]></category>
            <category><![CDATA[front-end-development]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[typescript]]></category>
            <dc:creator><![CDATA[Nikita Barsukov]]></dc:creator>
            <pubDate>Thu, 22 Jun 2023 12:12:43 GMT</pubDate>
            <atom:updated>2023-09-25T12:44:12.704Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Tuo_umXaDITNlSefSfCdGA.png" /></figure><p>We are happy to announce that we have released our project <a href="https://github.com/taiga-family/maskito">Maskito</a> to Open Source. The first stable major version is now available. Maskito is a collection of libraries to simplify the process of masking text fields with a convenient and flexible public API.</p><p>Maskito comes with several libraries. The main one is a zero dependency Typescript package. It is all you need to create a mask for your web application. There is also an optional package with configurable, ready-to-use masks. And of course there are libraries for modern web frameworks: you can use Maskito in React, Angular or Vue. Let’s dive into the details.</p><h3>A bit of theory</h3><p>The terms “mask”, “input or text field masking”, and other similar words are mentioned many times in the article. Let’s discuss the meaning of this term for the Web.</p><p>If it had a formal definition, it would sound something like this:</p><blockquote>Mask is a programmatic constraint (defined by developer) which ensures that the user enters a value inside a text field according to predefined format.</blockquote><p>It is important to make a distinction between the terms “masking” and “validation”. Yes, both processes have a similar purpose. However, masking helps the user to enter a valid value, and validation only checks if the final value is correct (it only returns a boolean answer as a result).</p><p>If such a nerd definition still does not clarify things, then read my previous article. It has a more detailed explanation of masking.</p><p><a href="https://javascript.plainenglish.io/uphill-battle-to-create-a-mask-for-text-field-f1e385455dd5">Uphill Battle to Create a Mask for Text Field</a></p><p>To get more understanding of this concept, I also propose to look into some examples of masked text fields: for <a href="https://maskito.dev/kit/time">time</a>, <a href="https://maskito.dev/kit/date">date</a>, <a href="https://maskito.dev/kit/number">number</a>, <a href="https://maskito.dev/recipes/phone">phone</a> or <a href="https://maskito.dev/recipes/card">credit card</a>.</p><blockquote>In the next two sections I will write about the history of Maskito’s development and explain the reasons for some of our architectural decisions. If you are not interested in these topics and are looking forward to seeing Maskito in action, please skip to the <a href="#6803">“Anatomy of Maskito”</a> section.</blockquote><h3>A bit of history</h3><p>All the necessary theoretical concepts have been discussed, and now I’m ready to explain why it was necessary to create a new library. Some readers may notice that some similar solutions are already available in open source.</p><p>I feel it necessary to have a disclaimer. There are no perfect libraries, frameworks, or languages. There is no silver bullet for all tasks. You decide which tool is better for your new case. Similarly with masking libraries — there are several popular libraries, but unfortunately not all of them were perfectly suited for our tasks.</p><p>My team is developing the design system of fintech company Tinkoff. We are responsible for maintaining the Angular UI Kit, a collection of libraries with a set of components. It is an Open Source project called <a href="https://github.com/taiga-family/taiga-ui">Taiga UI</a>.</p><p>Among our components there are a lot of masked text fields: for <a href="https://taiga-ui.dev/components/input-phone">phone</a>, for <a href="https://taiga-ui.dev/components/input-date">dates</a>, for <a href="https://taiga-ui.dev/components/input-time">time</a>, and even a complex component to enter <a href="https://taiga-ui.dev/components/input-card-grouped">credit card</a>. I have mentioned only the most popular components, our UI Kit includes significantly more examples of masked text fields.</p><p>The <a href="https://github.com/text-mask/text-mask">text-mask</a> library has historically been used for all our masked components. It provides a good public API, flexible enough to fit our requirements. However, if the library had no drawbacks, then you would not be reading this article now, because we would continue to use text-mask. But here we are.</p><p>Unfortunately, the library support gradually faded away, bugs were fixed less and less intensively. There are still unresolved issues in the project repository (for example, <a href="https://github.com/text-mask/text-mask/issues/657">#657</a> and <a href="https://github.com/text-mask/text-mask/issues/830">#830</a>), discovered more than five years ago by our own colleagues, who at that moment were already developing Taiga UI.</p><p>Long-lived bugs are not the only problem. The codebase becomes less up to date with modern standards every day. And the most tragic event happened in 2020 — author of this project <a href="https://github.com/text-mask/text-mask/blob/master/README.md">announced</a> that the library was no longer maintained.</p><p>For these reasons, the goal of finding an alternative solution for text field masking was given high priority in our project:</p><ol><li>The above-mentioned long-lived bugs remained unresolved.</li><li>The library became the only dependency outsider in our project: it was published using the legacy module systems. In addition, its Angular package was released under the legacy “ViewEngine” (instead of the modern “Ivy” engine). All of this causes build time warnings, and sooner or later this could become a serious problem.</li></ol><p>We started looking into other popular masking solutions — <a href="https://github.com/uNmAnNeR/imaskjs">imaskjs</a>, <a href="https://github.com/nosir/cleave.js">cleave.js</a>, <a href="https://github.com/JsDaddy/ngx-mask">ngx-mask</a> and <a href="https://github.com/RobinHerbots/Inputmask">InputMask</a>. The main advantage of all these solutions is simplicity to use. If you need to create some kind of classic mask that is not overcomplicated with additional logic, then they solve the task well.</p><p>But problems occur when you need to create a more complex solution with its own special behavior. The libraries didn’t provide the same flexible public API as it was in our previous library <a href="https://github.com/text-mask/text-mask">text-mask</a>. Moreover, they don’t have detailed documentation, and an in-depth understanding of the library is possible only through exploring the outdated source code.</p><p>We’ve communicated with other developers who used the above-mentioned libraries in their projects. They claimed that they had faced SSR or Shadow DOM errors, caret jumping issues and so on. In general, as I said before, there are no perfect solutions, different tasks require different tools.</p><p>Finally, the history of the <a href="https://github.com/text-mask/text-mask">text-mask</a> library shows that even a popular library can be retired if it is supported only by a few maintainers. Long-lived library should be backed by a huge team or even an entire organization that will always be interested in its further development.</p><p>If the library is maintained for the needs of the company, you can rely on it, because even if the existing maintainers decide to change the project or quit their jobs, the company will simply replace them with other employees since it is important for them to support the development of the library for their own needs.</p><p>All of our subsequent research and team discussions led to the decision that we should create our own library for masking that would satisfy all our needs.</p><h3>How it was developed</h3><p>Before starting the development, we defined the main tasks we wanted to achieve:</p><ol><li>The mask should support all user interactions with text fields: basic typing and deleting using the keyboard, pasting, dropping text in with the pointer, browser autofill, predictive text from mobile native keyboard.</li><li>Server-side rendering support.</li><li>The mask can be used not only with HTMLInputElement but also with HTMLTextAreaElement.</li><li>Our new project should consist of several libraries and the main one should be framework independent. For popular web frameworks, we should publish optional tiny packages.</li></ol><p>The first task was done with the help of modern browser capabilities. We used the beforeinput and input events to control all the necessary cases.</p><p>The second task about SSR was solved in the following way: all our Cypress tests are run on an SSR application. If an error is caught during server-side rendering, the application stops serving and all tests start failing immediately. This approach does not allow us to catch all bugs, but several times this strategy has helped catch SSR issues before they were released.</p><p>Our <a href="https://github.com/taiga-family/maskito">Maskito</a> library is ready to use. It is published to npm and can be used in your projects. For example, it is already actively used in the popular <a href="https://github.com/taiga-family/taiga-ui">Taiga UI</a> project (all its masked text fields were developed using Maskito) and is endorsed as the <a href="https://ionicframework.com/docs/api/input#input-masking">recommended masking solution</a> by Ionic Framework.</p><h3>Anatomy of Maskito</h3><p>Maskito is a collection of libraries. The main one @maskito/core is a lightweight 3kb package with no external dependencies. The core library is sufficient to mask the input in a simple vanilla javascript application.</p><p>There is also an optional framework-agnostic package @maskito/kit. It contains a set of configurable, ready-to-use masks.</p><p>For modern JavaScript frameworks, we have released small packages: for React, Angular and Vue. They are called @maskito/react, @maskito/angular and @maskito/vue respectively. They provide a convenient way to use Maskito in the style of those frameworks.</p><h3>Maskito in action</h3><p>Now let’s explore the basic concepts of Maskito. Look into an oversimplified piece of code:</p><pre>import {Maskito, MaskitoOptions} from &#39;@maskito/core&#39;;<br><br>const element: HTMLInputElement = document.querySelector(&#39;input&#39;)!;<br><br>const options: MaskitoOptions = {<br>    mask: new RegExp(&#39;...&#39;),<br>    preprocessors: [<br>        ({elementState, data}) =&gt; {<br>            return {elementState, data};<br>        },<br>    ],<br>    postprocessors: [<br>        ({value, selection}) =&gt; {<br>            return {value, selection};<br>        },<br>    ],<br>};<br><br>const maskedInput = new Maskito(element, options);<br><br>// Call it when the element is destroyed<br>maskedInput.destroy();</pre><p>The main entity is the Maskito class, which is initialized with two arguments. The first is a reference to a native &lt;input /&gt; or &lt;textarea /&gt; element, and the second argument is the mask configuration.</p><p>After the class is initialized, native event listeners are enabled to control all user interaction with text boxes. The only thing the developer should care about is the need to clean up all listeners by calling the only public method destroy() of the class instance after the masked element is detached from the DOM.</p><blockquote>You don’t need to worry about clean-ups if you use @maskito/react, @maskito/angular or @maskito/vue packages.</blockquote><p>Let’s have a look at the configuration of the mask. In the code block above it is an object that implements the MaskitoOptions interface and is passed as the second argument to the Maskito class. Let’s learn the full power of mask configuration through an example. We will write a simple number input mask and iteratively improve it to demonstrate the power of Maskito.</p><p>The only required property is mask. It’s an expression that specifies the pattern which the final value of the text field should fit after all checks. It can be a classic <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions">regular expression</a>, or it can be an array of mini-regular expressions. The last option is more complex, needed for masks with a fixed number of characters.</p><p>In the article, we will skip a more complex option of mask property. It is well described in the <a href="https://maskito.dev/core-concepts/mask-expression">documentation</a>, we will propose it as additional reading. For our task, an option with a simple regular expression is enough. The first version of mask for entering numbers is the following:</p><pre>const maskitoOptions: MaskitoOptions = {<br>   mask: /^\d+(,\d*)?$/,<br>};</pre><p>We’ve created a regular expression that specifies a pattern for entering a number with an optional fractional part that uses a comma as a separator.</p><p>Let’s complicate the task. Some users commonly use a comma as a decimal separator, while others might argue that the point is the more commonly used separator.</p><p>If we try to enter a point in the current version of the form, the form will reject it. This is unacceptable if we are trying to get the perfect UX. Of course, you can extend the regular expression to allow the decimal point, and let the user decide which separator to use. Let’s imagine that, according to our design system, the text field should only contain a comma. If a user tries to enter a point, it should be automatically replaced by a comma.</p><p>For this case we can use an optional field from the MaskitoOptions interface — preprocessors (array of preprocessors). The preprocessor allows the developer to add custom value mutations before the mask starts its work. After all preprocessors have finished their work, the new value is passed to the mask.</p><p>The preprocessor is a pure function. The first argument is an object containing the current state of the element (the elementState property): the value of the text field and the start/end positions of the text selection. Also, the first argument contains the data property with value from the <a href="https://developer.mozilla.org/en-US/docs/Web/API/InputEvent/data">same property</a> of the native event that was fired after the user’s interaction with the text field (for example, if the user types from the keyboard, data will contain the new character typed). And the preprocessor expects an object with the same interface as the return value. The developer can change all these values or leave them the same. We can implement our task by replacing a point with a comma as follows:</p><pre>import {MaskitoOptions} from &#39;@maskito/core&#39;;<br><br>const maskitoOptions: MaskitoOptions = {<br>    mask: /^\d+(,\d*)?$/,<br>    // 0.42 =&gt; 0,42<br>    preprocessors: [<br>        ({elementState, data}) =&gt; {<br>            const {value, selection} = elementState;<br><br>            return {<br>                elementState: {<br>                    selection,<br>                    value: value.replace(&#39;.&#39;, &#39;,&#39;),<br>                },<br>                data: data.replace(&#39;.&#39;, &#39;,&#39;),<br>            };<br>        },<br>    ],<br>};</pre><blockquote>Note that the point is not only replaced inside the data property, but also inside the value property! This is explained by the fact that while mutating the data property is sufficient for most cases, there is only one rare case where an invalid dot can be inside the value as well. This is browser autofill. Modern browsers do not fire a beforeinput event for this, and only a single input event is fired after browser autofill.</blockquote><p>Let’s make one last improvement to our mask for entering numbers and add the following behavior: if the user tries to insert a number with a lot of leading zeros at the beginning of the integer part, then discard the extra ones. For example, if a user enters the string 000.42, the value of the text field should become 0.42.</p><p>There is another optional property inside the MaskitoOptions interface that is perfect for our new goal. It is postprocessors (array of postprocessors). Similar to its preprocessor counterpart, a postprocessor is a pure function to modify the value of a text field to implement its own special logic. However, it is called after the mask has finished its work: after mask discards all invalid characters and ensures that the value of the text field matches the mask property.</p><p>The first argument of the postprocessor is the state of the element: the new value of the text field and the new positions of the text selection (after all validations and calibrations of the mask). As a return value, the postprocessor expects an object with the same interface as it received from the first argument, but allows to change the value of any of its properties. And the new version of the mask configuration looks like this:</p><pre>import {MaskitoOptions} from &#39;@maskito/core&#39;;<br><br>const maskitoOptions: MaskitoOptions = {<br>    mask: /^\d+(,\d*)?$/,<br>    preprocessors: [<br>        ({elementState, data}) =&gt; {<br>            const {value, selection} = elementState;<br><br>            return {<br>                elementState: {<br>                    selection,<br>                    value: value.replace(&#39;.&#39;, &#39;,&#39;),<br>                },<br>                data: data.replace(&#39;.&#39;, &#39;,&#39;),<br>            };<br>        },<br>    ],<br>    // 000000.42 =&gt; 0.42<br>    postprocessors: [<br>        ({value, selection}) =&gt; {<br>            const [from, to] = selection;<br>            const newValue = value.replace(/^0+/, &#39;0&#39;);<br>            const deletedChars = value.length - newValue.length;<br><br>            return {<br>                value: newValue,<br>                selection: [from - deletedChars, to - deletedChars],<br>            };<br>        },<br>    ],<br>};</pre><p>When using postprocessors, remember that this is the final step in validating and calibrating the value of the text field. You can make any changes you want, but at the end you must make sure that the final state of the element contains a valid value.</p><blockquote>The postprocessor gives you a lot of flexibility, but as Uncle Ben said: “With great power comes great responsibility.”</blockquote><p>In this article we have learned how to create a simple mask for entering numbers and we have become familiar with the basic concepts of Maskito! The final version of the example we’ve created can be further explored in the StackBlitz example:</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fstackblitz.com%2Fedit%2Fmaskito-article-demo%3Fembed%3D1%26file%3Dindex.ts&amp;display_name=StackBlitz&amp;url=https%3A%2F%2Fstackblitz.com%2Fedit%2Fmaskito-article-demo%3Ffile%3Dindex.ts&amp;image=https%3A%2F%2Fsocial-img.staticblitz.com%2Fprojects%2Fmaskito-article-demo%2F76db44108d3975bddd20c9aa683d2e23&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=stackblitz" width="745" height="400" frameborder="0" scrolling="no"><a href="https://medium.com/media/71cba1a0115dc4d1cb180609c833bf49/href">https://medium.com/media/71cba1a0115dc4d1cb180609c833bf49/href</a></iframe><h3>Wrapping up</h3><p>I have described only the core concepts of Maskito. But this is not all it is capable of. Maskito can do even more — you can read about it in the <a href="https://maskito.dev">documentation</a>.</p><p>If you like our new project, then <a href="https://github.com/taiga-family/maskito">star it on Github</a>. And we always welcome your feedback! If you encounter any problems, then <a href="https://github.com/taiga-family/maskito/issues/new/choose">create an issue </a>— we will do everything to fix it!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f64ec71951df" width="1" height="1" alt=""><hr><p><a href="https://medium.com/its-tinkoff/maskito-is-a-new-collection-of-libraries-for-text-field-masking-f64ec71951df">Maskito is a new collection of libraries for text field masking</a> was originally published in <a href="https://medium.com/its-tinkoff">IT-компании Тинькофф</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Uphill Battle to Create a Mask for Text Field]]></title>
            <link>https://javascript.plainenglish.io/uphill-battle-to-create-a-mask-for-text-field-f1e385455dd5?source=rss-8ad93c244ca3------2</link>
            <guid isPermaLink="false">https://medium.com/p/f1e385455dd5</guid>
            <category><![CDATA[html]]></category>
            <category><![CDATA[front-end-development]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[javascript]]></category>
            <dc:creator><![CDATA[Nikita Barsukov]]></dc:creator>
            <pubDate>Fri, 14 Apr 2023 08:16:48 GMT</pubDate>
            <atom:updated>2023-09-04T17:24:52.214Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KmQxVjaYqqS9J4_YKkErsA.png" /></figure><p>An old proverb says that everyone makes mistakes. It was relevant before and still relevant nowadays, people make them every day, and even a fluent filling out the forms cannot be completed without typos.</p><p>Good UI / UX helps the user to avoid mistakes. There are many approaches to validate a user’s input, but today I will tell you about creating a mask for the text field using JavaScript.</p><h3><strong>What is the mask?</strong></h3><p>Let us imagine that our web application has a text field for price. We want to allow the users to enter a number and reject any letters and other not-digit characters. Experienced readers can claim that HTML already offers &lt;input type=&quot;number&quot; /&gt; for this goal. Let’s open our favorite browser Chrome and visit the <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/number">documentation page</a> of the this element. Any attempt to enter something except digits or dot/comma fails, and the input value does not change. The browser prevents any invalid key typing for this input type. It seems that we have already found a built-in solution, and it is time to finish this article.</p><p>But wait a minute! Let us open Firefox and repeat all actions. Unfortunately, this browser is not so strict and allows you to enter some invalid characters. It means that this input type does not work in Firefox, does it? Actually, no, it works, but … It warns users only on form submit via dropdown with a validation error.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/740/1*OOP23QYwEhyE5yMHhe51-g.png" /><figcaption>Open this <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/number#examples">example</a> in Firefox</figcaption></figure><p>Somebody can say that it looks like a bad joke by Firefox and claim that it is not effective and too late for good UX! And, of course, there are people who disagree with it. How many people — so many opinions. In this article, we will not decide who is right. Firefox demonstrates its own way of warning users about an invalid input, and Chrome prefers another approach — no worries, it will not be one of the Chrome vs. Firefox articles. The only thing that matters for this article is that Chrome demonstrates an example of <strong>input masking</strong>.</p><blockquote><strong>Mask </strong>is a programmatic constraint (defined by developer) which ensures that user enters value according to predefined format.</blockquote><p>The previous example is just one kind of mask. There are an infinite number of different masks (even more complex!): <a href="https://maskito.dev/kit/time">time</a>, <a href="https://maskito.dev/kit/date">date</a>, <a href="https://maskito.dev/recipes/phone">phone</a>, etc.</p><p>Rejection of invalid characters is not the only ability of a mask. It can also help the user to automatically insert some required characters. For example, it can add spaces between thousandths of a number (the number 10000 is more readable in this way, 10 000, isn’t it?) or automatically insert separators between a day/month/year in a date.</p><p>The mask can even guess the user’s intentions. For example, people from different countries can use different symbols as decimal separators: dot or comma. Let’s make an assumption that the dot is “your case”. A good mask inserts a “valid” decimal separator (dot) even if the user presses a key with an “invalid” one (comma).</p><blockquote><strong>Input masking</strong> is more about UX enhancement than data validation. Even an inexperienced hacker is capable of bypassing any frontend web application. Therefore, additional final data validation on the backend is always required!</blockquote><h3><strong>Mask ingredients</strong></h3><p>We need to dive into the list of events that can be emitted by &lt;input /&gt; or &lt;textarea /&gt; to figure out how to control what the user inputs into the text field. Let’s discuss the main ones:</p><p><a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/keydown_event"><strong>Keydown</strong></a> event is emitted every time any key is pressed on the keyboard. It contains a useful key property that stores information about the entered value. And most importantly, the event can be canceled via event.preventDefault!</p><p>It seems that such an event is perfectly suitable for input masking and completely satisfies all necessary requirements. But there are two drawbacks:</p><ul><li>System keys and combinations also trigger keydown events. It causes a number of issues for our task. For example, the user will copy the value of the input via Ctrl + C, and our mask gets a “false positive” signal. It takes a lot of effort to filter out the right events.</li><li>The event cannot control the browser autofill and the selection of the suggested value from the native keyboard of the mobile device.</li></ul><p>Moreover, if we use the keydown event for our task, we also have to listen to paste and drop events, which we will discuss a little later.</p><p><a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/keypress_event"><strong>Keypress</strong></a> event is almost identical to the keydown event, but with one nice exception: it is fired only when a key that produces a character value is pressed down. keypress isn’t emitted by system keys that we don’t need and completely solves the first drawback of the keydown event.</p><p>But this event has another troublesome nuance. Official documentation page claims that this event is deprecated and is no longer supported by browsers. Yes, of course, you can open any modern browser and notice that this event is still running and works as intended, but it may cease to work at any time. Therefore, this event is no longer considered for input masking in this article.</p><p><a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/paste_event"><strong>Paste</strong></a> and <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/drop_event"><strong>Drop</strong></a> are important events for input masking, which are often unjustly neglected. The user can change the value of the text field not only by pressing keys from the keyboard but also by pasting from the clipboard and dropping the text into input by the cursor. Therefore paste and drop must be used if input masking is controlled via listening to the keydown event.</p><p><a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/change_event"><strong>Change</strong></a> event is fired when the user modifies the element’s value. However, &lt;input /&gt; and &lt;textarea /&gt; elements emit this event <strong>not</strong> for each alteration to an element’s value. They wait until the text field loses focus after its value is changed. Let’s imagine that the user focuses on the input and tries to type the word “hello” — the text field will fire five keydown events and only one change (after blur). Despite the promising name and good browser support, this event is not suitable for our task.</p><p><a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/input_event"><strong>Input</strong></a> event solves many of the issues of the previously mentioned “colleagues”. Here is a short list of event pros:</p><ul><li>The event is fired after each alteration to an element’s value, does not wait for the loss of focus (like a change event), and does not require other conditions.</li><li>Pressing the system keys will not fire an event if they do not cause a change in the element’s value.</li><li>It is watching all possible alterations to the text field: the event will be emitted on pasting text from the clipboard, on browser autofill, on dropping text into the input with the cursor, and even on selecting hints from the native mobile keyboard.</li><li>Good browser support.</li></ul><p>Unfortunately, there is a significant limitation to this event. It cannot be canceled via the preventDefault method, because the event notifies about a fact that has already happened. And the past cannot be changed, unless you have a vintage DeLorean!</p><p>Of course, we can save the element’s value and the position of its caret before each change. Of course, the saved data can be used to programmatically update the text field with the old value (to imitate the cancellation of the event). But this workaround can generate hard-to-maintain code.</p><p>There are many libraries on the Internet that make it easy to mask text fields. Most popular “mature” solutions use combinations of the described browser events with all the advantages and disadvantages.</p><p>But what if there was a need to create a new library in 2023… Would it repeat the experience of its predecessors? There is a trick up our sleeve that we have not discussed yet — the beforeinput event.</p><h3><strong>Modern mask recipe</strong></h3><p><a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/beforeinput_event"><strong>Beforeinput</strong></a> is a “young” event that is perfectly suitable for textfield’s masking. In March 2017, it was presented to us by … Safari. Yes, this browser can not only bring tears to the faces of front-end developers, but sometimes be the first who delivers new features.</p><p>Chrome was the next to implement this feature, and other browsers later picked it up. The lagging big player was Firefox, which only provided support for the event in 2021. At the time of this article’s writing, beforeinput has good modern browser support and works for <a href="https://caniuse.com/?search=beforeinput">94.59%</a> of global clients.</p><p>Beforeinput event has a lot of advantages for our task:</p><ul><li>It fires only when you press keys that cause alteration to an element’s value.</li><li>It supports all other ways to change the input besides keyboard interaction. The event has an inputType property, which can be one of the following values: insertText, insertFromDrop, insertFromPaste, deleteContentBackward, deleteContentForward, etc.</li><li>It can be canceled via preventDefault.</li></ul><p>It seems that the beforeinput event took all the best from the predecessors!</p><p>We’ve got a modern recipe for text field’s masking. Most of the value validation can be done in the beforeinput event, and in the input event you can make minor calibrations of the element’s value.</p><pre>element.addEventListener(&#39;beforeinput&#39;, event =&gt; {<br>   switch (event.inputType) {<br>       case &#39;deleteContentBackward&#39;:<br>       case &#39;deleteContentForward&#39;:<br>       case &#39;deleteByCut&#39;:<br>           return handleDelete(event);<br>       case &#39;insertLineBreak&#39;:<br>           return handleEnter(event);<br>       case &#39;insertFromPaste&#39;:<br>       case &#39;insertText&#39;:<br>           return handleInsert(event);<br>       // ...<br>       // Many other cases<br>       // ...<br>   }<br>});</pre><p>Let me emphasize an important thing. If you need to cancel the beforeinput-event (for example, to programmatically update the input with the desired value), then the subsequent input-event will also be canceled after it. This behavior is logically expected.</p><p>Sometimes we want to report what happened to external observers. A good example is the Angular framework — it has its own form tools that rely on the fact that an input event will fire every time the value of the input changes. The issue has many solutions, one of them being to execute element.dispatchEvent(new InputEvent(…)) after canceling the beforeinput event and then programmatically updating the text field.</p><h3><strong>Maskito libraries</strong></h3><p>We applied the revealed formula for creating masks in our new <a href="https://github.com/taiga-family/maskito">Maskito</a> project. It is a collection of libraries written in Typescript. The main @maskito/core library is built without any external dependencies, which allows it to be used in any vanilla JavaScript project.</p><p>Maskito has a library @maskito/kit — a set of ready-made configurable masks. We also created a separate optional package, @maskito/angular, in case you want to use development in your Angular project. Read more about all the features of Maskito in the <a href="https://maskito.dev">documentation</a>. And in the next article I will describe our new libraries in more detail, and we will analyze some real code.</p><p><a href="https://github.com/taiga-family/maskito">GitHub - taiga-family/maskito: Collection of libraries to create an input mask which ensures that user types value according to predefined format.</a></p><p>Maskito is already published to npm with zero-major versions and is ready to use. We are conducting final tests and fixing bugs in order to publish the first major version very soon. Keep the library in your notes! We hope you will find it useful in your next project!</p><p><em>More content at </em><a href="https://plainenglish.io/"><strong><em>PlainEnglish.io</em></strong></a><em>.</em></p><p><em>Sign up for our </em><a href="http://newsletter.plainenglish.io/"><strong><em>free weekly newsletter</em></strong></a><em>. Follow us on </em><a href="https://twitter.com/inPlainEngHQ"><strong><em>Twitter</em></strong></a>, <a href="https://www.linkedin.com/company/inplainenglish/"><strong><em>LinkedIn</em></strong></a><em>, </em><a href="https://www.youtube.com/channel/UCtipWUghju290NWcn8jhyAw"><strong><em>YouTube</em></strong></a><em>, and </em><a href="https://discord.gg/GtDtUAvyhW"><strong><em>Discord</em></strong></a><strong><em>.</em></strong></p><p><strong><em>Interested in scaling your software startup</em></strong><em>? Check out </em><a href="https://circuit.ooo?utm=publication-post-cta"><strong><em>Circuit</em></strong></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f1e385455dd5" width="1" height="1" alt=""><hr><p><a href="https://javascript.plainenglish.io/uphill-battle-to-create-a-mask-for-text-field-f1e385455dd5">Uphill Battle to Create a Mask for Text Field</a> was originally published in <a href="https://javascript.plainenglish.io">JavaScript in Plain English</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to Build Semantic Progress Indicators in Angular]]></title>
            <link>https://medium.com/bitsrc/no-div-arounds-writing-semantic-progress-indicators-in-angular-fe80512947d9?source=rss-8ad93c244ca3------2</link>
            <guid isPermaLink="false">https://medium.com/p/fe80512947d9</guid>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[html]]></category>
            <category><![CDATA[angular]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[front-end-development]]></category>
            <dc:creator><![CDATA[Nikita Barsukov]]></dc:creator>
            <pubDate>Fri, 17 Mar 2023 07:16:45 GMT</pubDate>
            <atom:updated>2023-03-17T07:16:45.994Z</atom:updated>
            <content:encoded><![CDATA[<h3>No div Arounds: Writing Semantic Progress Indicators in Angular</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GalKEoFz2BjmiAqBVBMs2g.png" /></figure><p>Developing a progress indicator is one of the simplest tasks for the frontend-developer. All you need is basic knowledge of HTML and CSS. JavaScript is used just to calculate the percentage of task completion.</p><p>However, this simplicity is deceiving. The Internet is teeming with a plethora of community solutions proposing to build the progress indicator with nested div-containers and CSS to spice things up. Steer clear of these solutions! Those are almost a crime against humanity since they worsen template semantics and violate accessibility.</p><p>In this article, I will show how in <a href="https://github.com/Tinkoff/taiga-ui">Taiga UI</a> we developed Angular components: <a href="https://taiga-ui.dev/components/progress-bar">ProgressBar</a> and <a href="https://taiga-ui.dev/components/progress-circle">ProgressCircle</a>.</p><h3>Developing ProgressBar</h3><h4><strong>Task formulation</strong></h4><p>There is a <a href="https://developer.mozilla.org/ru/docs/Web/HTML/Element/progress">built-in HTML tag &lt;progress /&gt;.</a> MDN claims:</p><blockquote>The &lt;progress&gt; HTML element displays an indicator showing the completion progress of a task, typically displayed as a progress bar.</blockquote><p>The important thing is that each browser differently renders this HTML tag. We require such a progress indicator which looks the same way on all browsers.</p><h4><strong>Wrong solution / De/IVious solution</strong></h4><p>The first idea which springs to one’s mind might be the superficial one: “Let us not use the built-in HTML tag at all. It is not fancy and looks different in each browser”. This idea cultivates another wrong solution within the community. The proposal revolves around creating two &lt;div/&gt;-containers: the first container is the progress tracker, and the second one is the progress indicator. The following step is to customize the containers and change the width of the progress indicator via JavaScript (from 0 to 100%).</p><p>The simplified version of this wrong solution is the following. The HTML file contains the two mentioned containers:</p><pre>&lt;div class=&quot;track&quot;&gt;<br>  &lt;div class=&quot;indicator&quot;&gt;&lt;/div&gt;<br>&lt;/div&gt;</pre><p>The CSS-file:</p><pre>.track {<br>  position: relative;<br>  background-color: grey;<br>    <br>  width: 300px;<br>  height: 20px;<br>}<br><br>.indicator {<br>  position: absolute;<br>  top: 0;<br>  left: 0;<br>  height: 100%;<br>  width: 50%; /* change it via JS */<br>  background-color: yellow;<br>}</pre><p>It does work visually. But that is only visually. However, what if a visually impaired user (e.g., cannot distinguish colors or is blind) decides to visit the website and doesn’t see the progress indicator?</p><p>In such cases, people use screen readers. These programs read out loud everything important that is happening on the screen to the user. Nevertheless, in the case of the ‘div’ solution the screen reader will not be able to comprehend this type of progress indicator. It will “see” only two &lt;div /&gt;-containers filled in with different colors and will not pronounce it to the user.</p><p>This proposed solution not only worsens HTML semantics but also violates the accessibility of our web application. Of course, we can help the screen reader detect the progress indicator: we can set <a href="https://www.w3.org/TR/wai-aria-1.1/#progressbar">role=”progressbar”</a> and also add<a href="https://www.w3.org/TR/wai-aria-1.1/#aria-valuenow"> aria-valuenow</a>, <a href="https://www.w3.org/TR/wai-aria-1.1/#aria-valuemax">aria-valuemax</a> and<a href="https://www.w3.org/TR/wai-aria-1.1/#aria-valuemin"> aria-valuemin</a> on the outer container. However, there is no need to reinvent the wheel and overcomplicate basic notions!</p><h4><strong>Improving DIV solution</strong></h4><p>There is a popular approach which can correct the accessibility of the previous solution. We can put visually-hidden native &lt;progress /&gt; HTML tag inside our custom progress component. Then we should pass attributes value and max to it and leave everything else as it was in the previous solution.</p><p>There are several approaches to visually hiding an HTML container but leaving it detectable for screen readers. Usually, it is sr-only CSS-class. For example, popular CSS frameworks such as <a href="https://getbootstrap.com/docs/4.0/utilities/screenreaders/">Bootstrap</a> and <a href="https://tailwindcss.com/docs/screen-readers">Tailwind CSS</a> have such classes.</p><blockquote><em>Warning</em>! Do not use display: none, height: 0 and width: 0. They hide content not only visually but also for screen readers. Read more about this in the article “<a href="https://webaim.org/techniques/css/invisiblecontent/">CSS in Action: Invisible Content Just for Screen Reader Users</a>”.</blockquote><p>After it, the progress indicator is not only visually fancy but also does not violate the accessibility principle. For example, the built-in Mac OS screen reader pronounces it as «n-percentage progress indicator» (where n — 100 × value / max).</p><p>Despite the progress, we are still not content with the template’s semantics! Moreover, we should create many @Input()-properties in our component to pass all required attributes to injected native &lt;progress /&gt;-tag without any mutations. For now, it is only value and max, and who knows, maybe tomorrow we will need to add id or one of data-* attributes. As a result, our component carries attributes, which are of no use!</p><h4>Our solution with Angular attribute component</h4><p>We propose a solution which requires no extra HTML tags. All we need to do is to extend the native &lt;progress /&gt;-element. Whenever we extend any native HTML element, the <a href="https://angular.io/guide/accessibility#augmenting-native-elements">official Angular documentation</a> recommends creating a component that uses an attribute selector with this element. This practice is actively used in our UI-Kit library Taiga: for example, <a href="https://github.com/Tinkoff/taiga-ui/blob/main/projects/core/components/button/button.component.ts#L33">button</a>, <a href="https://github.com/Tinkoff/taiga-ui/blob/main/projects/core/components/link/link.component.ts#L28">link</a> and <a href="https://github.com/Tinkoff/taiga-ui/blob/main/projects/core/components/label/label.component.ts#L18">label</a> components.</p><p>Let’s create a TypeScript file with our attribute component:</p><pre>import {<br>  ChangeDetectionStrategy,<br>  Component,<br>  HostBinding,<br>  Input<br>} from &#39;@angular/core&#39;;<br><br>@Component({<br>  selector: &#39;progress[tuiProgressBar]&#39;,<br>  template: &#39;&#39;,<br>  styleUrls: [&#39;./progress-bar.component.less&#39;],<br>  changeDetection: ChangeDetectionStrategy.OnPush,<br>})<br>export class TuiProgressBarComponent {<br>  @Input()<br>  @HostBinding(&#39;style.--tui-progress-color&#39;)<br>  color?: string;<br>}</pre><p>The Less file includes some <a href="https://lesscss.org/features/#mixins-feature">less-mixins.</a></p><p>The following one clears all built-in customization from the browser:</p><pre>.clearProgress() {<br>    -webkit-appearance: none;<br>    -moz-appearance: none;<br>    appearance: none;<br>    border: none;<br>}</pre><p>And this one helps to customize the progress tracker:</p><pre>.progressTrack(@property, @value) {<br>    @{property}: @value; // Edge | Mozilla<br><br>    &amp;::-webkit-progress-bar {<br>        @{property}: @value; // Chrome | Opera | Safari<br>    }<br>}</pre><p>The last mixin helps to customize the color of the progress indicator:</p><pre>.progressIndicatorColor(@color) {<br>    color: @color; // Not Chromium Edge<br><br>    &amp;::-webkit-progress-value {<br>        background: @color; // Chromium Edge | Chrome | Opera | Safari<br>    }<br><br>    &amp;::-moz-progress-bar {<br>        background: @color; // Mozilla<br>    }<br>}</pre><p>Finally, apply all created mixins to :host-element of our component (reminder: it is a native &lt;progress /&gt; on which our attribute component was applied).</p><pre>:host {<br>    .clearProgress();<br>    .progressIndicatorColor(var(--tui-progress-color, currentColor));<br>    .progressTrack(background-color, grey);<br><br>    color: yellow;<br>}</pre><p>That’s all! We developed a nice Angular attribute component which is wrapped around a native &lt;progress /&gt; element. The color of this indicator can be set via the CSS color-property, or via the input property of the component (for example, if we want to create a complex gradient color). In the code, the component declares in the following way:</p><pre>&lt;progress tuiProgressBar value=&quot;60&quot; max=&quot;100&quot;&gt;&lt;/progress&gt;</pre><p>You can <a href="https://taiga-ui.dev/components/progress-bar">see the component in action</a> on the showcase of our project Taiga UI. The final source code is available <a href="https://github.com/Tinkoff/taiga-ui/tree/main/projects/kit/components/progress/progress-bar">on GitHub</a>.</p><h3>Developing ProgressCircle</h3><p>Unfortunately, the creation of the <a href="https://taiga-ui.dev/components/progress-circle">ProgressCircle</a> component from only native &lt;progress /&gt;-element is not possible. We need some extra template tags.</p><p>And here the state of things is the same, curious Internet users might stumble upon various frivolous solutions which propose to create a circular progress indicator from div-containers which have been rounded using border-radius: 50%. Also, such solutions use a significant amount of JavaScript.</p><p>Yet, since we are in the ‘no-div club’, this task can be resolved without appeals to div-s. We will use svg-tag &lt;circle /&gt;. Moreover, we will use as little JavaScript as possible. Less preprocessor and CSS variables help us with it.</p><p>Let’s create a TypeScript file of our future component:</p><pre>import {<br>    ChangeDetectionStrategy,<br>    Component,<br>    HostBinding,<br>    Input,<br>} from &#39;@angular/core&#39;;<br><br>@Component({<br>    selector: &#39;tui-progress-circle&#39;,<br>    templateUrl: &#39;./progress-circle.template.html&#39;,<br>    styleUrls: [&#39;./progress-circle.style.less&#39;],<br>    changeDetection: ChangeDetectionStrategy.OnPush,<br>})<br>export class TuiProgressCircleComponent {<br>    @Input()<br>    value = 0;<br><br>    @Input()<br>    max = 1;<br><br>    @Input()<br>    @HostBinding(&#39;style.--tui-progress-color&#39;)<br>    color: string | null = null;<br><br>    @Input()<br>    @HostBinding(&#39;attr.data-size&#39;)<br>    size: &#39;m&#39; | &#39;l&#39; = &#39;m&#39;;<br><br>    @HostBinding(&#39;style.--progress-percentage&#39;)<br>    get progressPercentage(): number {<br>        return this.value / this.max;<br>    }<br>}</pre><p>The HTML file contains:</p><pre>&lt;progress<br>  class=&quot;hidden-progress&quot;<br>  [value]=&quot;value&quot;<br>  [max]=&quot;max&quot;<br>&gt;&lt;/progress&gt;<br><br>&lt;svg class=&quot;svg&quot; height=&quot;100%&quot; width=&quot;100%&quot; aria-hidden=&quot;true&quot;&gt;<br>  &lt;circle<br>    class=&quot;track&quot;<br>    cx=&quot;50%&quot;<br>    cy=&quot;50%&quot;<br>  &gt;&lt;/circle&gt;<br><br>  &lt;circle<br>    class=&quot;progress&quot;<br>    cx=&quot;50%&quot;<br>    cy=&quot;50%&quot;<br>  &gt;&lt;/circle&gt;<br>&lt;/svg&gt;</pre><p>And the last step — the creation of the LESS file. We will use many features of Less: <a href="https://lesscss.org/features/#mixins-feature">mixins</a>, <a href="https://lesscss.org/features/#maps-feature">Maps</a>, and built-in <a href="https://lesscss.org/functions/#math-functions">Math functions</a>.</p><p>Firstly, let’s create map constants which store values for the different sizes of circular indicators (there are 4 sizes in our project, but for the sake of the code’s simplicity we will leave only 2). It is worth noting that Safari does not support rem-units inside svg-elements. However, it supports em-units. Therefore, we set font-size: 1rem for the host element to use em-units inside it.</p><pre>@width: {<br>    @m: 2em;<br>    @l: 7em;<br>};<br><br>@track-stroke: {<br>    @m: 0.5em;<br>    @l: 0.25em;<br>};<br><br>@progress-stroke: {<br>    @m: 0.5em;<br>    @l: 0.375em;<br>};</pre><p>The process of progress filling happens due to setting of stroke-dasharray and recalculations of the stroke-dashoffset. Look into the article “<a href="https://css-tricks.com/building-progress-ring-quickly">Building a Progress Ring, Quickly</a>” to understand how it works. Our solution is just an improved version of what was suggested by its author. Create a mixin which calculates the state of the progress indicator for different components’ sizes:</p><pre>.circle-params(@size) {<br>  width: @width[ @@size];<br>  height: @width[ @@size];<br><br>  .track {<br>    r: (@width[ @@size] - @track-stroke[ @@size]) / 2;<br>    stroke-width: @track-stroke[ @@size];<br>  }<br><br>  .progress {<br>    @radius: (@width[ @@size] - @progress-stroke[ @@size]) / 2;<br>    @circumference: 2 * pi() * @radius;<br>    <br>    r: @radius;<br>    stroke-width: @progress-stroke[ @@size];<br>    stroke-dasharray: @circumference;<br>    stroke-dashoffset: calc(@circumference - var(--progress-percentage) * @circumference);<br>  }<br>}</pre><p>Finally, apply our mixin on host-element and add some cosmetic improvements:</p><pre>:host {<br>    display: block;<br>    position: relative;<br>    color: yellow;<br>    transform: rotate(-90deg);<br>    transform-origin: center;<br>    font-size: 1rem;<br><br>    &amp;[data-size=&#39;m&#39;] {<br>        .circle-params(m);<br>    }<br><br>    &amp;[data-size=&#39;l&#39;] {<br>        .circle-params(l);<br>    }<br>}<br><br>.track {<br>    fill: transparent;<br>    stroke: grey;<br>}<br><br>.progress {<br>    fill: transparent;<br>    stroke: var(--tui-progress-color, currentColor);<br>    transition: stroke-dashoffset 300 linear;<br>}<br><br>.hidden-progress {<br>    .sr-only(); // see this mixin in the chapter with ProgressBar<br>}<br><br>.svg {<br>    overflow: unset;<br>}</pre><p>That is all! You can <a href="https://taiga-ui.dev/components/progress-circle">see the component in action</a> on the showcase of our Taiga UI project. The final source code is available <a href="https://github.com/Tinkoff/taiga-ui/tree/main/projects/kit/components/progress/progress-circle">on GitHub</a>.</p><blockquote><em>💡</em> Tip: You can now deploy your newly created semantic components to a platform such as <a href="https://bit.cloud/"><strong>Bit</strong></a> so they can be reused across all of your projects. With Bit, you’ll have independent versioning, tests, and documentation for your components, making it easier for others to understand and use your code. This would let your team reuse and collaborate on components to write scalable code, speed up development, and maintain a consistent UI. Find out more <a href="https://bit.dev/docs/angular-components/components-overview"><strong>here</strong></a>.</blockquote><h3>Wrapping up</h3><p>There are no absolutely correct solutions in the frontend-development. The same functionality can be implemented in different ways. But there are superficial and lazy solutions, which deliver the final product by sacrificing convenience and optimization. I have shown what mistakes can be made if you want to create progress indicators: poor semantics and neglected accessibility.</p><p>In this article, I have shown my point of view on these components. I don’t claim that my solutions are the best ones in the field. And still, during the development of those, I took all mentioned problems into consideration, ensured compatibility with all modern browsers and got simple solutions with a minimum usage of JavaScript.</p><h4>Build apps with reusable components, just like Lego</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/700/0*6GJ-JCmk27gu0cn3.png" /></figure><p><a href="https://bit.cloud/"><strong>Bit</strong></a><strong>’s open-source tool </strong>help 250,000+ devs to build apps with components.</p><p>Turn any UI, feature, or page into a <strong>reusable component</strong> — and share it across your applications. It’s easier to collaborate and build faster.</p><p><strong>→ </strong><a href="https://bit.dev/"><strong>Learn more</strong></a></p><p>Split apps into components to make app development easier, and enjoy the best experience for the workflows you want:</p><h4>→ <a href="https://blog.bitsrc.io/how-we-build-micro-front-ends-d3eeeac0acfc">Micro-Frontends</a></h4><h4>→ <a href="https://blog.bitsrc.io/how-we-build-our-design-system-15713a1f1833">Design System</a></h4><h4>→ <a href="https://bit.cloud/blog/how-to-reuse-react-components-across-your-projects-l4pz83f4">Code-Sharing and reuse</a></h4><h4>→ <a href="https://www.youtube.com/watch?v=5wxyDLXRho4&amp;t=2041s">Monorepo</a></h4><h4>Learn more</h4><ul><li><a href="https://blog.bitsrc.io/how-we-build-micro-front-ends-d3eeeac0acfc">How We Build Micro Frontends</a></li><li><a href="https://blog.bitsrc.io/how-we-build-our-design-system-15713a1f1833">How we Build a Component Design System</a></li><li><a href="https://bit.dev/blog/how-to-reuse-react-components-across-your-projects-l4pz83f4/">How to reuse React components across your projects</a></li><li><a href="https://blog.bitsrc.io/5-ways-to-build-a-react-monorepo-a294b6c5b0ac">5 Ways to Build a React Monorepo</a></li><li><a href="https://bit.dev/blog/how-to-create-a-composable-react-app-with-bit-l7ejpfhc/">How to Create a Composable React App with Bit</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=fe80512947d9" width="1" height="1" alt=""><hr><p><a href="https://medium.com/bitsrc/no-div-arounds-writing-semantic-progress-indicators-in-angular-fe80512947d9">How to Build Semantic Progress Indicators in Angular</a> was originally published in <a href="https://medium.com/bitsrc">Bits and Pieces</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[«Bots should work, developers should think»: Writing Github App with Node.js]]></title>
            <link>https://medium.com/its-tinkoff/bots-should-work-developers-should-think-writing-github-app-with-node-js-2e8eb049d7e4?source=rss-8ad93c244ca3------2</link>
            <guid isPermaLink="false">https://medium.com/p/2e8eb049d7e4</guid>
            <category><![CDATA[github]]></category>
            <category><![CDATA[nodejs]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[node]]></category>
            <dc:creator><![CDATA[Nikita Barsukov]]></dc:creator>
            <pubDate>Mon, 29 Nov 2021 12:08:44 GMT</pubDate>
            <atom:updated>2021-12-30T10:09:19.991Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*UeyA_fr0-gKRGC6-CPb45Q.png" /></figure><p>Undoubtedly, software developers are creative and pretty busy people — they do not have enough time to take care of routine tasks. However, machines can do all the monotonous tasks and, occasionally, even more effectively than humans. That is why everything should be automated to a large extent.</p><p>Hi! My name is Nikita. I am a frontend developer from <a href="https://github.com/Tinkoff/taiga-ui">Taiga UI</a>. It is an Angular UI-Kit library that is actively used in the “Tinkoff” company. I will speak about solving one of such routine tasks in our project by writing a <a href="https://docs.github.com/en/developers/apps/getting-started-with-apps/about-apps#about-github-apps">Github App</a> in Node.js from scratch.</p><h4><strong>Problem statement — Lost in the forest of screenshots</strong></h4><p>In our project, we actively write screenshot tests using the framework <a href="https://www.cypress.io/">Cypress</a>.</p><p>After committing all code changes and opening pull request, a new Github CI Workflow runs every test. It saves our upcoming releases from introducing new bugs into our awesome UI-Kits components. If any test fails, all screenshots are attached as the zip-archive artifact to this workflow. The developer can download it and look into the screenshots. Unfortunately, we do not live in the perfect world where developers never make mistakes, and tests sometimes fail. Developers should download the archive and compare screenshots with “before” and “after” states. If there are too many tests, such simple action becomes an exhausting task. As I mentioned, this can be automated!</p><p>Cypress offers an official paid tool — <a href="https://www.cypress.io/dashboard/">Dashboard</a>, which costs an arm and a leg. I bet that all developers need their limbs, so an alternative is unofficial, but a somewhat popular tool — <a href="https://sorry-cypress.dev/">Sorry Cypress</a>. This open-source solution proposes almost the same dashboard but with lower prices and the possibility of hosting the entire infrastructure on your servers.</p><p>Even though this bootleg alternative is a better option, we still decided to write a simple Github bot.</p><h4><strong>How Github App works</strong></h4><p>To cut a long story short, the Github App is a set of callback-functions. They are called when the watched webhook-event is triggered in the repository. A list of all events is available on this <a href="https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads">Github Doc page</a>. The callback-functions usually make Github API requests, leading to the creation of new commits, branches, files, and other changes in the repository.</p><p>We can monitor the necessary webhook-event and send the required API requests using only vanilla Javascript. However, it is much simpler to use community-approved solutions, which provide some abstraction over the vanilla JS. We will use the popular framework <a href="https://probot.github.io/">Probot</a> for writing Github Apps.</p><p>You can initialize a new app using cli-commands. It is well described in the <a href="https://probot.github.io/docs/development/#generating-a-new-app">framework’s documentation</a> and will not be repeated here. We recommend using a Typescript template during the app’s creation: strict typing prevents you from making random mistakes.</p><h4><strong>Listening to repository events</strong></h4><p>Our bot should listen to only three types of events: when the workflow begins (1), completes (2), and closes the pull request (3). Open the generated by cli index.ts file and add the following code:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8a818bad6911c3f458c1ccba3705e5d1/href">https://medium.com/media/8a818bad6911c3f458c1ccba3705e5d1/href</a></iframe><blockquote>Reminder: do not forget to give permissions to the bot to monitor workflow_run and pull_request events on the Github App’s settings page.</blockquote><p>In the code, each callback-function to the repository event takes a context argument. This context contains much helpful information about the “watched” event. For example, it is the selector-function to get the name of the workflow that triggered the given webhook-event:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/fa90ca6fd54d854626f308a03363cce0/href">https://medium.com/media/fa90ca6fd54d854626f308a03363cce0/href</a></iframe><p>The context.payload also contains other required information: the id of the workflow run, the name of the branch on which this workflow was triggered, the number of the opened pull request, and a lot of other information.</p><h4><strong>Using the Github API</strong></h4><p>The framework Probot uses Node.js module <a href="https://github.com/octokit/rest.js#readme">@octokit/rest</a>. It helps to use Github REST API methods via context.octokit…. See the list of all available methods <a href="https://octokit.github.io/rest.js/">here</a>.</p><p>Our bot requires the following methods to create comments on pull requests:</p><ul><li>context.octokit.issues.createComment (create a new PR’s comment).</li><li>context.octokit.rest.issues.updateComment (edit an already existing PR’s comment).</li></ul><p>Do not be confused that we use methods from the issue object. Pull request is the same issue containing code. <a href="https://docs.github.com/en/rest/reference/pulls">Github docs claims</a>: “Every pull request is an issue, but not every issue is a pull request”. Therefore, all issue’s methods are applicable to the pull requests as well.</p><p>To download artifacts with screenshots of failed tests, we use the following methods:</p><ul><li>context.octokit.actions.listWorkflowRunArtifacts (a list of meta-information about the all artifacts of a given workflow).</li><li>context.octokit.actions.downloadArtifact (downloading an artifact-archive by its id).</li></ul><p>So, we have screenshot files and an understanding of how to create comments. Github’s comments support Markdown, and Markdown allows to insert images as base64 strings. It seems that it is a home stretch… but it is not. The Markdown’s version used by Github does not support this feature. You can insert an image into a Github’s comment only by an external link.</p><p>But this problem can be solved too. You can upload the required file (which we plan to attach to the failed tests’ report) to a separate repository branch and access it later via https://raw.githubusercontent.com/…. The code will be as follows:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6c1ddbc9550c7cd9dd790bc103e68815/href">https://medium.com/media/6c1ddbc9550c7cd9dd790bc103e68815/href</a></iframe><p>After the PR’s closing, uploaded images can always be deleted. <a href="https://github.com/octokit/rest.js#readme">@octokit/rest</a> library has API methods for it.</p><h4><strong>Deployment</strong></h4><p>Deployment is a compulsory part of every application life. The official documentation of the Probot framework <a href="https://probot.github.io/docs/deployment/#deploy-the-app">offers detailed tutorials</a> on how to deploy Github App using popular services. We deployed our Node.js application to <a href="https://glitch.com/">Glitch</a>. This platform provides free hosting, and the limitations of a free account are negligible for a simple application like a Github bot.</p><p>The final source code of our bot can be found on this Github repository:</p><p><a href="https://github.com/Tinkoff/argus">GitHub - Tinkoff/argus: GitHub App for screenshots tests in CI</a></p><p>We already actively use it in our project. The repository with the application was named <strong>Argus</strong> (a many-eyed “all-seeing” giant in Greek mythology). It was developed much more resounding than described in this article, but the application’s core was described above.</p><h4><strong>Wrapping up — Getting out of the woods</strong></h4><p>The development of a Github App is easy. It requires neither deep knowledge of the programming language nor framework. You should just look through the <a href="https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads">list of the repository webhooks-events</a> and look into <a href="https://octokit.github.io/rest.js/">Github REST API methods</a> to find the necessary ones and apply them for your task.</p><p>In this article, we have built a Github application that watches workflows with tests. If any tests fail, the bot downloads artifacts, finds screenshots with the differences between the “before” / “after” states and then attaches them as a comment to the PR.</p><p>We deployed the code as a Github bot called <a href="https://github.com/apps/lumberjack-bot">Lumberjack</a>. It is already actively following our <a href="https://github.com/Tinkoff/taiga-ui">Taiga UI</a> project. But the bot has configurable parameters, and you can easily customize it for your project. Just invite it to your repository and show workflow to watch.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2e8eb049d7e4" width="1" height="1" alt=""><hr><p><a href="https://medium.com/its-tinkoff/bots-should-work-developers-should-think-writing-github-app-with-node-js-2e8eb049d7e4">«Bots should work, developers should think»: Writing Github App with Node.js</a> was originally published in <a href="https://medium.com/its-tinkoff">IT-компании Тинькофф</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Dedicated and Shared Web Workers in Angular]]></title>
            <link>https://medium.com/@nsbarsukov/dedicated-and-shared-web-workers-in-angular-c3df473882f6?source=rss-8ad93c244ca3------2</link>
            <guid isPermaLink="false">https://medium.com/p/c3df473882f6</guid>
            <category><![CDATA[angular]]></category>
            <category><![CDATA[webworker]]></category>
            <category><![CDATA[shared-webworker]]></category>
            <category><![CDATA[dedicated-webworker]]></category>
            <dc:creator><![CDATA[Nikita Barsukov]]></dc:creator>
            <pubDate>Sat, 17 Aug 2019 19:12:17 GMT</pubDate>
            <atom:updated>2019-08-17T19:12:17.003Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/865/1*KEw0VeYPeiTItELK34RPWw.jpeg" /></figure><blockquote><strong>Disclaimer:</strong> <em>this article is not written by experienced front-developer. The purpose of this paper is to help the author to reproduce successful implementation of web workers (dedicated and shared ones) in angular 8.</em></blockquote><h3><strong>Introduction</strong></h3><p>As you know, Javascript is a single thread programming language, and it means that it can handle only one task at a time (you can read more about it <a href="https://github.com/leonardomso/33-js-concepts#1-call-stack">here</a>). If user executes the function which produces heavy calculations, he/she can’t interact with SPA until finishing the execution (your application will be just frozen). I hope there is no need to explain why it is not excellent UX.</p><p>The web workers can help us to solve the problem.</p><blockquote>“Workers run in another global context that is different from the current window” — <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Web_Workers_API">MDN web docs</a>.</blockquote><p>Web worker is a script which executes in separate thread (without interrupting javascript call stack!) and then return the result of calculation to UI. There are different kinds of web workers, but only dedicated and shared ones will be discussed in this article.</p><blockquote>«A dedicated worker is only accessible from the script that first spawned it, whereas shared workers can be accessed from multiple scripts.</blockquote><blockquote>[…]</blockquote><blockquote>If SharedWorker can be accessed from several browsing contexts, all those browsing contexts must share the exact same origin (same protocol, host, and port)».</blockquote><blockquote><a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers">MDN web docs</a></blockquote><h3><strong>Description of task example</strong></h3><p>It is no secret that all new concepts are easier to learn with examples. Let’s imagine that you are front-end intern and you have got a training task to write an app which will calculate the sum of all numbers from 1 to 2 billion. Additionally, your boss has said to include timer of application’s lifespan (which will be updated every second).</p><p>Firstly, you launch your IDE and create new angular app.</p><pre><em>ng new largeNumberFactorial</em></pre><p>In your <em>app.component.ts </em>you wrote this heavy function, rxjs timer and created some properties:</p><pre>// app.component.ts<br> <strong>import </strong>{ Component } <strong>from &#39;@angular/core&#39;</strong>;<br> <strong>import </strong>{timer} <strong>from &#39;rxjs&#39;</strong>;<br> <br> <strong>@Component</strong>({<br>   selector: <strong>&#39;app-root&#39;</strong>,<br>   templateUrl: <strong>&#39;./app.component.html&#39;</strong>,<br>   styleUrls: [<strong>&#39;./app.component.css&#39;</strong>]<br> })<br> <strong>export class </strong>AppComponent {<br>   heavyComputationsStatus = <strong>&#39;no tasks yet&#39;</strong>;<br>   appLifespan$ = timer(0, 1000);<br> <br>   launchHeavyFunctionJSThread(): <strong>void </strong>{<br>     <strong>let </strong>result = 0;<br>     <strong>for </strong>(<strong>let </strong>i = 1; i &lt; 2000000000; i++) {<br>       result += i;<br>     }<br>     <strong>this</strong>.heavyComputationsStatus = <strong>&#39;Calculations (inside component) were finished with id &#39; </strong>+ Math.round(Math.random() * 100);<br>   }<br> }</pre><p>Let’s create a simple template.</p><pre>&lt;!--app.component.html--&gt;<br><br>&lt;div&gt;<br><br>  &lt;h1&gt;Lifespan of application&lt;/h1&gt;<br><br>  &lt;p&gt;{{appLifespan$ | async}}&lt;/p&gt;</pre><pre>  &lt;div&gt;<br><br>    &lt;button<br><br>      (click)=&quot;launchHeavyFunctionJSThread()&quot;&gt;<br><br>      Count (JS thread)<br><br>    &lt;/button&gt;<br><br>  &lt;/div&gt;</pre><pre>  &lt;p&gt;{{heavyComputationsStatus}}&lt;/p&gt;<br><br>&lt;/div&gt;</pre><p>After running this version of app, you can notice that clicking on button interrupts the timer (if you are a lucky owner of powerful computer, just increase number of iterations in function to notice it).</p><h3><strong>Dedicated worker</strong></h3><p>It is time for dedicated worker. Angular team with new version 8 introduces the possibility to generate new web workers from your Angular CLI (you can read about it <a href="https://blog.logrocket.com/whats-new-in-angular-8-web-worker-support-and-more/">here</a>).</p><pre><em>ng generate webWorker dedicatedWorker</em></pre><p>It creates dedicated-worker.worker.ts. In this separate file you should insert all your heavy calculations. The worker communicates with your app component using postMessage function and listening to ‘message’ event.</p><pre>// dedicated-worker.worker.ts<br><br>/// &lt;reference lib=&quot;webworker&quot; /&gt;<br><br>addEventListener(<strong>&#39;message&#39;</strong>, ({ data }) =&gt; {<br><br>  <strong>let </strong>result = 0;<br><br>  <strong>for </strong>(<strong>let </strong>i = 1; i &lt; 2000000000; i++) {<br>    result += i;<br>  }<br><br>  <strong>const </strong>heavyComputationsStatus = <strong>&#39;Calculations (inside component) were finished with id &#39; </strong>+ Math.round(Math.random() * 100);<br><br>  postMessage(heavyComputationsStatus);<br><br>});</pre><p>Then, create worker only once in ngOnInit of your component and subscribe to getting event “message” from the worker. Don`t forget to destroy your worker in ngOnDestroy (it is meaningless for the app component, but was implementing for demonstration).</p><pre>// app.component.ts<br> <strong>import </strong>{Component, OnDestroy, OnInit} <strong>from &#39;@angular/core&#39;</strong>;<br> <strong>import </strong>{timer} <strong>from &#39;rxjs&#39;</strong>;<br> <br> <strong>@Component</strong>({<br>   selector: <strong>&#39;app-root&#39;</strong>,<br>   templateUrl: <strong>&#39;./app.component.html&#39;</strong>,<br>   styleUrls: [<strong>&#39;./app.component.css&#39;</strong>]<br> })<br> <strong>export class </strong>AppComponent <strong>implements </strong>OnInit, OnDestroy {<br> <br>   heavyComputationsStatus = <strong>&#39;no tasks yet&#39;</strong>;<br>   appLifespan$ = timer(0, 1000);<br>   dedicatedWorker: Worker;<br> <br>   ngOnInit(): <strong>void </strong>{<br>     <strong>this</strong>.dedicatedWorker = <strong>new </strong>Worker(<strong>&#39;./dedicated-worker.worker&#39;</strong>, { type: <strong>&#39;module&#39; </strong>});<br> <br>     <strong>this</strong>.dedicatedWorker.onmessage = ({data}) =&gt; {<br>       <strong>this</strong>.heavyComputationsStatus = data;<br>     };<br>   }<br> <br>   ngOnDestroy(): <strong>void </strong>{<br>     <strong>this</strong>.dedicatedWorker.terminate();<br>   }<br> <br>   launchHeavyFunctionJSThread(): <strong>void </strong>{<br>     <strong>let </strong>result = 0;<br>     <strong>for </strong>(<strong>let </strong>i = 1; i &lt; 2000000000; i++) {<br>       result += i;<br>     }<br>     <strong>this</strong>.heavyComputationsStatus = <strong>&#39;Calculations (inside component) were finished with id &#39; </strong>+<br>       Math.round(Math.random() * 100);<br>   }<br> <br>   askDedicatedWorker(): <strong>void </strong>{<br>     <strong>this</strong>.dedicatedWorker.postMessage({});<br>   }<br> }</pre><p>And updated template:</p><pre>&lt;!--app.component.html--&gt;<br><br>&lt;div&gt;<br><br>  &lt;h1&gt;Lifespan of application&lt;/h1&gt;<br><br>  &lt;p&gt;{{appLifespan$ | async}}&lt;/p&gt;<br><br>  &lt;div&gt;<br>    &lt;button<br>      (click)=&quot;launchHeavyFunctionJSThread()&quot;&gt;<br>      Count (JS thread)<br>    &lt;/button&gt;<br><br>    &lt;button<br>      (click)=&quot;askDedicatedWorker()&quot;&gt;<br>      Count (worker)<br>    &lt;/button&gt;<br>  &lt;/div&gt;<br><br>  &lt;p&gt;{{heavyComputationsStatus}}&lt;/p&gt;<br><br>&lt;/div&gt;</pre><p>Run your app. Clicking on button which posts message to worker doesn’t freeze our application!</p><h3><strong>Shared worker</strong></h3><p>You showed your result to the chief, he was satisfied, but in reply he asks you to add another feature: add button which will send result of calculations to other open tabs of your application in the browser.</p><p>Implementing shared worker is the most appropriate tool for this task. Unfortunately, at this moment (relevant for the summer 2019) shared worker doesn’t have the same Angular CLI support.</p><p>Firstly, you should create your shared worker’s logic by creating shared-worker.worker.js inside your root folder «assets».</p><pre>//shared-worker.worker.js<br> <br> connections = [];<br> <br> self.onconnect = connectEvent =&gt; {<br>   <strong>const </strong>port = connectEvent.ports[0];<br> <br>   port.start();<br>   connections.push(port);<br> <br>   port.onmessage = messageEvent =&gt; {<br>     connections.forEach(connection =&gt; {<br>       connection.postMessage(messageEvent.data);<br>     });<br>   }<br> };</pre><p>Secondly, you should declare typings of SharedWorker constructor.</p><pre>npm i @types/sharedworker</pre><p>Then find this file “index.d.ts” in “node_module@types/sharedworker” and copy it to the root directory of your app (“src/app”) as “typings.d.ts” (don’t ask me why it is necessary but it does; it is the most «black box» part of shared web workers for me).</p><p>Thirdly, add creation of shared worker in ngOnInit of app component, subscribe to get new messages from this worker and define function to post messages from component to worker. Final version of .ts file.</p><pre>// app.component.ts<br> <strong>import </strong>{Component, OnDestroy, OnInit} <strong>from &#39;@angular/core&#39;</strong>;<br> <strong>import </strong>{timer} <strong>from &#39;rxjs&#39;</strong>;<br> <br> <strong>@Component</strong>({<br>   selector: <strong>&#39;app-root&#39;</strong>,<br>   templateUrl: <strong>&#39;./app.component.html&#39;</strong>,<br>   styleUrls: [<strong>&#39;./app.component.css&#39;</strong>]<br> })<br> <strong>export class </strong>AppComponent <strong>implements </strong>OnInit, OnDestroy {<br> <br>   heavyComputationsStatus = <strong>&#39;no tasks yet&#39;</strong>;<br>   appLifespan$ = timer(0, 1000);<br>   dedicatedWorker: Worker;<br>   sharedWorker: SharedWorker.SharedWorker;<br> <br>   ngOnInit(): <strong>void </strong>{<br>     <strong>this</strong>.dedicatedWorker = <strong>new </strong>Worker(<strong>&#39;./dedicated-worker.worker&#39;</strong>,<br>       { type: <strong>&#39;module&#39; </strong>});<br> <br>     <strong>this</strong>.dedicatedWorker.onmessage = ({data}) =&gt; {<br>       <strong>this</strong>.heavyComputationsStatus = data;<br>     };<br> <br>     <strong>this</strong>.sharedWorker = <strong>new </strong>SharedWorker(<strong>&#39;/assets/shared-worker.worker.js&#39;</strong>);<br>     <strong>this</strong>.sharedWorker.port.onmessage = ({data}) =&gt; {<br>       <strong>this</strong>.heavyComputationsStatus = data;<br>     };<br>     <strong>this</strong>.sharedWorker.port.start();<br>   }<br> <br>   ngOnDestroy(): <strong>void </strong>{<br>     <strong>this</strong>.dedicatedWorker.terminate();<br>   }<br> <br>   launchHeavyFunctionJSThread(): <strong>void </strong>{<br>     <strong>let </strong>result = 0;<br>     <strong>for </strong>(<strong>let </strong>i = 1; i &lt; 2000000000; i++) {<br>       result += i;<br>     }<br>     <strong>this</strong>.heavyComputationsStatus = <strong>&#39;Calculations (inside component) were finished with id &#39; </strong>+<br>       Math.round(Math.random() * 100);<br>   }<br> <br>   askDedicatedWorker(): <strong>void </strong>{<br>     <strong>this</strong>.dedicatedWorker.postMessage({});<br>   }<br> <br>   askSharedWorker(): <strong>void </strong>{<br>     <strong>this</strong>.sharedWorker.port.postMessage(<strong>this</strong>.heavyComputationsStatus);<br>   }<br> }</pre><p>Final version of template</p><pre>&lt;!--app.component.html--&gt;<br> &lt;div&gt;<br> <br>   &lt;h1&gt;Lifespan of application&lt;/h1&gt;<br>   &lt;p&gt;{{appLifespan$ | async}}&lt;/p&gt;<br> <br>   &lt;div&gt;<br>     &lt;button<br>       (click)=&quot;launchHeavyFunctionJSThread()&quot;&gt;<br>       Count (JS thread)<br>     &lt;/button&gt;<br>     &lt;button<br>       (click)=&quot;askDedicatedWorker()&quot;&gt;<br>       Count (worker)<br>     &lt;/button&gt;<br>     &lt;button<br>     (click)=&quot;askSharedWorker()&quot;&gt;<br>       share<br>     &lt;/button&gt;<br>   &lt;/div&gt;<br> <br>   &lt;p&gt;{{heavyComputationsStatus}}&lt;/p&gt;<br> <br> &lt;/div&gt;</pre><p>Finally, run your app. Open some new tabs of your application. Press any button to execute heavy calculations on any page. After finishing it, press “share” button and look at other open tabs. The last line of every page will have the same string message (with the same id)!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c3df473882f6" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>