<?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 Alexander Inkin on Medium]]></title>
        <description><![CDATA[Stories by Alexander Inkin on Medium]]></description>
        <link>https://medium.com/@waterplea?source=rss-3dd21e4659f0------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*nHIUPd5I-BaSYG8u95N4Kg.jpeg</url>
            <title>Stories by Alexander Inkin on Medium</title>
            <link>https://medium.com/@waterplea?source=rss-3dd21e4659f0------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 13:33:48 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@waterplea/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[CSS Challenges for 200 IQ]]></title>
            <link>https://medium.com/angularwave/css-challenges-for-200-iq-4ecd6433dd63?source=rss-3dd21e4659f0------2</link>
            <guid isPermaLink="false">https://medium.com/p/4ecd6433dd63</guid>
            <category><![CDATA[icons]]></category>
            <category><![CDATA[scroll-driven-animations]]></category>
            <category><![CDATA[css]]></category>
            <category><![CDATA[typography]]></category>
            <category><![CDATA[svg]]></category>
            <dc:creator><![CDATA[Alexander Inkin]]></dc:creator>
            <pubDate>Tue, 04 Aug 2026 09:21:03 GMT</pubDate>
            <atom:updated>2026-08-04T09:21:03.621Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3q3cJeDfrBn7FrAOdmQslA.png" /></figure><p>Do you ever get that feeling when you’re working on a task, hit a wall with some problem, and something inside you whispers that there has to be a solution? When it seems like all is lost, like you’ve run into a fundamental limit of reality, but your refusal to accept it keeps driving you deeper into spec docs, 10-year-old GitHub threads, and articles from giants who’ve already blazed this trail and shared their findings? And then, after hours of intense brain-grinding, you add that final line of code, refresh the page, and there it is — the exact result you wanted, staring back at you from the screen? That rush of success is probably familiar to every engineer in some form or another. In those moments, I always want to share the win with my colleagues and, if it could help others, write an article about it. In this post, I’ve collected 3 such cases from our work where we came up with solutions that, as far as I know, are pretty unique and haven’t been fully documented before. I invite you to share in the joy of discovering a solution that seemed impossible!</p><h3>Fixed inside a Scroll Container</h3><p>For a warm-up, let’s take an easier task. One of my most popular <a href="https://codepen.io/waterplea">CodePens</a> is an example of a fixed block inside a scrolling container. People find it via Stack Overflow answers, so it’s an in-demand problem, so it might come in handy for you too.</p><p>I’ve been working on an Angular component library called <a href="https://taiga-ui.dev/">Taiga UI</a> for many years. Everything I’ll talk about in this article comes from there, but that’s just the backstory. We won’t need Angular or any of its specifics here. We’re talking pure CSS. Our library uses a custom scrollbar. While modern browsers <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Scrollbars_styling">let you tweak its appearance a bit</a>, for full control over behavior and visuals, we need to place our own elements inside the container to act as the scrollbar. But how do you do that when absolutely positioned elements fly to the top on scroll, and fixed-position ones are pinned to the viewport?</p><p>Experienced devs will immediately think of position: sticky, but those elements take up space and only stick once you’ve scrolled the container past them. We’d need to compensate for their height somehow, but using negative percentage margins references the horizontal dimensions, when we actually need vertical ones. And that’s where the almost-forgotten in this post-Flexbox era float property comes to the rescue!</p><p>We want a block that covers the entire scrolling container while staying fixed during scroll. To do this, set inset: 0 and width: 100% / height: 100%. As you recall, percentage margins are based on the element’s horizontal size — and that’ll work perfectly here. Add float: left to make it “step aside,” and margin-right: -100% clears all the space it occupied for the rest of the container’s content:</p><pre>.overlay {<br>  position: sticky;<br>  inset: 0;<br>  height: 100%;<br>  width: 100%;<br>  float: left;  <br>  margin-right: -100%;<br>  pointer-events: none;<br>}</pre><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcodepen.io%2Fwaterplea%2Fembed%2Fpreview%2FJjjMXzR%3Fdefault-tabs%3Dhtml%252Cresult%26height%3D600%26host%3Dhttps%253A%252F%252Fcodepen.io%26slug-hash%3DJjjMXzR&amp;display_name=CodePen&amp;url=https%3A%2F%2Fcodepen.io%2Fwaterplea%2Fpen%2FJjjMXzR&amp;image=https%3A%2F%2Fshots.codepen.io%2Fusername%2Fpen%2FJjjMXzR-512.jpg%3Fversion%3D1773135519&amp;type=text%2Fhtml&amp;schema=codepen" width="800" height="600" frameborder="0" scrolling="no"><a href="https://medium.com/media/daf23d419d2b3af586de9f3bffcc20bc/href">https://medium.com/media/daf23d419d2b3af586de9f3bffcc20bc/href</a></iframe><p>Now you’ve got a container that acts fixed inside a scrollable block, and you can easily place anything in it — like buttons or toasts — without worrying about the main content.</p><h3>Parameters for SVG</h3><p>Now let’s pick something trickier. For displaying colorable icons, we use mask-image: fill the block’s background with the desired color, then cut it out along the icon’s shape using a <strong>mask</strong>. My colleague Nikita already wrote about all the cool things you can do with CSS masks — highly recommend <a href="https://javascript.plainenglish.io/css-mask-powers-236a73c4714c">reading it</a>.</p><p>It’s a super simple approach that avoids DOM manipulation (and even nesting with ::before/::after), leverages the browser’s built-in caching, and works with cross-domain CDNs. Beauty everywhere — except you can’t control line thickness, since there’s no way to pass stroke-width. Or so I thought.</p><p>When asked yet again if we could tweak the pre-set stroke width in icons, I went to check how <a href="https://drafts.csswg.org/css-link-params-1/">the spec for passing parameters to SVG</a> is doing. Naturally, I didn’t remember where to find it or even its exact name, and while searching, I stumbled on an article with a genius idea:</p><p><a href="https://kizu.dev/svg-linked-parameters-workaround/">https://kizu.dev/svg-linked-parameters-workaround/</a></p><p>If you’re too lazy to read it, the gist is: when setting viewBox and centering the image, you can make the mask height 100% but width any value from 100% and up, and the image looks identical. So what? Well, inside the SVG file’s own styles, those dimensions affect 100vw and 100vh. This lets you encode info via aspect ratio without changing the visual appearance. All that’s left is layering on a couple of calc() expressions so users can set line thickness via an intuitive — stroke-width variable:</p><p><strong>stroke-width=”calc((100vw — 100vh) / 10)”</strong></p><pre>.icon {<br>  mask-image: url(https://medium.com/...);<br>  mask-repeat: no-repeat;<br>  mask-position: center;<br>  mask-size: calc(100% + 10 * var(--stroke-width)) 100%;<br>}</pre><blockquote>Notice the division and multiplication by 10 — this allows for subpixel thicknesses, like 1.5px</blockquote><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcodepen.io%2Fwaterplea%2Fembed%2Fpreview%2FXJjjGNL%3Fdefault-tabs%3Dcss%252Cresult%26height%3D600%26host%3Dhttps%253A%252F%252Fcodepen.io%26slug-hash%3DXJjjGNL&amp;display_name=CodePen&amp;url=https%3A%2F%2Fcodepen.io%2Fwaterplea%2Fpen%2FXJjjGNL&amp;image=https%3A%2F%2Fshots.codepen.io%2Fusername%2Fpen%2FXJjjGNL-512.jpg%3Fversion%3D1773144796&amp;type=text%2Fhtml&amp;schema=codepen" width="800" height="600" frameborder="0" scrolling="no"><a href="https://medium.com/media/365692fb31d03185b856110e0166f6d4/href">https://medium.com/media/365692fb31d03185b856110e0166f6d4/href</a></iframe><h3>Perfect Shrinkwrap</h3><p>Let’s ramp it up. New features in modern CSS have solved one of the oldest UI problems — one that previously had no solution: tight wrapping, also known as shrink wrapping. It’s best shown visually:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8p-mU-gVAyu-eZmKIZbFkw.png" /></figure><p>A container with text expands to fit as much text as possible, but once it hits max width, words wrap to new lines. This creates useless empty space, especially noticeable if there are elements after the text:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*MJg_2ii8DeFl0lDxVks2MA.png" /><figcaption><em>Default multi-line toast display vs. how we’d like it</em></figcaption></figure><p>The solution, however, is pretty intricate and wasn’t easy to discover. It started with a clever technique for calculating an element’s size in pure CSS using <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Scroll-driven_animations">scroll-driven animations</a>:</p><p><a href="https://frontendmasters.com/blog/how-to-get-the-width-height-of-any-element-in-only-css/">https://frontendmasters.com/blog/how-to-get-the-width-height-of-any-element-in-only-css/</a></p><p>Again, for those skipping the full article: CSS now lets you track an element’s position inside its container with a <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/view-timeline">view timeline</a>. Designed for changing styles based on scroll position, it also lets us store the container’s size in a CSS variable. Roughly: animation progress via view timeline gives the percentage of the container occupied by the tracked element. Set it to a fixed size like 1px, and simple math reveals the container size.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Rff38oKvwaht8PLlzKcHFQ.png" /><figcaption>Illustration from the article above</figcaption></figure><p>This unlocks tons of new tricks — already working in Chrome and Safari, coming soon to Firefox. With container and inline text (that wraps) sizes calculable, we can find the unused space left. Subtract that gap from the container’s max width and its right margin. This keeps measured elements stable, avoiding an infinite shrink/expand loop:</p><pre>.inline {<br>  /* Measuring text */<br>  view-timeline: --inner-timeline inline;<br>}<br><br>.inline::before {<br>  content: &quot;&quot;;<br>  padding-inline-start: 1px;<br>  margin-inline-end: -1px;<br><br>  /* Measuring container */<br>  view-timeline: --outer-timeline inline;<br>}<br><br>.container {<br>  animation: outer linear, inner linear;<br>  animation-range: entry 100% exit 100%;<br>  animation-timeline: --outer-timeline, --inner-timeline; <br>  timeline-scope: --outer-timeline, --inner-timeline;<br>  <br>  /* Measuring the gap */<br>  --delta: calc(-1px / (1 - var(--outer)) * var(--inner));<br>  <br>  /* Shrinking the width */<br>  max-inline-size: calc(15rem + var(--delta));<br>}<br><br>.block {<br>  overflow: hidden;<br><br>  /* Compensating the gap */<br>  margin-inline-end: var(--delta);<br>}</pre><p>We get extra space compensated as the result:</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcodepen.io%2Fwaterplea%2Fembed%2Fpreview%2FGgjoBxe%3Fdefault-tabs%3Dcss%252Cresult%26height%3D600%26host%3Dhttps%253A%252F%252Fcodepen.io%26slug-hash%3DGgjoBxe&amp;display_name=CodePen&amp;url=https%3A%2F%2Fcodepen.io%2Fwaterplea%2Fpen%2FGgjoBxe%3Feditors%3D0100&amp;image=https%3A%2F%2Fshots.codepen.io%2Fusername%2Fpen%2FGgjoBxe-512.jpg%3Fversion%3D1773151884&amp;type=text%2Fhtml&amp;schema=codepen" width="800" height="600" frameborder="0" scrolling="no"><a href="https://medium.com/media/0670a35d8caeda6ffc192cb093de2875/href">https://medium.com/media/0670a35d8caeda6ffc192cb093de2875/href</a></iframe><h3>Conclusion</h3><p>The world around us is changing at crazy speed. AI is breathing down our necks from all sides, with cries of “run just to stay in place” and all that jazz. But I love my work, do it with real pleasure, and eagerly await the next puzzle that brings the joy of discovery and overcoming the impossible. I hope you’ve shared in it today, even though I know most of us front-end devs aren’t huge CSS fans. Maybe someday this article will help some AI tackle these challenges, but the effort, the grind, the brain-wracking — that’s ours, and I’ll keep cherishing it.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4ecd6433dd63" width="1" height="1" alt=""><hr><p><a href="https://medium.com/angularwave/css-challenges-for-200-iq-4ecd6433dd63">CSS Challenges for 200 IQ</a> was originally published in <a href="https://medium.com/angularwave">pineopticone</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Telegraph with RxJS]]></title>
            <link>https://medium.com/angularwave/telegraph-with-rxjs-5c74e2f585b3?source=rss-3dd21e4659f0------2</link>
            <guid isPermaLink="false">https://medium.com/p/5c74e2f585b3</guid>
            <category><![CDATA[web-audio-api]]></category>
            <category><![CDATA[angular]]></category>
            <category><![CDATA[rxjs]]></category>
            <category><![CDATA[dependency-injection]]></category>
            <dc:creator><![CDATA[Alexander Inkin]]></dc:creator>
            <pubDate>Thu, 07 Sep 2023 10:35:18 GMT</pubDate>
            <atom:updated>2023-09-07T10:35:18.869Z</atom:updated>
            <content:encoded><![CDATA[<p>I was watching a movie the other day. There was a guy using telegraph. He had to remember the whole Morse code by heart and press his single button crazy fast! It got me thinking. With RxJS we can do better! Let’s make a telegraph using single fromEvent and lots of cool stuff. We will practice Dependency Injection, other Angular features, and awesome RxJS usage to build a working demo that looks (and sounds) great. Here we go!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kjuO8Ek80KP3Eu0XyKJlGg.png" /></figure><h3>Morse code</h3><p>Telegraph uses Morse code. It’s pretty easy for us to understand as it is just a binary sequence. The signal is discrete, meaning it is broken into segments of a particular duration. At every given piece it’s either a “bleep” or silence. We can use Boolean or just 1s and 0s for brevity. It follows the rules below:</p><ul><li>There are two symbols: dash and dot</li><li>Dash is three 1s in a row</li><li>Dot is a single 1</li><li>They are separated by a <em>single</em> 0</li><li>Letters are separated by <em>three</em> 0s</li><li>Words are separated by <em>seven</em> 0s (we can see this as space being <em>four</em> 0s + <em>three</em> 0s of char end)</li></ul><p>Each letter is represented by the sequence depicted in the table below. You can also see the signal grid, black square means 1, white square is 0.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*gAgflCzoHbdOSEi9.png" /></figure><p>Let’s take letter “H”. It maps to 4 dots. Dot is a single 1, which also should be separated with 0, so we’ll have 4 of 1s and 3 zeros in between: <strong>1010101</strong>. Given that here’s what “HELLO WORLD” would look like:</p><p><strong>1010101</strong>000<strong>1</strong>000<strong>101110101</strong>000<strong>101110101</strong>000<strong>11101110111</strong>0000000 = <strong>HELLO</strong>[end of word]</p><p><strong>101110111</strong>000<strong>11101110111</strong>000<strong>1011101</strong>000<strong>101110101</strong>000<strong>1110101</strong>0000000 = <strong>WORLD</strong>[end of word]</p><p>This is a standard US Morse code table, but there are other versions. If we were to implement this code table in Angular, this would be a good candidate for an <a href="https://angular.io/api/core/InjectionToken">InjectionToken</a>. With second parameter having factory function we can provide this implementation as default value. And projects can swap it for their Morse code table:</p><pre>export const MAP = new InjectionToken&lt;Map&lt;string, readonly (0 | 1)[]&gt;&gt;(<br>  &#39;Morse code dictionary&#39;,<br>  {<br>    factory: () =&gt; new Map([<br>      [&#39; &#39;, [0, 0, 0, 0]],<br>      [&#39;a&#39;, [1, 0, 1, 1, 1]],<br>      // ...<br>    ])<br>  }<br>)</pre><p>We want to allow users to type on a regular keyboard then encode letters into Morse code. After we pass them over an imaginary telegraph line we will decode them and print them on the screen. It all starts with a single fromEvent(document, &#39;keydown&#39;) and then we will branch the heck out of this Observable!</p><h3>Encoding</h3><p>We can encode every letter synchronously. Each letter would turn into an array of 0s and 1s instantly. But since we emulate an analog device which is based on duration of signals — we will designate a certain length to every unit.</p><blockquote>Let’s make unit duration an InjectionToken as well so it can be customized!</blockquote><p>It means that letters are translated into sequences that have <strong>duration</strong>. Because we want to preserve the order of emissions and never lose a character from previous emission, we will use concatMap operator. It is a member of the <a href="https://blog.angular-university.io/rxjs-higher-order-mapping/">Higher Order Observables</a> family of operators and we use it to map every value to an Observable. Whenever a new value comes (a user presses a key) — it waits for the previous Observable to complete (a sequence is played out) before proceeding to create the next sequence Observable. This way we can type really fast and we&#39;ll never lose a letter in transition.</p><p>For the sake of a demo let’s also make a simple service to send those sequences. We will use it to send letter with mouse and virtual on-screen keyboard:</p><pre>@Injectable({<br>  providedIn: &#39;root&#39;<br>})<br>export class MorseService extends Subject&lt;readonly (0 | 1)[]&gt; {<br>  constructor(<br>    @Inject(MAP) private readonly chars: Map&lt;string, readonly (0 | 1)[]&gt;<br>  ) {<br>    super();<br>  }<br>  <br>  send(char: string) {<br>    this.next(this.chars.get(char));<br>  }<br>}</pre><p>Now we can make a token that translates letters into Morse code sequence. First we get our mapping table and unit duration from the DI, as well as the service from the snippet above. Next we map all keydown events from document to their corresponding sequences from the table. We merge those key events with emissions from the service and use concatMap to losslessly turn them into a stream of 0s and 1s. We end each character sequence with a space sequence (<em>three</em> 0s in a row):</p><pre>export const MORSE = new InjectionToken&lt;Observable&lt;0 | 1&gt;(<br>  &#39;A sequence of Morse code&#39;, <br>  {<br>    factory: () =&gt; {<br>      const chars = inject(MAP);<br>      const duration = inject(UNIT);<br>      const service$ = inject(MorseService);<br>      const keydown$ = fromEvent(inject(DOCUMENT), &#39;keydown&#39;).pipe(<br>        map(({ key }: KeyboardEvent) =&gt; chars.get(key)),<br>        filter(Boolean)<br>      );<br>	<br>      return merge(service$, keydown$).pipe(<br>        concatMap(sequence =&gt;<br>          from(sequence).pipe(<br>            endWith(...SPACE),<br>            delayEach(duration)<br>          )<br>        ),<br>        share(),<br>      );<br>    }<br>  }<br>);</pre><p>Note usage of from instead of of. When used with <em>Arrays</em> it would turn them into sequence, whereas of will make a single emission with an entire array.</p><p>There’s a custom operator in there — delayEach. It delays every emission from Observable, rather than an entire stream:</p><pre>function delayEach&lt;T&gt;(duration: number): MonoTypeOperatorFunction&lt;T&gt; {<br>  return concatMap(x =&gt; of(x).pipe(delay(duration)));<br>}</pre><p>Now we can listen to the incoming Morse code by injecting MORSE token and subscribing!</p><h3>Decoding</h3><p>Decoding is much more fun. We need to create an operator to compare values with a sequence. It must reset if a value does not equal to the digit in the corresponding sequence position, emit a letter after the whole sequence is finished, followed by three 0s indicating the end of a letter. Then it repeats the same pattern.</p><p>We terminate and restart the stream so emission indices match those of our sequence.</p><p>We also want to visualize it so let’s create a special module for characters. It will consist of a <em>Component</em>, <em>Directive</em> and a <em>Service</em>. Why do we need all those? For a good logic separation. We need to know what character we are working with. But we need it in both <em>Component</em> and <em>Service</em>. To avoid cyclic dependency we will create a <em>Directive</em> with single purpose — let us know the character:</p><pre>@Directive({<br>  selector: &#39;[char]&#39;<br>})<br>export class CharDirective {<br>  @Input() char = &#39;&#39;;<br>}</pre><p>We can use the same selector for a <em>Component</em>. We will use it to send signals with mouse and to visualize our demo:</p><pre>@Component({<br>  selector: &#39;[char]&#39;,<br>  templateUrl: &#39;char.template.html&#39;,<br>  styleUrls: [&#39;char.style.less&#39;],<br>  providers: [CharService]<br>})<br>export class CharComponent {<br>  constructor(<br>    @Inject(CharService) readonly service: Observable&lt;number | string&gt;,<br>    @Inject(CharDirective) readonly directive: CharDirective,<br>    @Inject(MorseService) private readonly emitter: MorseService<br>  ) {}<br><br>  @HostListener(&#39;click&#39;)<br>  onClick() {<br>    this.emitter.send(this.directive.char);<br>  }<br>}</pre><p>Now we need to implement actual decoding. Notice that CharService has the type of Observable&lt;number | string&gt;. It will emit decoding progress as numbers between 0 and 1 and the letter once the decoding is complete. We will later display decoding progress for each character in the demo app.</p><h3>Service</h3><p>Creating this service is the hardest part. I’m not gonna lie, it took me quite some time to figure this RxJS puzzle out. Biggest difficulty came from space character. It looks a lot like a character end sequence which is also a bunch of consequent 0s.</p><p>We will start by creating all the helper functions. Our service extends Observable and it will have a private internal stream based on Morse code Observable we created earlier:</p><pre>@Injectable()<br>export class CharService extends Observable&lt;number | string&gt; {<br>  private readonly inner$ = this.morse$.pipe(<br>    // ...<br>  )<br><br>  constructor(<br>    @Inject(MORSE) private readonly morse$: Observable&lt;0 | 1&gt;,<br>    @Inject(MAP) private readonly chars: Map&lt;string, readonly (0 | 1)[]&gt;,<br>    @Inject(CharDirective) private readonly directive: CharDirective<br>  ) {<br>    super(subscriber =&gt; this.inner$.subscribe(subscriber));<br>  }<br>}</pre><p>We need to know what <strong>sequence</strong> are we looking for. It has to be a <em>getter</em> and not a <em>read-only property</em> because at the construction time <strong>Directive input has not been processed yet</strong>:</p><pre>private get sequence(): readonly (0 | 1)[] {<br>  return [...this.chars.get(this.directive.char), ...SPACE];<br>}</pre><p>If it was a static string value and not an input we could have changed <em>Directive</em> to @Attribute instead of @Input and get its value in constructor. But then we will not be able to create them in *ngFor.</p><p>There’s one thing that we do not need and that is — <strong>recreating the </strong>Array<strong> everytime we request it</strong>. For this purposes we can use @tuiPure decorator from <a href="https://taiga-ui.dev/">Taiga UI</a>. We use it a lot in our library for <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get#smart_self-overwriting_lazy_getters">lazy getters</a>. This is a <em>memoization</em> pattern for delayed computation. After the value is requested for the first time we replace getter with a plain property containing the result. All we need to do is add decorator on top of the getter.</p><p>It works on methods too. It would check for arguments and if they all match arguments from the previous call it would just return the previous result, without calling the method.</p><p>We also need to check that value at particular index of the sequence matches the given value. This way we would check that our Morse code stream matches the sequence we are looking for:</p><pre>private isValid(value: number, index: number): boolean {<br>  return this.sequence[index] === value;<br>}</pre><p>Next we need a value that the service is going to emit. We want to know the progress of decoding and the decoded letter:</p><pre>private getValue(index: number): number | string {<br>  return this.sequence.length === index + 1 <br>    ? this.char <br>    : (index + 2) / this.sequence.length;<br>}</pre><p>With all those helpers we can build internal stream:</p><pre>private readonly inner$ = this.morse$.pipe(<br>  takeWhile(this.isValid.bind(this)),<br>  map((_, index) =&gt; this.getValue(index)),<br>  startWith(0),<br>  endWith(0),<br>  takeWhile(isNumber, true),<br>)</pre><p>We have two takeWhile operators. First one terminates the stream if the sequence didn&#39;t match. Second is used to terminate if a letter went through (isNumber is a simple helper to check type).</p><blockquote>Second argument for takeWhile lets value that broke the condition though as well.</blockquote><h3>Restarting the stream</h3><p>Terminating and restarting the stream makes it easy for us to check that sequences match. It makes emission indices in sync with the sequence we are looking for. Once we matched the whole sequence and a character went through (second takeWhile) we can just restart. But if decoding failed (the sequences didn&#39;t match) — we need to wait for the new character. It means that we must wait for <strong>three consecutive 0s</strong>.</p><p>My initial thought on restarting failed decoding was to use repeatWhen. But the trick with repeatWhen is that its factory function is only called once upon first termination. Then it continues to listen to the Observable that it returned:</p><pre>repeatWhen(() =&gt; threeZeroes$),</pre><p>This means that it would work well for us only for the second and all forthcoming restarts. But at the first one, those <em>three</em> 0s indicating the end of the letter <strong>have already passed by the time we start to listen to this restart stream</strong>.</p><p>First, let’s add code to listen for <em>three consecutive 0s</em>. It’s a great case for scan which is a lot like reduce in Array but runs on the fly (RxJS operator reduce is the same but it is only triggered upon completion).</p><pre>export function consecutive&lt;T&gt;(<br>  value: T,<br>  amount: number<br>): OperatorFunction&lt;T, unknown&gt; {<br>  return pipe(<br>    startWith(...new Array(amount).fill(value)),<br>    scan((result, item) =&gt; (item === value ? ++result : 0), 0),<br>    filter(v =&gt; v &gt;= amount),<br>  );<br>}</pre><p>We use startWith with three 0s to kickstart the stream. Then we add up the number of consecutive 0s and only let through 3 or more. We cannot use <em>exactly</em> 3 because then space character (which translates to seven 0s) would break all other letters.</p><p>Now we need to separate two takeWhile operators. We want first one to terminate some <em>inner</em> stream. This means we need a Higher Order Observable again and concatMap is here again to save the day:</p><pre>private readonly inner$ = this.morse$.pipe(<br>  consecutive(0, SPACE.length),<br>  concatMapTo(this.morse$.pipe(<br>    takeWhile(this.isValid.bind(this)),<br>    map((_, index) =&gt; this.getValue(index)),<br>    startWith(0),<br>    endWith(0),<br>  )),<br>  takeWhile(isNumber, true),<br>  repeat(),<br>)</pre><p>Now when inner stream is terminated by failed decoding <em>three consecutive 0s</em> will restart it. Space character causes multiple groups of <em>three consecutive 0s</em> but they all will wait for internal stream to complete and will be dismissed once space character restarts the whole stream through second takeWhile.</p><h3>Demo</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/788/0*Uxc-eHc-cK_6jhEu.png" /></figure><p>We got everything ready, now it’s time to create a demo app! It will listen to keyboard presses and display a virtual keyboard. Demo will also print received message on screen and each letter button will display decoding progress.</p><p>Since it’s a telegraph with Morse code it just wouldn’t feel authentic without the sound. And simple bleeps are easy to create with <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API">Web Audio API</a> and <a href="https://developer.mozilla.org/en-US/docs/Web/API/OscillatorNode">OscillatorNode</a> in particular. We can use my wrapper library <a href="https://github.com/ng-web-apis/audio">@ng-web-apis/audio</a>.</p><p>It’s a part of our open-source initiative <strong>Web APIs for Angular</strong>. Our goal is to build high quality lightweight wrappers for native Web APIs for idiomatic use with Angular. Check out <a href="https://ng-web-apis.github.io/">what we have so far</a>!</p><p>With it we can turn declarative Angular directives into Web Audio API node graph:</p><pre>&lt;ng-container waOscillatorNode autoplay frequency=&quot;523&quot;&gt;<br>  &lt;ng-container<br>    waGainNode<br>    gain=&quot;0&quot;<br>    [gain]=&quot;morse$ | async | waAudioParam : 0.02&quot;<br>  &gt;<br>    &lt;ng-container waAudioDestinationNode&gt;&lt;/ng-container&gt;<br>  &lt;/ng-container&gt;<br>&lt;/ng-container&gt;</pre><p>It would play a bleep for 1s and be silent for 0s by controlling the volume. Now let’s layout our keyboard and decoded output. It also makes sense to add «Clear» button:</p><pre>&lt;section&gt;<br>  &lt;button *ngFor=&quot;let char of chars&quot; type=&quot;button&quot; [char]=&quot;char&quot;&gt;<br>    {{char}}<br>  &lt;/button&gt;<br>&lt;/section&gt;<br>&lt;output&gt;{{ output$ | async }}&lt;/output&gt;<br>&lt;footer&gt;<br>  &lt;button type=&quot;reset&quot; (click)=&quot;reset$.next()&quot;&gt;Clear&lt;/button&gt;<br>&lt;/footer&gt;</pre><p>But how do we get the output$? By querying services from template! Did you know that Angular queries can reach for services and other instances from node injectors?</p><pre>@ViewChildren(CharService)<br>readonly services: QueryList&lt;Observable&lt;string | number[]&gt;&gt;</pre><blockquote>Keep in mind they are unavailable until ngAfterViewInit hook!</blockquote><p>We can compose output$ stream this way:</p><pre>readonly reset$ = new Subject&lt;void&gt;();<br><br>readonly output$ = this.reset$.pipe(<br>  switchMap(() =&gt; merge(...this.services.toArray()).pipe(<br>    filter(x =&gt; !isNumber(x)),<br>    scan((result, char) =&gt; result + char, &#39;&#39;),<br>    startWith(&#39;&#39;),<br>  )),<br>  startWith(&#39;&#39;),<br>);<br><br>ngAfterViewInit() {<br>  this.reset$.next();<br>}</pre><p>And we also want to indicate decoding progress. To do so we will add a simple span block to our CharComponent and control its height and color with these two streams:</p><pre>readonly progress$ = this.service.pipe(<br>  filter(isNumber),<br>  map(value =&gt; value * 100),<br>);<br><br>readonly pending$ = this.service.pipe(<br>  filter(Boolean),<br>  map(isNumber),<br>);</pre><p>And one last neat trick I wanted to show is controlling CSS transitions with the same token we create for UNIT duration! Since Angular moved to Ivy renderer we are able to bind to CSS variables, so lets use --duration in our styles and add this to our main component:</p><pre>@Component({<br>  // ...<br>  host: {<br>    &#39;[style.--tui-duration]&#39;: &#39;unit + &quot;ms&quot;&#39;,<br>  },<br>})<br>export class AppComponent implements AfterViewInit {<br>  constructor(<br>    // ...<br>    @Inject(UNIT) readonly unit: number,<br>  ) {}<br><br>  // ...<br>}</pre><p>Everything is finished and you are welcome to try the final product!</p><p><strong>Note</strong>: sound doesn’t work in embeds, click “Edit on StackBlitz” to open it in a separate tab to enjoy glorious Web Audio API 😉</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fstackblitz.com%2Fedit%2Frxjs-telegraph%3Fembed%3D1&amp;display_name=StackBlitz&amp;url=https%3A%2F%2Fstackblitz.com%2Fedit%2Frxjs-telegraph&amp;image=https%3A%2F%2Fsocial-img.staticblitz.com%2Fprojects%2Frxjs-telegraph%2F73037b2259f285543f7dd304eddb4676&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=stackblitz" width="745" height="400" frameborder="0" scrolling="no"><a href="https://medium.com/media/2a47e140dec32fb492d4ddbd703f8a8b/href">https://medium.com/media/2a47e140dec32fb492d4ddbd703f8a8b/href</a></iframe><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5c74e2f585b3" width="1" height="1" alt=""><hr><p><a href="https://medium.com/angularwave/telegraph-with-rxjs-5c74e2f585b3">Telegraph with RxJS</a> was originally published in <a href="https://medium.com/angularwave">pineopticone</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Tracking user interaction area]]></title>
            <link>https://medium.com/angularwave/tracking-user-interaction-area-6ad3ab7f0c8b?source=rss-3dd21e4659f0------2</link>
            <guid isPermaLink="false">https://medium.com/p/6ad3ab7f0c8b</guid>
            <category><![CDATA[dependency-injection]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[rxjs]]></category>
            <category><![CDATA[angular]]></category>
            <dc:creator><![CDATA[Alexander Inkin]]></dc:creator>
            <pubDate>Wed, 06 Sep 2023 14:15:28 GMT</pubDate>
            <atom:updated>2023-09-06T14:15:28.004Z</atom:updated>
            <content:encoded><![CDATA[<p>It is often necessary to know what area of the page the user is interacting with. For example, if you are making a dropdown — you want to know when to close it. A naïve implementation would just to listen to clicks and check if it happened outside of the dropdown element. But the mouse is not the only way of interaction. A person can use a keyboard to navigate focusable elements. And a dropdown can have a nested multi-level menu which makes plain click target checking problematic.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LUuY3QHWGtqcZMsGaoSA-Q.png" /></figure><p>In this article, let’s explore <strong>ActiveZone</strong> directive — an approach we took in <a href="https://taiga-ui.dev/">Taiga UI</a> Angular components library. It employs two of my favorite Angular features: dependency injection and RxJS. We will also need a thorough understanding of native DOM events. However abstracted from plain JavaScript Angular might be, it still relies on good old Web APIs so it is important to grow your knowledge of vanilla front-end.</p><h3>Outlining the task</h3><p>Basic usage example can be a button with a dropdown menu:</p><pre>&lt;button <br>  [dropdownOpen]=&quot;open&quot;<br>  [dropdownContent]=&quot;template&quot; <br>  (activeZoneChange)=&quot;onActiveZone($event)&quot;<br>  (click)=&quot;onClick()&quot;<br>&gt;<br>  Show menu<br>  &lt;ng-template #template&gt;<br>    &lt;some-menu-component activeZone&gt;&lt;/some-menu-component&gt;<br>  &lt;/ng-template&gt;<br>&lt;/button&gt;</pre><p>Let’s assume menu is rendered <a href="https://medium.com/its-tinkoff/demystifying-taiga-ui-root-component-portals-pattern-in-angular-e9463bc2b584?sk=406daf012a6fea89ee80e451a7baf5eb">in a portal</a> and is not a direct descendant of button. We want to open and close menu by a click on the button. And if user clicks away or moves focus with a <em>tab</em> key — a dropdown must close.</p><p>To track user interactions we will keep an eye mainly on the following events: focusin, focusout and mousedown.</p><blockquote>focus/blur events don&#39;t bubble, unlike focusin/focusout</blockquote><p>Keyboard navigation can only occur on focusable elements, while mousedown can be triggered by user selecting some text inside a dropdown, for example or interacting with non-focusable elements on the page. So those 3 events cover nearly everything we care about. But there are some tricky corner cases we will explore down the line.</p><blockquote>Focus loss happens after mousedown event as a default behavior. If you call preventDefault() on this event, focus will remain where it is.</blockquote><p>To achieve our goal it makes sense to always know what is the current element user is interacting with. In Angular world this means having an Observable of an <em>active element. </em>A very important clarification: we want it to be synchronous. This means, if a developer calls element.blur() in some of their code, on the very next line we must already know that we left the zone. This makes our task tenfold trickier and soon you will know why!</p><h3>Dependency Injection token</h3><p>To be able to reach for active element Observable anywhere within our app, we will turn it into an InjectionToken. It is global by default and provides a handy factory option which we can use to construct our stream. It will be called once somebody first injects the token.</p><p>Let’s make a first draft. First we will create all the constants we need:</p><pre>export const ACTIVE_ELEMENT = new InjectionToken(<br>  &#39;An element that user is currently interacting with&#39;,<br>  {<br>    factory: () =&gt; {<br>      const documentRef = inject(DOCUMENT);<br>			const windowRef = documentRef.defaultView;<br>      const focusout$ = fromEvent(windowRef, &#39;focusout&#39;);<br>      const focusin$ = fromEvent(windowRef, &#39;focusin&#39;);<br>      const mousedown$ = fromEvent(windowRef, &#39;mousedown&#39;);<br>      const mouseup$ = fromEvent(windowRef, &#39;mouseup&#39;);<br><br>      // ... continue below ↓↓↓<br>    }<br>  },<br>);</pre><p>Now let’s combine those Observables into an Element stream. Since focus loss naturally happens after mousedown event we will stop listening to focusout after mousedown and repeat it after mouseup. That is why we need mouseup$ stream — this way we isolate all mouse related activity from focus related:</p><pre>const loss$ = focusout$.pipe(<br>  takeUntil(mousedown$),<br>  repeatWhen(() =&gt; mouseup$),<br>  map(({ relatedTarget }) =&gt; relatedTarget)<br>);<br><br>// ... continue below ↓↓↓</pre><blockquote><strong>relatedTarget</strong> for the focusout event is an element that focus is about to be moved to or null if focus is going nowhere.</blockquote><p>In terms of focus <em>gain</em> we just need to map focusin event to the target:</p><pre>const gain$ = focusin$.pipe(map(({ target }) =&gt; target));<br><br>// ... continue below ↓↓↓</pre><p>Mouse interaction is more complicated. On each mousedown event we check if something is currently focused. If not (activeElement equals to body) we simply map mousedown event to its target. If something is focused we start to listen to focusout event, expecting focus loss. We use mapTo to turn this event into the mousedown target as well. But if default action was prevented and focus remained where it was, we stop waiting for focusout event on the next frame (timer(0)):</p><pre>const mouse$ = mousedown$.pipe(<br>  switchMap(({ target }) =&gt;<br>    documentRef.activeElement === documentRef.body<br>      ? of(target)<br>      : focusout$.pipe(<br>        take(1),<br>        takeUntil(timer(0)),<br>        mapTo(target)<br>      )<br>    )<br>);<br><br>// ... continue below ↓↓↓</pre><p>Next let’s merge all those streams together. Now we have an Observable of an Element user currently interacts with:</p><pre>return merge(loss$, gain$, mouse$).pipe( <br>  distinctUntilChanged(),<br>  share() <br>);</pre><p>We pipe the whole stream through distinctUntilChanged and share operators so there are no extra emission and subscriptions happening.</p><h3>ActiveZone directive</h3><p>Now that we have the stream, let’s make a simple directive. It would map the stream to boolean letting us know if user is currently interactive with a given area. It would also reach through DI for parent directive of the same kind and register itself as its <em>child zone</em>. This way we can handle nested dropdowns mentioned in the beginning of the article.</p><p>Let’s see how we can do this:</p><pre>&lt;button (activeZoneChange)=&quot;onActiveZone($event)&quot;&gt;<br>  Show menu<br>  &lt;ng-template #template&gt;<br>    &lt;!-- <br>      &quot;activeZone&quot; injects parent directive &quot;activeZoneChange&quot;<br>      from the button above, even if template is instatiated<br>      in a different place in the DOM<br>    --&gt;<br>    &lt;some-menu-component activeZone&gt;&lt;/some-menu-component&gt;<br>  &lt;/ng-template&gt;<br>&lt;/button&gt;</pre><p>This DI nesting can be any levels deep, that’s how nested menus and dropdowns can still be parts of the topmost zone. Now let’s write the actual directive:</p><pre>@Directive({<br>  selector: &#39;[activeZone],[activeZoneChange]&#39;<br>})<br>export class ActiveZoneDirective implements OnDestroy {<br>  private children: readonly ActiveZoneDirective[] = [];<br><br>  constructor(<br>    @Inject(ACTIVE_ELEMENT)<br>    private readonly active$: Observable&lt;Element&gt;,<br>    private readonly elementRef: ElementRef&lt;Element&gt;,<br>    @Optional()<br>    @SkipSelf()<br>    private readonly parent: ActiveZoneDirective | null,<br>  ) {<br>    this.parent?.addChild(this);<br>  }<br><br>  ngOnDestroy() {<br>    this.parent?.removeChild(this);<br>  }<br><br>  contains(node: Node): boolean {<br>    return (<br>      this.elementRef.nativeElement.contains(node) ||<br>      this.children.some(item =&gt; item.contains(node))<br>    );<br>  }<br><br>  private addChild(activeZone: ActiveZoneDirective) {<br>    this.children = this.children.concat(activeZone);<br>  }<br><br>  private removeChild(activeZone: ActiveZoneDirective) {<br>    this.children = this.children.filter(item =&gt; item !== activeZone);<br>  }<br>}</pre><p>This directive has only one public method — contains which is used to check if an element is located within current zone or any of its children. Now let&#39;s add an @Output. Since Angular outputs are Observables we can simply use our stream and pipe it:</p><pre>@Output()<br>readonly activeZoneChange = this.active$.pipe(<br>  map(element =&gt; this.contains(element)),<br>  startWith(false),<br>  distinctUntilChanged(),<br>  skip(1),<br>);</pre><p>Every new active element is checked against our directive, the stream starts with false so that distinctUntilChanged will not let through subsequent false results. We also skip the starting value so it doesn&#39;t immediately emit.</p><h3>Potholes and pitfalls</h3><p>Where’s the fun in everything working from the get go? The code above is pretty clean and functional, however there are certain cases where it will fail. Let’s explore them and expand our solution to cover them all.</p><h4>☹️ iframe</h4><p>There’s a frustrating behavior when using iframe. Whenever you click it, mousedown event does not happen. This means that if we have some nested iframe on our page and user clicks it, we will not know that they left the active zone. Thankfully, a blur event is dispatched on window when we start interacting with an iframe. It makes sense — we left the window to work with a nested one.</p><p>During most of the focus events if you were to try and check document.activeElement you will find body there. However in this particular case, inside blur event callback active element is already the clicked iframe. So all we need to do is amend our stream by including this in the merge:</p><pre>const iframe$ = fromEvent(windowRef, &#39;blur&#39;).pipe(<br>  map(() =&gt; documentRef.activeElement),<br>  filter(element =&gt; !!element &amp;&amp; element.matches(&#39;iframe&#39;)),<br>);</pre><blockquote>Another case when activeElement is not body inside focusout event is when we leave the tab. We will use it later so our dropdowns won&#39;t close when we go to DevTools!</blockquote><h4>😫 ShadowDOM</h4><p>When working with Web Components or just ShadowDOM in general, you might have multiple focusable elements inside. window will not know about focus transitions within the shadow root. document.activeElement will remain the same — the shadow root element. And that shadow root will have its own activeElement to track the real focused element. Moreover, the target of all our events will not be the real element. It will be the shadow root as well. However, real target will be exposed to us through <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/composedPath">composedPath method</a> on event.</p><blockquote><em>It will not work for closed shadow roots but it’s the best we can do</em></blockquote><p>So we need a utility function to retrieve the actual target. To reach for activeElement within ShadowDOM we will also need a function to get <a href="https://html.spec.whatwg.org/multipage/interaction.html#dom-documentorshadowroot-activeelement-dev">DocumentOrShadowRoot</a>.</p><p>Let’s add them both:</p><pre>function getActualTarget(event: Event): EventTarget {<br>  return event.composedPath()[0];<br>}<br><br>function getDocumentOrShadowRoot(node: Node): Node {<br>  return node.isConnected ? node.getRootNode() : node.ownerDocument;<br>}</pre><blockquote><em>We need to check </em><em>isConnected because nodes detached from DOM will return the topmost element in their structure as a root node. For detached nodes it returns false and we get their </em><em>document.</em></blockquote><p>Let’s add one more function to track focus within a shadow root:</p><pre>function shadowRootActiveElement(root: Node): Observable&lt;EventTarget&gt; {<br>  return merge(<br>    fromEvent(root, &#39;focusin&#39;).pipe(map(({target}) =&gt; target)),<br>    fromEvent(root, &#39;focusout&#39;).pipe(map(({relatedTarget}) =&gt; relatedTarget)),<br>  );<br>}</pre><p>Now that we have those helpers let’s rewrite our focusin handler:</p><pre>const gain$ = focusin$.pipe(<br>  switchMap(event =&gt; {<br>    const target = getActualTarget(event);<br>    const root = getDocumentOrShadowRoot(target);<br><br>    return root === documentRef<br>      ? of(target)<br>      : shadowRootActiveElement(root).pipe(startWith(target));<br>  }),<br>);</pre><p>If focus is moving inside a shadow root we start listening to those encapsulated focus events, otherwise we just return target as before. For the mousedown event it is enough to just use getActualTarget.</p><h4>😭 Deletion and disable</h4><p>Not every focus loss should be considered a departure from zone. When we explicitly call .blur() on a focused element — it&#39;s a good case to call leaving the zone. However, when you click a button and it becomes disabled, for example by triggering some loading process, <strong>Chrome</strong> would also dispatch focusout event. Same goes for a button that removes itself (or its container) on click. When this happens inside a dropdown, most likely we do not want to close it automatically.</p><blockquote><em>As far as I know there’s no way to tell an </em><em>element.blur() call from a </em><em>blur event that was caused by element being removed from DOM.</em></blockquote><p>Checking disabled is easy, but removal from DOM is a hard nut. Remember, we have to do it <strong>synchronously</strong>. We cannot just wait and check if element disappears in the next frame. This time, I&#39;m afraid we will have to resort to a workaround. <a href="https://github.com/TinkoffCreditSystems/taiga-ui">Taiga UI</a> requires you to use <a href="https://angular.io/guide/animations">Angular animations</a>. And <a href="https://github.com/angular/angular/blob/master/packages/animations/browser/src/render/animation_engine_next.ts#L25-L26">AnimationEngine knows what element is being removed</a>. Unfortunately there&#39;s no way to reach it because it is not exposed. So we will have to use <em>private API</em> for this. This is <strong>bad</strong>. But there&#39;s nothing else we can do in <strong>Chrome</strong>. And this hasn&#39;t changed since the introduction of Angular animations. Let&#39;s make a stream of an element being removed:</p><pre>const REMOVED_ELEMENT = new InjectionToken&lt;Observable&lt;Element | null&gt;&gt;(<br>  &#39;Element currently being removed by AnimationEngine&#39;,<br>  {<br>    factory: () =&gt; {<br>      const stub = {onRemovalComplete: () =&gt; {}};<br>      const element$ = new BehaviorSubject&lt;Element | null&gt;(null);<br>      const engine = inject(ɵAnimationEngine, { optional: true }) ?? stub;<br>      const {onRemovalComplete = stub.onRemovalComplete} = engine;<br><br>      engine.onRemovalComplete = (element, context) =&gt; {<br>        element$.next(element);<br>        onRemovalComplete(element, context);<br>      };<br><br>      return element$.pipe(<br>        switchMap(element =&gt; timer(0).pipe(<br>          mapTo(null), <br>          startWith(element)<br>        )),<br>        share(),<br>      );<br>    },<br>  },<br>);</pre><p>Let’s break down what we’re doing here. We make a simple stub and optionally inject AnimationEngine with a fallback just in case. We substitute onRemovalComplete with our own method to notify the BehaviorSubject we created. Comment on onRemovalComplete reads:</p><pre>// this method is designed to be overridden by the code that uses this engine</pre><p>So we pretty much do what we’re supposed to. If AnimationEngine was exposed.</p><p>Then we add a simple switchMap to reset our stream back to null on the next frame.</p><p>Let’s add this new stream to our chain and write a utility function to check if we want to react to a particular focusout event or not:</p><pre>const loss$ = focusout$.pipe(<br>  takeUntil(mousedown$),<br>  repeatWhen(() =&gt; mouseup$),<br>  withLatestFrom(inject(REMOVED_ELEMENT)),<br>  filter(([event, removedElement]) =&gt;<br>    isValidFocusout(getActualTarget(event), removedElement),<br>  ),<br>  map(([{relatedTarget}]) =&gt; relatedTarget),<br>);<br><br>// ...<br><br>function isValidFocusout(target: any, removed: Element | null): boolean {<br>  return (<br>    // Not due to switching tabs/going to DevTools<br>    target.ownerDocument?.activeElement !== target &amp;&amp;<br>    // Not due to button/input becoming disabled<br>    !target.disabled &amp;&amp;<br>    // Not due to element being removed from DOM<br>    (!removed || !removed.contains(target))<br>  );<br>}</pre><h3>End result</h3><p>That was probably a lot to digest. This is not something one comes up with instantly when given this task. Such things are built gradually. You can see this whole solution in action in the StackBlitz below, with all the mentioned corner cases:</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fstackblitz.com%2Fedit%2Factive-zone-directive%3Fembed%3D1&amp;display_name=StackBlitz&amp;url=https%3A%2F%2Fstackblitz.com%2Fedit%2Factive-zone-directive&amp;image=https%3A%2F%2Fsocial-img.staticblitz.com%2Fprojects%2Factive-zone-directive%2F230bee7350c62bd0e00e910e01754e6a&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=stackblitz" width="745" height="400" frameborder="0" scrolling="no"><a href="https://medium.com/media/b3589fe527166144472aff008872831f/href">https://medium.com/media/b3589fe527166144472aff008872831f/href</a></iframe><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6ad3ab7f0c8b" width="1" height="1" alt=""><hr><p><a href="https://medium.com/angularwave/tracking-user-interaction-area-6ad3ab7f0c8b">Tracking user interaction area</a> was originally published in <a href="https://medium.com/angularwave">pineopticone</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: A year in Open Source]]></title>
            <link>https://medium.com/angularwave/taiga-ui-a-year-in-open-source-3f0ea3cb52df?source=rss-3dd21e4659f0------2</link>
            <guid isPermaLink="false">https://medium.com/p/3f0ea3cb52df</guid>
            <category><![CDATA[angular]]></category>
            <category><![CDATA[open-source]]></category>
            <dc:creator><![CDATA[Alexander Inkin]]></dc:creator>
            <pubDate>Tue, 05 Sep 2023 19:43:46 GMT</pubDate>
            <atom:updated>2023-09-05T19:43:46.476Z</atom:updated>
            <content:encoded><![CDATA[<blockquote>This is <a href="https://dev.to/angular/taiga-ui-a-year-in-open-source-416l">a repost</a> in an effort to collect all my articles under Medium. This is an old article, at the moment you can also read <a href="https://dev.to/angular/taiga-ui-year-of-the-tiger-9ih">second year overview</a> by my colleague</blockquote><p><a href="https://taiga-ui.dev/">Taiga UI</a> is a huge Angular components library. We at <a href="https://www.tinkoff.ru/eng">Tinkoff</a> were developing it closed-source for a couple of years before finally going public with a second major version. It’s been a year since our open source release and today I want to look back at this part of our journey and lay down some plans for 2022. Let’s go!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/0*MG-yU6g1SRvDHQH8.jpg" /></figure><h3>Open source</h3><p>Over this time the community started to build up. One benefit for going <a href="https://github.com/taiga-family/taiga-ui">open source on GitHub</a> is contribution made simpler even for colleagues from different projects. The environment is familiar, no extra authentication or VPN required and the pipeline is clear.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/512/0*S_PUBf-Wu_bDiasd.png" /></figure><p>We also spent quite some time to improve our processes utilizing many different tools to help us ship new versions safer and faster.</p><p>Taiga UI is a monorepo that includes several libraries and a demo application that we deploy using GitHub Pages. Now it is managed by <a href="https://nx.dev/">NX tools</a> that provide a lot of useful commands and increase speed of building and testing at both local development and CI/CD pipelines. If you plan on working with monorepos you should definitely try NX!</p><p>Whenever a Pull Request is created we need to be able to quickly checkout the changes. Reading code diff is great, but sometimes you just need to tinker with the new version, test it on mobile, different browsers and OS. Cloud services are perfect for this case, they allow you to deploy the code temporarily and access it with a link from any device. We chose <a href="https://firebase.google.com/">Firebase</a> to host it for us and a GitHub action posts a link to the deployment as a comment in the Pull Request. It works like a charm and speeds up code reviews a lot. Read <a href="https://firebase.google.com/docs/hosting/github-integration">this article</a> to set it up on your repository!</p><p>Moreover, every PR runs a set of <a href="https://www.cypress.io/">Cypress</a> screenshot tests that iterates over the demo portal and compares current components with their reference from the main branch. Cypress has a commercial solution where you can easily access failed screenshots through a dashboard. But we created a little <a href="https://github.com/TinkoffCreditSystems/argus">Github bot</a> that stores failed screenshots in a temporary branch and posts them to the PR as a comment, updating it on every push. This makes identifying problems with the UI library fast and easy. We opensourced this bot so you can use it too, read <a href="https://medium.com/its-tinkoff/bots-should-work-developers-should-think-writing-github-app-with-node-js-2e8eb049d7e4">this article</a> going over it in detail!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/512/0*b2qZ4uTqHDFRoeyn.png" /></figure><p>There’s also a <a href="https://github.com/typicode/husky">husky</a> precommit hook with <a href="https://github.com/eslint/eslint">ESlint</a> and <a href="https://github.com/prettier/prettier">Prettier</a> fixing and formatting changed code on every commit with <a href="https://github.com/okonet/lint-staged">lint-staged</a> which makes sure malformed code doesn’t even make it to the Pull Request. Combined with <a href="https://github.com/conventional-changelog/standard-version">standard-version</a> and strict commit messages it makes releasing and changelog generation a breeze. For a contributor the workflow is as smooth as:</p><p>fork → npm ci → npm start → code → commit → push</p><h3>What’s new?</h3><p>When 2.0 went public it already was pretty big. But over the course of 2021 we added and improved a lot of things. With ideas and help from the community we significantly expanded customization and localization capabilities. This would be very helpful for our own international projects too. Taiga UI today has over 10 languages for built-in texts, RTL support for most of the components and various formats for money and dates. We also provide “ng add” schematic to get you started smoothly with Taiga UI. Let’s look at the most notable additions to our 150+ components base this year!</p><h3>New components</h3><p><a href="https://taiga-ui.dev/components/sheet-dialog"><strong>SheetDialog</strong> </a>— mobile sliding dialog</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/333/0*F3IMokH72cNQZaUY.png" /></figure><p><a href="https://taiga-ui.dev/components/tree"><strong>Tree</strong></a> — a flexible component to display tree-like data structures</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/496/0*C6QulGmdlq6SBot6.png" /></figure><p><a href="https://taiga-ui.dev/components/pdf-viewer"><strong>PdfViewer</strong></a> — a custom dialog to preview PDFs in an iframe</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/512/0*L_8OWUpH5hBbXM-L.png" /></figure><p><a href="https://taiga-ui.dev/components/carousel"><strong>Carousel</strong></a> — a common UI pattern for slider of images</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/512/0*avb5fO26sQ5dqh37.png" /></figure><p><a href="https://taiga-ui.dev/components/arc-chart"><strong>ArcChart</strong></a> — a new way to display numerical data</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/512/0*fZ4Nx4Zzd1vh3w3o.png" /></figure><h3>New add-on packages</h3><p><a href="https://taiga-ui.dev/components/table"><strong>Table</strong></a> — an interactive table component and related utilities</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/512/0*6scaaAQLlr726Uds.png" /></figure><p><a href="https://taiga-family.github.io/tui-editor"><strong>Editor</strong> </a>— a rich text editor based on <a href="https://tiptap.dev/">tiptap 2</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/512/0*93snPL2o14Dk1vCY.png" /></figure><p><a href="https://taiga-ui.dev/components/preview"><strong>Preview</strong></a> — a custom dialog to preview arbitrarily content such as images and documents</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/512/0*BQMu0SOxubNyY58H.png" /></figure><p>We’ve also added a bunch of helpful utils, tokens, services and directives, such as Pan, Swipe, DropdownHover and more and a night theme for all components:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/512/0*8s04z4FRG581bWt-.png" /></figure><h3>What’s coming?</h3><p>It was a productive year. We’ve grown a lot in infrastructure and content, we started to build community of users, most notably from cryptocurrency area as it seems from the screenshots that come with user questions 🙂 In the coming year we plan to expand our localization and internationalization options so that developers from across the globe can enjoy Taiga UI and use it in whatever project they are working on. Here’s a brief overview of what we plan to focus on in 2022:</p><ul><li>Improve and stabilize new Editor add-on package</li><li>Make all native inputs accessible from the outside of Taiga UI controls so it’s easy to assign attributes and listen to native events</li><li>Rework some of our internals to rely more on CSS and less on JavaScript which would reduce change detection cycles</li><li>Utilize DI more for customization</li><li>Drop dependency on global styles</li><li>Create ComponentHarnesses for Taiga UI components so they can be easily tested</li><li>Support different implementations of notifications, like we do now for dialogs</li><li>Reduce tech debt such as old components waiting for refactor and outdated mask library</li><li>Prepare for the next major release with Angular 13 and Ivy distribution</li></ul><p>I hope you’re happy with Taiga UI and if you didn’t try it — please give it a shot! Feel free to open <a href="https://github.com/TinkoffCreditSystems/taiga-ui/discussions">GitHub discussion</a> or contact us over at <a href="https://discord.gg/zrB2EdJjEy">Angular Discord</a>. For Russian speaking users we have a <a href="https://t.me/taiga_ui">Telegram chat</a>. Our issues list is open for feature requests and bug reports. And it also always has <a href="https://github.com/TinkoffCreditSystems/taiga-ui/issues?q=is%3Aissue+is%3Aopen+label%3A%22contributions+welcome%22">some things for you to try and solve</a> if you want to contribute!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3f0ea3cb52df" width="1" height="1" alt=""><hr><p><a href="https://medium.com/angularwave/taiga-ui-a-year-in-open-source-3f0ea3cb52df">Taiga UI: A year in Open Source</a> was originally published in <a href="https://medium.com/angularwave">pineopticone</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[DOM, DI and View: the trees of Angular]]></title>
            <link>https://medium.com/angularwave/dom-di-and-view-the-trees-of-angular-2ef959e7fafd?source=rss-3dd21e4659f0------2</link>
            <guid isPermaLink="false">https://medium.com/p/2ef959e7fafd</guid>
            <category><![CDATA[angular]]></category>
            <category><![CDATA[dependency-injection]]></category>
            <category><![CDATA[dom]]></category>
            <category><![CDATA[css]]></category>
            <category><![CDATA[typescript]]></category>
            <dc:creator><![CDATA[Alexander Inkin]]></dc:creator>
            <pubDate>Mon, 04 Sep 2023 04:12:53 GMT</pubDate>
            <atom:updated>2026-03-26T09:00:05.571Z</atom:updated>
            <content:encoded><![CDATA[<p>To be a productive Angular developer, you need to be fluent in various types of trees your app consists of. It is easy to mistake DI tree of injectors for DOM tree of actual HTML elements and Angular views. They seem to correspond to one another but that is not always the case. In this article we will explore the differences, learn how to spot them and work around potential difficulties.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*1rIu4XQpZ-MM2UXV.png" /></figure><h3>Our test case</h3><p>Let’s assume our-component with the following structure:</p><pre>&lt;parent&gt;<br>  &lt;child *ngFor=”let child of children”&gt;<br>    &lt;ng-container <br>      *ngTemplateOutlet=”template; context: { $implicit: child }”<br>    &gt;&lt;/ng-container&gt;<br>  &lt;/child&gt;<br>&lt;/parent&gt;</pre><p>And let’s say we use it as follows:</p><pre>&lt;our-component <br>  [children]=”children” <br>  [template]=”template”<br>&gt;&lt;/our-component&gt;<br>&lt;ng-template #template let-child&gt;<br>  &lt;some-component [item]=”child”&gt;&lt;/some-component&gt;<br>&lt;/ng-template&gt;</pre><p>We will also list parent component template to be:</p><pre>&lt;parent-header&gt;&lt;/parent-header&gt;<br>&lt;ng-content&gt;&lt;/ng-content&gt;</pre><p>And child would be similar:</p><pre>&lt;child-header&gt;&lt;/child-header&gt;<br>&lt;ng-content&gt;&lt;/ng-content&gt;</pre><p>That is probably as simple as we can get to explore everything relevant. With a single child the rendered DOM tree is rather straightforward:</p><pre>&lt;our-component&gt;<br>  &lt;parent&gt;<br>    &lt;parent-header&gt;&lt;/parent-header&gt;<br>    &lt;child&gt;<br>      &lt;child-header&gt;&lt;/child-header&gt;<br>      &lt;some-component&gt;&lt;/some-component&gt;<br>    &lt;/child&gt;<br>  &lt;/parent&gt;<br>&lt;/our-component&gt;</pre><h3>View tree</h3><p>Angular application consists of views, rather than straight up DOM elements. This allows for directives on non-existent elements, such as ng-container and interchangeable dynamic pieces of layout, such as ng-template. This subject is mostly relevant for change detection propagation. Let’s color our rendered example so we can see how it is broken down in terms of Angular views:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*DN8bNZ058sPyNvjY.png" /></figure><p>This weird breakdown is caused by <a href="https://angular.io/guide/content-projection">content projection</a> that places tags declared in one view into ng-content spot of another. Hierarchy of those views looks like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*otDc7BA_vJDrpHpk.png" /></figure><p>So even though the some-component is inside the child component which is inside the parent component it is actually a part of the top level view. That means, if you call ChangeDetectorRef.markForCheck() inside some-component which marks parent views for change detection check — it would <strong>only affect the top level view</strong>. If your components use OnPush change detection strategy, host listeners inside some-component will not trigger change detection neither for child, nor for parent and not even for our-component. A little less important, but still peculiar is that parent and child views are parallel to each other for the same reason, although they look nested in the DOM.</p><blockquote>It’s important to know that @HostBinding is exactly what it is called — a binding on the host and therefore it belongs to the parent view in terms of change detection.</blockquote><h3>DI tree</h3><p>The Injector tree in this example is a bit closer to DOM. Inside DI tree, communication can be organized with Dependency Injection pattern. Let’s quickly review those so later we can spot why they won’t always work.</p><p>When we need to get some data from the component above, we can inject it in the constructor or with the inject function made possible with Angular 14. It’s a powerful tool because it traverses the tree upward to the root so you can use it at any level of nesting. No need to drill inputs through multiple components. In <a href="https://taiga-ui.dev/">Taiga UI</a>, an open-source library my team maintains, we use directives to control similar properties for multiple components. For example the size of input fields can be declared on the parent form.</p><blockquote><em>Keep in mind with </em><em>OnPush change detection strategy your child views will not be updated if parent data changes. You might consider the controller pattern described by my colleague Roman in </em><a href="https://indepth.dev/posts/1443/how-we-make-our-base-components-more-flexible-controllers-concept-in-angular"><em>this article</em></a><em>.</em></blockquote><p>Injecting parent also allows children to communicate both ways. They can call back parent methods, passing data as arguments. For example an Accordion component can have a mode when only a single item can be expanded at a time:</p><pre>&lt;accordion&gt;<br>  &lt;accordion-item&gt;...&lt;/accordion-item&gt;<br>  &lt;accordion-item&gt;...&lt;/accordion-item&gt;<br>  &lt;accordion-item&gt;...&lt;/accordion-item&gt;<br>&lt;/accordion&gt;</pre><p>Child items can communicate with parent to let it know that an item was clicked and expanded. Parent can then tell all its other children to collapse.</p><p>If you ever implemented ControlValueAccessor you remember that child NgControl would call NG_VALUE_ACCESSOR methods providing callbacks for updating control value, touched state, etc. This is a similar approach.</p><p>Another pattern made possible with DI is somewhat reverse injecting. When your parent needs to keep track of their children and for various reasons cannot use @ViewChildren/@ContentChildren, children can instead inject parent and “register” themselves with some callback and “unregister” inside ngOnDestroy. This is how we link multiple “zones” a user can interact with across the DOM in our <strong>ActiveZone</strong> directive which I <a href="https://indepth.dev/posts/1479/tracking-user-interaction-area">explored in detail before</a>.</p><h3>Inconsistencies with DOM tree</h3><p>Now we can examine cases where differences with the DOM tree might make these solutions impossible. Consider following template:</p><pre>&lt;parent-component&gt;<br>  &lt;child-component&gt;&lt;/child-component&gt;<br>&lt;/parent-component&gt;</pre><p>This seems straightforward but what if we want to affect children from inside the parent view? Say <strong>parent</strong> template looks like this:</p><pre>&lt;div myDirective&gt;<br>  &lt;ng-content&gt;&lt;/ng-content&gt;<br>&lt;/div&gt;</pre><p>As expected, it would result in following DOM tree:</p><pre>&lt;parent-component&gt;<br>  &lt;div myDirective&gt;<br>    &lt;child-component&gt;&lt;/child-component&gt;<br>  &lt;/div&gt;<br>&lt;/parent-component&gt;</pre><p>However since <strong>content is rendered before view</strong>, child-component is not a child of myDirective in DI tree and therefore cannot inject it. This is easy to understand since content is part of the parent view. And you cannot expect the child view to render before the parent.</p><p>Another situation where we can easily end up with a DOM tree different from a DI tree is when we use ng-template. Consider following parent component template:</p><pre>&lt;child-component [template]=”tmp”&gt;&lt;/child-component&gt;<br>&lt;ng-template #tmp&gt;<br>  &lt;div myDirective&gt;Hey!&lt;/div&gt;<br>&lt;/ng-template&gt;</pre><p>And let’s say child-component instantiates the template straight away. We would end up with DOM that looks like this:</p><pre>&lt;parent-component&gt;<br>  &lt;child-component&gt;<br>    &lt;div myDirective&gt;Hey!&lt;/div&gt;<br>  &lt;/child-component&gt;<br>&lt;/parent-component&gt;</pre><p>Even though myDirective is inside child-component in the DOM tree, it is a direct descendent of parent-component in DI tree because that is where containing ng-template is declared. Sometimes you can remedy that by simply moving ng-template so the child-component tag is wrapping around it.</p><blockquote><em>This is negated somewhat by the newly added injector option for </em><em>ngTemplateOutlet in the later versions of Angular</em></blockquote><h3>Handling the difference</h3><p>Sometimes we need to pass data from DOM parent to DOM child and back, regardless of DI structure. There are some native tools at our disposal made easy to use by Angular. They rely on CSS custom properties and custom DOM events. Let us have a look at examples from <a href="https://github.com/Tinkoff/taiga-ui">Taiga UI</a> once again, to understand them better. Our text field form controls use the basic PrimitiveTextfield component with built-in input element enclosed inside. However sometimes you want to add some native attributes, such as inputMode or autocomplete to that element. In this case, you can use following layout:</p><pre>&lt;tui-input&gt;<br>  &lt;input tuiTextfield inputMode=”email” /&gt;<br>&lt;/tui-input&gt;</pre><p>This translates into the following DOM (simplified):</p><pre>&lt;tui-input&gt;<br>  &lt;tui-primitive-textfield&gt;<br>    &lt;input tuiTextfield inputMode=”email” /&gt;<br>  &lt;/tui-primitive-textfield&gt;<br>&lt;/tui-input&gt;</pre><p>Now consider that PrimitiveTextfield is aware of a cleaner <strong>X</strong> icon. It knows whether it is visible or not. This affects padding on the right side of the native input tag, so that it does not overlay long text. But unlike the built-in element, input tuiTextfield is not under direct control of PrimitiveTextfield.</p><p>In Angular we can bind values to CSS custom properties. We can even add units with no hustle and it works in templates as well as in @HostBinding. CSS custom properties propagate DOM and even Shadow DOM, so it is very helpful if you build Web Components or use native style encapsulation in your Angular components.</p><p>This is how we pass that padding along:</p><pre>@HostBinding(‘style.--padding.rem’)<br>get padding(): number {<br>  return this.hasCleaner ? 24 : 0;<br>}</pre><p>Then we just declare padding-right: var(--padding) inside our native input. Sometimes this also helps us work around change detection, since it uses native CSS and propagates instantly. It only applies to styles so the usage is limited but if you work on low-level components it can often come in handy.</p><h3>Custom events</h3><p>Another way to pass data across are native custom events. You will not be able to initiate data going down the DOM tree, but a child can dispatch the event to send some data to its parents or even request it from them. Let’s explore an example. Say we have a tabs component:</p><pre>&lt;nav tabs&gt;<br>  &lt;a tab&gt;Tab&lt;/a&gt;<br>  …<br>&lt;/nav&gt;</pre><p>The tabs component keeps track of the active tab. In this case every tab can inject the parent wrapper and notify it that they became activated (with clicks or routerLink activation for example). But we might want to hide tabs in the “More” section if they overflow. We will use a structural directive so we can move tabs around. Our component would look like this:</p><pre>&lt;tabs-with-more&gt;<br>  &lt;a *tab&gt;Tab&lt;/a&gt;<br>&lt;/tabs-with-more&gt;</pre><p>And inside it would still have that nav tabs:</p><pre>&lt;nav tabs&gt;<br>  &lt;!-- we will have ngTemplateOutlets here for tabs --&gt;<br>&lt;/nav tabs&gt;<br>&lt;button&gt;<br>  More<br>  &lt;!-- <br>    we will have some dropdown logic here with <br>    ngTemplateOutlets for overflown tabs <br>  --&gt;<br>&lt;/button&gt;</pre><p>Now individual tabs cannot inject `nav tabs`, because in DI tree they are not its children. What they can do instead is to dispatch `new CustomEvent(‘tab-activated’, { bubbles: true })` and with `@HostListener(‘tab-activated’)` its DOM parents can listen to that event and react accordingly. Don’t forget the `{ bubbles: true }` part to make it propagate up the DOM. Default is false.</p><h3>A few tricks with events</h3><p>You can control what parent reacts to this event. If you want your closest parent to react first, just listen to the event as usual. You can stop propagation if you do not want other parents to know about it or you can ignore the event and let somebody else handle it up the tree. But you can also have the topmost parent to react first by listening to this event in the <strong><em>capture phase</em></strong>.</p><p>This is made simple by the <a href="https://github.com/taiga-family/ng-event-plugins">ng-event-plugins</a> library that we’ve created. It extends built-in Angular event handling with several plugins. It allows you to write @HostListener(‘tab-activated.capture.stop’) and declaratively listen to the event in capture phase and stop propagation at the same time. I strongly recommend you to try this library, it improves DX so much that I cannot imagine writing Angular apps without it anymore. If you use Taiga UI, it is already included.</p><p>Another helpful thing to know about events is they are synchronous. While the detail property of a custom event is immutable, it can in itself be an object that you can mutate. This allows children to request data from parents. Imagine for the sake of example, that in our tabs component a tab needs to ask the parent if it is active or not. Here’s how we can do it:</p><pre>element = inject(ElementRef).nativeElement;<br>active = false;<br><br>checkIfActive() {<br>  const detail = { active: false };<br><br>  this.element.dispatchEvent(‘is-active’, { bubbles: true, detail })<br>  this.active = detail.active;<br>}</pre><p>Because event handling is synchronous — on the very next line from dispatching an event all parents have already reacted to it. Our tabs parent component can check if the tab is active or not and set the value to the detail.active. Then the tab component updates its internal state. That neat trick allows us to request data across the DOM tree, rather than the DI tree.</p><h3>Summary</h3><p>After you digest all this, you will have a meaningful knowledge of how DI, DOM and View trees vary in Angular. This topic is somewhat advanced, but I tried to make it practical. This is a useful toolset to have in the back of your mind when developing Angular apps, especially if you tend to go low level, like we need to do when building Taiga UI. An important lesson to take from here is that Angular is a framework built on top of JavaScript. While for the most part we are abstracted away from the real deal, knowledge of native behavior can be helpful. Deeper understanding of the whole web ecosystem will be helpful in advancing your career from Angular developer to a Frontend specialist.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2ef959e7fafd" width="1" height="1" alt=""><hr><p><a href="https://medium.com/angularwave/dom-di-and-view-the-trees-of-angular-2ef959e7fafd">DOM, DI and View: the trees of Angular</a> was originally published in <a href="https://medium.com/angularwave">pineopticone</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: more than UI kit]]></title>
            <link>https://medium.com/angularwave/taiga-ui-more-than-ui-kit-8867a52a63c6?source=rss-3dd21e4659f0------2</link>
            <guid isPermaLink="false">https://medium.com/p/8867a52a63c6</guid>
            <category><![CDATA[angular]]></category>
            <category><![CDATA[open-source]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[masking]]></category>
            <dc:creator><![CDATA[Alexander Inkin]]></dc:creator>
            <pubDate>Mon, 03 Jul 2023 07:54:16 GMT</pubDate>
            <atom:updated>2026-03-26T09:00:49.546Z</atom:updated>
            <content:encoded><![CDATA[<p><a href="https://github.com/taiga-family/taiga-ui">Taiga UI</a> is a huge Angular UI kit and a components library that you should definitely try out. But it’s not just that. We’ve been working on it for over 5 years, starting as far back as Angular 4. And being fascinated by decomposition, we’ve spawned a multitude of independently helpful isolated libraries that allow us to build impressive Angular apps. While you might not be interested in using Taiga UI itself, these separate projects could definitely improve your Angular DX and provide you with amazingly useful tools. So in this article, let’s explore the Taiga UI family and see what makes it great.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/781/1*HSE4pDiD8LdSmFPj4ai8dQ@2x.png" /></figure><h3><a href="https://github.com/taiga-family/ng-web-apis">Web APIs for Angular</a></h3><p><a href="https://twitter.com/marsibarsi">Roman</a> and <a href="https://twitter.com/Waterplea">I</a> started our open source journey with <a href="https://github.com/taiga-family/ng-web-apis">this project</a>. It is a collection of tiny libraries, wrapping various native Web APIs from Canvas to Speech recognition, from IntersectionObserver to Web MIDI API and more. They all follow same key principles:</p><ol><li>Minimum interference with how native JavaScript works, but</li><li>Easy consumption of these APIs in an Angular app in a natural way</li></ol><blockquote>This means declarative directives, DI, services and RxJS streams where in vanilla app you would have imperative methods, callbacks and manual class creations.</blockquote><p>I have published <a href="https://medium.com/angularwave/web-apis-for-angular-e9ae3206ed1d">an overview article</a> of this project. There you can find brief descriptions of currently available libraries and credits for community contributions. While we don’t really have time for new adventurous additions anymore, like <a href="https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API">Gamepad API</a> or <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebXR_Device_API">WebXR API</a>, this is an open source project and everybody can participate. And if we will need to use <a href="https://github.com/Tinkoff/ng-web-apis/issues/96">another Web API</a> in our work, that would be a place where we first implement it.</p><h3><a href="https://github.com/taiga-family/ng-event-plugins">Event manager plugins</a></h3><p>Event manager is an obscure area of Angular, although it saw some traction as of late, thanks to <a href="https://github.com/taiga-family/ng-event-plugins">our library</a> being discussed on Twitter by multiple influencers in the Angular ecosystem. If you are interested in the mechanics of all this, I have written a couple of articles on the subject a while back. They are rather dated at this point, so the main idea is — you can augment Angular with custom event handling logic with DI token. So, for example, you can write (click.stop)=&quot;onClick()&quot; and have it implicitly call $event.stopPropagation(). With time we have collected a set of plugins that range from quality of life improvements like the previous example to pretty vital things that you couldn’t really do in Angular before.</p><blockquote>You can now listen to events in capture phase!</blockquote><p>If you are using Taiga UI, those plugins are all enabled by default, but they are a <a href="https://github.com/taiga-family/ng-event-plugins">separate library</a> which you can easily add to any Angular project of yours. What they allow you to do is add modifiers to standard DOM events (and CustomEvents too) that would enable certain handling logic. And those modifiers can be combined together. I’ll give you an interesting example where this library really helped me out!</p><p>I had a component with a form, which I needed to be able to submit, but also to be able to reset to default values. The trick was, I need to be able to do that from a place that knows nothing about the underlying form. So I went for native type=&quot;submit&quot; and type=&quot;reset&quot; buttons. Once the form was submitted it would disappear from DOM (it was in a modal window). Due to the dynamic nature of this modal, Angular form directive that is responsible for preventing default form submit action was destroyed before it could handle the event. This caused a page reload (default submit action). Also, there’s was no way to reset form to the default values unless you do it manually and native reset action just clears all inputs inside the form.</p><p>So this is how my form looked like with the plugins:</p><pre>&lt;form<br>  (submit.capture.prevent.silent)=&quot;0&quot;<br>  (reset.capture.prevent.stop)=&quot;onReset()&quot;<br>  [formGroup]=&quot;form&quot;<br>&gt;</pre><p>Let’s unwrap what this does. Submit event is listened to in the capture phase and we call preventDefault() on it so our page does not reload. We also sprinkle .silent in there so that change detection is not triggered one extra time upon submit. Then we listen to the reset event, once again, in the capture phase to be the first to react. We prevent native behavior so it does not clean our input tags and we also call stop. This makes the reset event not trigger in the bubbling phase at all so Angular never finds out and we can call onReset with our custom logic.</p><p>While this example is rather extreme, you can see how just a few words can save you from pretty tricky situations normally requiring bulky workarounds. This plugins library is dependency free, weighs less than 2KB gzip and works with any Angular version from 12 and above. It would most surely improve your DX with Angular so give it a shot!</p><h3><a href="https://github.com/taiga-family/ng-polymorpheus">Polymorpheus</a></h3><p>This is one of my <a href="https://github.com/taiga-family/ng-polymorpheus">favorite Angular projects</a> of all time because it allows for a very important paradigm shift. Angular has multiple ways to create dynamic content, with multiple ways to provide input to the process. You can just use interpolation, and for dynamic content you can use functions:</p><pre>{{ showSelected(items.length) }}</pre><pre>showSelected(length: number): string {<br>  return length ? `${length} selected` : `Nothing is selected`<br>}</pre><p>For something more complex (HTML, styles, directives) you can use ng-template + ngTemplateOutlet and pass context to provide some input:</p><pre>&lt;ng-template let-length&gt;<br>  &lt;strong *ngIf=&quot;length else empty&quot;&gt;{{ length }} selected&lt;/strong&gt;<br>  &lt;ng-template #empty&gt;Nothing is selected&lt;ng-template&gt;<br>&lt;/ng-template&gt;</pre><p>Finally, for something reusable across apps or something really complicated that you would like to properly isolate you can use ngComponentOutlet + dynamic components. Passing data gets even more complicated as the only way to do it would be through custom Injector.</p><blockquote>What <a href="https://github.com/taiga-family/ng-polymorpheus">ng-polymorpheus</a> does is abstract all this hustle away to allow you to think simply in terms of <strong>content</strong> and <strong>context</strong>.</blockquote><p>It adds a universal outlet directive which you can use with primitives, functions, templates or components in the same way.</p><pre>&lt;strong *polymorpheusOutlet=&quot;content as text; context: context&quot;&gt;<br>  {{ text }}<br>&lt;/strong&gt;</pre><p>In the example above, primitive values and results of functions called with context would display &lt;strong&gt;value_or_function_result_here&lt;/strong&gt;. As for templates or dynamic components — they will be instantiated at that spot. This might not seem like a big deal at a glance, but once you learn to harness the power of universal content you will never have to worry about designers changing specs of your components to allow for more visual customization.</p><h3><a href="https://github.com/taiga-family/maskito">Maskito</a></h3><p>While not tied to Angular, this separate project is also worth mentioning here. It is a <a href="https://github.com/taiga-family/maskito">framework agnostic input masking solution</a> we developed and it is the most recent addition to the family. It provides a very flexible and transparent API to add masking to input and textarea inside your application, whether you use Angular, React, Vue or even vanilla JavaScript. It can work with components libraries without direct access to native input tags, it has got you covered on every possible user interaction — keyboard, virtual keyboard, autofill, drag and drop, copy and paste — you name it!</p><p>The only reason I only briefly mention it here is because I have already published an <a href="https://medium.com/angularwave/maskito-a-holy-grail-of-input-masking-25e729a71ef2?sk=58d97ac159ddd244b05c18053b69d7be">extensive article</a> about Maskito benefits and why it is worth considering. Share it with your team lead if you ever face a decision on a masking solution for your project. If you are interested in more technical aspects of this library, <a href="https://twitter.com/nsbarsukov">Nikita Barsukov</a> is better suited to give you an overview as Maskito lead developer. You can read his two part article <a href="https://javascript.plainenglish.io/uphill-battle-to-create-a-mask-for-text-field-f1e385455dd5">here</a> and <a href="https://medium.com/its-tinkoff/maskito-is-a-new-collection-of-libraries-for-text-field-masking-f64ec71951df">here</a>. You can also check out our <a href="https://maskito.dev/">extensive demo portal</a>.</p><h3>Honorable mentions</h3><p>There are also some other libraries we maintain that I would like to mention. But in order to not overload this article I’ll do it briefly:</p><h4><a href="https://github.com/taiga-family/ng-morph">ng-morph</a></h4><p>This library has made Angular schematics for Taiga UI breaking changes migration easy. It is a large set of tools for both global code base updates in your project and speeding up your work on Angular schematics manipulating with TypeScript AST safely.</p><p>You do not need to know how TypeScript works under the hood to transform your project. You can use simple ready functions for manipulating TS and NG entities. Moreover ng-morph adds several benefits in the process:</p><ul><li>You make changes in virtual file structure. So, you can check the new structure first, fix all the bugs and inconsistencies before saving changes in the real project structure. It is much faster than migrating a real project and reverting changes after checking</li><li>You can write unit tests for your functions and scripts before using it in real projects or share with a lot of colleagues</li></ul><h4><a href="https://github.com/taiga-family/ng-dompurify">ng-dompurify</a></h4><p>Pretty sure you have seen «WARNING: sanitizing HTML stripped some content» before. With this tiny library you can extend Angular’s built-in sanitizing capabilities with an acclaimed dedicated library <a href="https://github.com/cure53/DOMPurify">DOMPurify</a>. For more information, you can read my <a href="https://medium.com/angular-in-depth/warning-sanitizing-html-stripped-some-content-and-how-to-deal-with-it-properly-10ff77012d5a">old article</a> which is still relevant. <strong><em>Note:</em></strong><em> ignore sections about styles sanitizing, Angular no longer does that.</em></p><h4><a href="https://github.com/taiga-family/taiga-ui/tree/main/projects/cdk">@taiga-ui/cdk</a></h4><p>You may not care for Taiga UI as a components library, but the foundational package is completely detached from visual style and is fully tree shakable. There are plenty of goodies that can help you build your own stuff, such as extensive abstractions over ControlValueAccessor, @tuiPure decorator or tuiMapper pipe for memoization, low level directives and services and a lot more.</p><h3>Summary</h3><p>All these libraries are developed and supported by our team at Tinkoff. They are backed by a huge commercial enterprise and are heavily used internally. This means you can rely on them still being maintained in the future. Go ahead and check them out and if you find some feature lacking or a bug — don’t hesitate to raise an issue at the corresponding GitHub repository. Please also consider giving them a star — that simple action helps us show management that our open source initiatives are relevant and worth doing. If you wish to contribute to open source, you can find an issue labeled “Contributions welcome” or “Good first issue” to help us improve the Taiga UI family!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8867a52a63c6" width="1" height="1" alt=""><hr><p><a href="https://medium.com/angularwave/taiga-ui-more-than-ui-kit-8867a52a63c6">Taiga UI: more than UI kit</a> was originally published in <a href="https://medium.com/angularwave">pineopticone</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Maskito: a Holy Grail of input masking]]></title>
            <link>https://medium.com/angularwave/maskito-a-holy-grail-of-input-masking-25e729a71ef2?source=rss-3dd21e4659f0------2</link>
            <guid isPermaLink="false">https://medium.com/p/25e729a71ef2</guid>
            <category><![CDATA[vue]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[masking]]></category>
            <category><![CDATA[angular]]></category>
            <category><![CDATA[react]]></category>
            <dc:creator><![CDATA[Alexander Inkin]]></dc:creator>
            <pubDate>Mon, 26 Jun 2023 09:15:43 GMT</pubDate>
            <atom:updated>2023-08-21T22:12:24.391Z</atom:updated>
            <content:encoded><![CDATA[<p>If you have ever worked on complex user forms, you definitely needed a masking solution. You want your numbers to be properly formatted so it is easy for users to make sure they have entered the correct amount at a glance. Or compare credit card number with an actual card, enter their phone or birthday or an appointment time. Masking has a lot of use cases on the web but to find a good solution is not an easy task. A lot of the time masking libraries can cause frustration for both developers and users. Common issues are jumping caret, troubles with added symbols like dashes or brackets, browser autofill or errors in server side rendering and convoluted API.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yiIn_qbIzY58MpCiZemm4Q.jpeg" /></figure><p>We were no strangers to these troubles and at some point we decided to dedicate serious resources to our own solution. Today I would like to present it to you and explain why it is worth your attention.</p><h3>What is Maskito and why should you care?</h3><p><strong>Maskito </strong>is a set of libraries for input masking with focus on both UX and DX. You can check out our <a href="https://maskito.dev/">extensive documentation portal</a>. There you can find plenty of examples, as well as a detailed walkthrough of the API. The “Getting started” section will explain the basics and will quickly get you familiar with concepts and use cases. You can also read a <a href="https://medium.com/its-tinkoff/maskito-is-a-new-collection-of-libraries-for-text-field-masking-f64ec71951df">more technical article</a> by Maskito lead developer <a href="https://twitter.com/nsbarsukov">Nikita Barsukov</a>.</p><p>Of course, Maskito is not the only solution you can find. But I believe there are certain things setting it apart from most alternatives available right now. Hence the ambitious title of this article. Let’s briefly explore why you should consider Maskito if you are looking for a masking solution for your project.</p><h4>Backed by enterprise</h4><p>Unlike many open source libraries that are maintained by individuals, Maskito is a product developed and used by one of the world’s leading fintech companies. We are seasoned open source authors getting paid for what we do so you can rely on timely support and continuous development.</p><p>Our team already has a stock of welcomed projects, especially in the Angular ecosystem, such as <a href="https://github.com/taiga-family/taiga-ui">Taiga UI</a>, <a href="https://github.com/taiga-family/ng-polymorpheus">ng-polymorpheus</a> and <a href="https://github.com/taiga-family/ng-web-apis">Web APIs for Angular</a> to name a few. We take them very seriously because they are not pet projects or hobbies (although we love them as if they were). Therefore you can expect our documentation to be easy to navigate, examples to be plentiful and communication to go effortlessly.</p><h4>Framework agnostic</h4><p>Whether you use React, Vue or Angular for your product or you rely on some other framework or even vanilla JavaScript — Maskito got you covered. Our main package is dependency free and can be used in any frontend application. And so are mask presets that we will cover below.</p><p>At the time of writing we have dedicated packages to use Maskito in React, Vue and Angular with comfort. Since the public API is rather succinct, it would be easy to expand support to other popular frameworks. Actual masking configuration is the same and can be shared across different projects and architectures with ease.</p><h4>Environment agnostic</h4><p>Maskito works in all modern browsers regardless of the environment you run it in. You can be using Web Components with Shadow DOM or employ SSR to improve your time to interactive and SEO — your inputs will not give you trouble by trying to access ‘window’ under nodejs or messing with encapsulated focus. It will also work properly on touch devices and virtual keyboards on both inputs and textarea elements. Clipboard interaction, undo/redo, text drag and drop or browser autofill are also covered by Maskito.</p><p>Our goal is to create a go-to masking solution for the web and the web is as diverse as it gets. If you face any issues with some specific configuration, please do not hesitate to <a href="https://github.com/taiga-family/maskito/issues/new/choose">raise an issue</a>.</p><h4>Quality code</h4><p>Maskito is maintained by an experienced lead developer and a team of contributors with a history of delivering widely used versatile solutions and open source libraries. Multiple Google Developer Experts curate the project and make sure architectural decisions are well thought through and make sense in the long run.</p><p>All code is written in strict TypeScript and covered by tons of e2e tests and community contributions are thoroughly reviewed. We have tightly configured linters and automated pipelines we perfected for years on other projects to make sure technical flaws do not make it to the release. We use it in a multi million users business product. It is in our best interest that the caret does not jump all over your input fields.</p><h4>Not just us</h4><p>While we of course trust our solution and are happy with it, you might be wondering if it would suit your particular needs. We have a wide array of products with different browser and device usage, different specs and requirements. Our team consists of engineers that specialize in generalized solutions and flexible APIs. So we know there’s an endless pool of use cases and we are prepared.</p><p>But it is not just us — <a href="https://ionicframework.com/">Ionic Framework</a>, a multi platform UI solution with <a href="https://github.com/ionic-team/ionic-framework">nearly 50k stars on GitHub</a> has adopted Maskito as its official masking <a href="https://ionic.io/blog/announcing-ionic-v7-1">library recommendation</a>. Their team did an investigation of existing alternatives and considered an option to develop their own mask. They decided Maskito would satisfy the needs of their extremely varied user base and added examples of masking their components to the <a href="https://ionicframework.com/docs/api/input#input-masking">documentation</a>.</p><h4>Ready to use</h4><p>Maskito comes with a dedicated package of ready-to-use masks. We maintain a huge components library that has number formatting, credit cards, phones, dates etc. It is used in dozens of applications across our ecosystem, where additional masking is required for zip codes, IP addresses, serial numbers and so on. Once they are implemented and properly tested, all generally applicable cases are extracted and reused.</p><p>This is an ever growing collection of presets that you can take as-is and be sure they behave properly. You can contribute to it or raise a feature request for a particular solution at our <a href="https://github.com/taiga-family/maskito">GitHub repository</a>. This makes Maskito as tiny or as comprehensive as you need it to be. Core package is only about 3KB gzip and every piece of presets is tree shakable so your bundle is in safe hands.</p><h3>Summary</h3><p>Maskito is free for both personal and commercial use and is released under Apache 2.0 license. I am really proud of what the team is doing and believe it to be the best solution for the task. I hope it can help you bring your ideas to life in a smooth way both for your users and developers. Check Maskito out and if you like it, support us with a star and help us spread the word. Happy masking everyone!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=25e729a71ef2" width="1" height="1" alt=""><hr><p><a href="https://medium.com/angularwave/maskito-a-holy-grail-of-input-masking-25e729a71ef2">Maskito: a Holy Grail of input masking</a> was originally published in <a href="https://medium.com/angularwave">pineopticone</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Web APIs for Angular]]></title>
            <link>https://medium.com/angularwave/web-apis-for-angular-e9ae3206ed1d?source=rss-3dd21e4659f0------2</link>
            <guid isPermaLink="false">https://medium.com/p/e9ae3206ed1d</guid>
            <category><![CDATA[rxjs]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[angular]]></category>
            <dc:creator><![CDATA[Alexander Inkin]]></dc:creator>
            <pubDate>Fri, 26 May 2023 09:20:08 GMT</pubDate>
            <atom:updated>2023-08-21T22:14:49.749Z</atom:updated>
            <content:encoded><![CDATA[<p>Web is a rich ecosystem with a bunch of powerful APIs that keeps getting bigger. There are things we are used to like <a href="https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API">Canvas</a> or <a href="https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API">IntersectionObserver</a>, but the ever growing list of browsers capabilities now includes <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_MIDI_API">Web MIDI API</a>, <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Speech_API">Speech Recognition</a> and even more exotic things like <a href="https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API">gamepads</a> and <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebXR_Device_API">VR</a>. Naturally, they might be hard to use in Angular applications due to different paradigms of native JavaScript and declarative Angular nature. This is where we come in!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*O_hZBNw16xG0yPaezw6htA.jpeg" /></figure><h3>What is it?</h3><p><a href="https://github.com/taiga-family/ng-web-apis">Web APIs for Angular</a> is an open source initiative <a href="https://twitter.com/Waterplea">me</a> and <a href="https://twitter.com/marsibarsi">Roman</a> started a long time ago. If you follow our tech writing, you know that we are devoted advocates of <strong><em>the Angular way</em></strong> of doing things. We love declarative idiomatic approach and try to promote it wherever it is applicable. So at some point we looked at <a href="https://developer.mozilla.org/en-US/docs/Web/API">Web APIs</a> page on MDN and decided why don’t we make it easier to use those in Angular. I started with <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API">Web Audio API</a>, because that subject fascinated me. We also noticed that with only the <a href="https://angular.io/api/common/DOCUMENT">DOCUMENT</a> token at our disposal it is not easy to work with other global objects, such as window or navigator in safe DI fashion. So we have created what would then become the most popular library of the set — <a href="https://www.npmjs.com/package/@ng-web-apis/common">@ng-web-apis/common</a>, a collection of tokens for global objects. This made them easy to test and use in Server Side Rendering, thanks to a sister package <a href="https://www.npmjs.com/package/@ng-web-apis/universal">@ng-web-apis/universal</a>.</p><p>I have already written <a href="https://indepth.dev/posts/1440/global-objects-in-angular">an article</a> on why it is important. And also one on <a href="https://medium.com/its-tinkoff/writing-retrowave-in-angular-4e1ff80798a8">Web Audio API in Angular</a>, as well as using it <a href="https://medium.com/its-tinkoff/jam-on-your-midi-keyboard-in-angular-2a2c3f91ad75">together with Web MIDI API</a>. But I have never spoken about this initiative as a whole and there’s a lot of other very helpful libraries you might have missed when you had a need to use <a href="https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver">MutationObserver</a> or <a href="https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API">Geolocation API</a> in your Angular app. So with this article I would like to fill in the blanks and briefly showcase everything we have now.</p><h3>APIs</h3><p>With Common and Universal packages described in the article I linked above, and Web Audio and MIDI out of the way, our remaining packages are:</p><h3><a href="https://www.npmjs.com/package/@ng-web-apis/intersection-observer">IntersectionObserver</a></h3><p>This is our most popular library after the common package. It allows you to use <a href="https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API">IntersectionObserver</a> in a natural Angular way. Mark element to be the intersection root by a dedicated directive, define parameters, such as threshold or margins. Then add a second directive to DOM elements you want to observe. Simple Angular output would emit <a href="https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry">intersection entries</a>. Same thing can be done with a service and providing config with DI tokens.</p><h3><a href="https://www.npmjs.com/package/@ng-web-apis/resize-observer">ResizeObserver</a></h3><p>Following the same approach of declarative directives + Observable service, this library adds such a vital missing feature as watching element size change in a performant way. These observer libraries outline our core principles well — wrap native APIs with minimal interference. Working with DOM means directives, events mean RxJS streams. Then we get benefits of using API in a comfortable way for a couple of kilobytes gzip.</p><h3><a href="https://www.npmjs.com/package/@ng-web-apis/mutation-observer">MutationObserver</a></h3><p>The last of the observers bunch. This API is rather old, first introduced over a decade ago. It follows the same principle of directives and service once again. But this time directive extends native MutationObserver class which makes it even smaller and showcases an interesting approach modern JavaScript opens up. Don’t worry about node.js and server side, since our dedicated <a href="https://www.npmjs.com/package/@ng-web-apis/universal">universal</a> package got everything covered. Keep it in mind for Jest testing too!</p><h3><a href="https://www.npmjs.com/package/@ng-web-apis/geolocation">Geolocation</a></h3><p>A contribution by <a href="https://twitter.com/v_potekhin">Vladimir Potekhin</a> who would later become a core member of our flagship project <a href="https://taiga-ui.dev/">Taiga UI</a>. This package gives you an Observable service to watch for your GPS location in an Angular app.</p><h3><a href="https://www.npmjs.com/package/@ng-web-apis/canvas">Canvas</a></h3><p>This package adds directives to draw on HTMLCanvasElement in a similar way you declaratively describe graphics with SVG. It covers every 2D capability Canvas API has, such as paths, transforms, text and stroke/fill rules.</p><h3><a href="https://www.npmjs.com/package/@ng-web-apis/workers">Workers</a></h3><p>A contribution by <a href="https://twitter.com/katsuba_igor">Igor Katsuba</a> whose project <a href="https://github.com/tinkoff/ng-morph">ng-morph</a> has helped us make really complex breaking change migrations automatic (read about it <a href="https://medium.com/angularwave/ng-morph-is-the-best-way-to-do-big-changes-in-your-angular-project-217c4051beeb">here</a>, <a href="https://medium.com/angularwave/ng-morph-is-your-first-assistant-for-angular-libs-development-ac06dbb00f99">here</a> and <a href="https://medium.com/angularwave/how-ng-morph-can-help-you-keep-your-big-monorepo-always-clean-as-new-bbca8e62c4b3">here</a>). The workers package allows you to use Web Workers in Angular in a familiar fashion using RxJS event streams.</p><h3><a href="https://www.npmjs.com/package/@ng-web-apis/payment-request">Payment Request</a></h3><p>Historically the first open source project by <a href="https://twitter.com/marsibarsi">Roman</a>. It is a declarative way to set up payment requests with directives that you can use on a basket component at a checkout of your e-commerce app.</p><h3><a href="https://www.npmjs.com/package/@ng-web-apis/permissions">Permissions</a></h3><p>A contribution by <a href="https://twitter.com/dmitryaefimenko">Dmitry Efimenko</a>. A tiny package with a service that allows you to query for permissions in RxJS streams.</p><h3><a href="https://www.npmjs.com/package/@ng-web-apis/speech">Speech</a></h3><p>Speech synthesis and speech recognition APIs in one package. You have a text-to-speech configurable directive and a service to recognize speech using RxJS operators.</p><h3><a href="https://www.npmjs.com/package/@ng-web-apis/storage">Storage</a></h3><p>Using Web Storage API with Observables.</p><h3>Moving to Taiga Family</h3><p>We started this initiative to explore the open source landscape as our personal project. Of course since it was very helpful, we quickly adopted those libraries in our work. Over the course of recent years we have had less and less time to dedicate to them. Especially to the more niche ones like Web Audio API, the biggest and the most complex one to date. Libs remained in View Engine publication with Angular 9, when Angular 16 was already out. The most reasonable thing to do was to move them all into an nx monorepo to easily maintain dependencies versions. But that was not something we had time to do on our own as many other projects had our attention. I couldn’t just ask my colleagues to do this in their spare time either. Given some of these libraries are vital parts of our products, we decided to move this initiative under the Taiga Family organization so our whole team could help during work hours. For the end user the only thing that changes would be the license switch from MIT to Apache 2.0. That and better, timely support. It remains open source and you are very welcome to contribute. New major versions will be released to indicate the license change and every library would be updated to Ivy publication which is good news, considering ngcc is phased out. Make sure to raise an issue at <a href="https://github.com/taiga-family/ng-web-apis">our new monorepo</a> if you face a problem with any of our libraries, old repositories will be switched to read only.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e9ae3206ed1d" width="1" height="1" alt=""><hr><p><a href="https://medium.com/angularwave/web-apis-for-angular-e9ae3206ed1d">Web APIs for Angular</a> was originally published in <a href="https://medium.com/angularwave">pineopticone</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The magic of ContentChild, ViewChild and Template References]]></title>
            <link>https://medium.com/angularwave/the-magic-of-contentchild-viewchild-and-template-references-d1cbf7f819c4?source=rss-3dd21e4659f0------2</link>
            <guid isPermaLink="false">https://medium.com/p/d1cbf7f819c4</guid>
            <category><![CDATA[angular]]></category>
            <category><![CDATA[html]]></category>
            <category><![CDATA[frontend]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[typescript]]></category>
            <dc:creator><![CDATA[Alexander Inkin]]></dc:creator>
            <pubDate>Mon, 30 Jan 2023 15:31:55 GMT</pubDate>
            <atom:updated>2023-02-01T08:25:26.019Z</atom:updated>
            <content:encoded><![CDATA[<p>In Angular we tend to write declarative code. This means we are not supposed to manually query things when we need them. There are instruments in the framework that allow us to do so. First, lets recap what content and view mean.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mOLrFMgRN873Yi71St75qQ.jpeg" /></figure><p><strong>View</strong> is template of your component. This means it doesn’t exist in directives.</p><p><strong>Content</strong> is what your component (or directive) is wrapped around. For components you need to add ng‑content tag in your view to preserve content. Otherwise it is replaced with view upon render.</p><blockquote><em>💡 </em><strong><em>Performance tip:</em></strong> even if ng‑content is hidden with *ngIf and detached from DOM, it is still rendered. And it still goes through change detection cycles. If you need lazy content initialization you need to use templates.</blockquote><h3>ViewChild and ViewChildren</h3><p>When you create component, you might need access to some parts of its template. @ViewChild decorator allows you to do so and more. First, you need to provide selector. You can mark item with a string in the template: &lt;div #ref&gt;...&lt;/div&gt;. And then query it with a decorator: @ViewChild(&#39;ref&#39;)</p><blockquote>This is a template reference variable and we will discuss it in detail later.</blockquote><p>It could also be class if you aim at a component or a directive: @ViewChild(MyComponent)</p><p>It can even be an injection token: @ViewChild(MY_TOKEN)</p><p>Real strength though comes with the second argument — an options object. First option is trivial: static: boolean. It tells Angular whether some child always exists or present with a condition. static: true means it can be queried in ngOnInit. static: false means it is not available until ngAfterViewInit. Default value is <strong>false</strong>.</p><pre>&lt;div #static&gt;<br>    I&#39;m a static child<br>&lt;/div&gt;<br>&lt;div *ngIf=&quot;true&quot; #dynamic&gt;<br>    I&#39;m not a static child<br>&lt;/div&gt;</pre><blockquote>Note that even static queries are resolved only in ngOnInit. So technically they are undefined for some time. It is a good practice to reflect that with proper type so you do not try to accidentally access it in a constructor. Hence a question mark in type in examples above.</blockquote><p>Second option is read. First argument tells Angular where to look and read tells it what to look for. We can get everything from target injector - be it a service, a token or a component instance.</p><p>By default, @ViewChild retrieves a component when we query it. The most common use for read option is to get ElementRef instead:</p><pre>@ViewChild(MyComponent, { read: ElementRef })<br>readonly elementRef?: ElementRef&lt;HTMLElement&gt;</pre><p>Quite often though you don’t need @ViewChild at all. In a lot of cases you can just use <em>template reference variable</em> and pass it straight to the callback:</p><pre>&lt;input #input type=&quot;text&quot; [(ngModel)]=&quot;model&quot;&gt;<br>&lt;button (click)=&quot;onClick(input)&quot;&gt;Focus input&lt;/button&gt;&quot;</pre><pre>// Notice that it&#39;s HTMLElement not ElementRef here in this case<br>onClick(element: HTMLElement) {<br>    element.focus();<br>}</pre><p>You can think of <em>template reference variables</em> as kind of closures in template in this case. The reference is there, but it only exists in a closed circuit where it is needed. It helps you keep component’s code clean.</p><p>Template reference variable targets component when it’s on one and an HTML element when it’s not. It can also target directives, that’s what exportAs is for in @Directive decorator:</p><pre>@Directive({<br>  selector: &#39;[myDirective]&#39;,<br>  exportAs: &#39;someName&#39;,<br>})<br>export class MyDirective {}</pre><pre>&lt;div myDirective #ref=&quot;someName&quot;&gt;...&lt;/div&gt;</pre><p>Sometimes you need to query multiple items of the same kind. In this case you can use @ViewChildren. You can mark many items in template with the same reference and retrieve a whole collection. All advices above are applicable here too, except the type would be QueryList&lt;T&gt; and static is not available for lists. This allows you to go over every item when you need to. Consider an example with tabs component. Instead of horizontal scroll you want to hide extra tabs into &quot;More&quot; button. See the demo below utilizing @ViewChildren to do the job:</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fstackblitz.com%2Fedit%2Fangular-view-children-tabs%3Fembed%3D1&amp;display_name=StackBlitz&amp;url=https%3A%2F%2Fstackblitz.com%2Fedit%2Fangular-view-children-tabs&amp;image=https%3A%2F%2Fsocial-img.staticblitz.com%2Fprojects%2Fangular-view-children-tabs%2F93630fb9a00594b001f8f7f52cac49cb&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=stackblitz" width="745" height="400" frameborder="0" scrolling="no"><a href="https://medium.com/media/fe20ef17c20bb7b908d5da37827fabac/href">https://medium.com/media/fe20ef17c20bb7b908d5da37827fabac/href</a></iframe><h3>ContentChild and ContentChildren</h3><p>A handy way to customize component is to use content. In Angular, similar to native Web Components, you can wrap your component around some DOM elements and project them into component view. This is what built-in tag ng‑content is for. And while in most cases you will just project all content in one place, you can define what content goes where. ng‑content has an optional attribute select. It is a lot like selector for directives or components and supports the same syntax. This means you can hook to tag names, classes, attributes and combine them any way you like. You can always leave single ng‑content without selector. Everything that did not match slots with selectors will go there.</p><p>An important thing to keep in mind is this: <strong>even though content seems to appear in your component view it is actually part of the parent view and follows its change detection cycle. </strong>It means that if content element gets marked for checking due to subscribed event for example, your component view will not be checked. Because technically it is parallel to content and this is not how change detection propagates in Angular. Since your component might be dependent on its content children you need to make sure changes to content will not go unnoticed.</p><blockquote>changes: Observable&lt;void&gt; from QueryList is a handy way to keep your component up-to-date with content changes.</blockquote><p>You can query content the same way you query view. Angular has @ContentChild and @ContentChildren decorators for this purpose. The syntax isthe same as with their view counterparts.</p><p>Below you can find an example of a dropdown menu where items are passed as content to a wrapping component. This allows you to hook into events like clicks or key presses, sparing component this logic. And component can implement navigation logic.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fstackblitz.com%2Fedit%2Fangular-content-children-menu%3Fembed%3D1&amp;display_name=StackBlitz&amp;url=https%3A%2F%2Fstackblitz.com%2Fedit%2Fangular-content-children-menu&amp;image=https%3A%2F%2Fsocial-img.staticblitz.com%2Fprojects%2Fangular-content-children-menu%2F572340170fd8ca58fe01cef948d7265e&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=stackblitz" width="745" height="400" frameborder="0" scrolling="no"><a href="https://medium.com/media/abe3675412b94a9c5f138660a8dc42d7/href">https://medium.com/media/abe3675412b94a9c5f138660a8dc42d7/href</a></iframe><p>One thing in syntax that differs is @ContentChild also has descendants: boolean to be able to query children nested inside other components (<strong>but not from their views</strong>):</p><pre>&lt;!-- @ContentChild(&#39;child&#39;, { descendants: true }) --&gt;<br>&lt;my-component&gt;<br>  &lt;another-component&gt;<br>    &lt;div #child&gt;...&lt;/div&gt;<br>  &lt;/another-component&gt;<br>&lt;/my-component&gt;</pre><p>This wraps it up for this Angular feature. There’s quite a lot you can do with it, make sure to practice to be able to spot where it can help you design robust, maintainable components!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d1cbf7f819c4" width="1" height="1" alt=""><hr><p><a href="https://medium.com/angularwave/the-magic-of-contentchild-viewchild-and-template-references-d1cbf7f819c4">The magic of ContentChild, ViewChild and Template References</a> was originally published in <a href="https://medium.com/angularwave">pineopticone</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Declarative Web Audio API]]></title>
            <link>https://javascript.plainenglish.io/declarative-web-audio-api-ad98b9b49d7e?source=rss-3dd21e4659f0------2</link>
            <guid isPermaLink="false">https://medium.com/p/ad98b9b49d7e</guid>
            <category><![CDATA[web-components]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[declarative-programming]]></category>
            <dc:creator><![CDATA[Alexander Inkin]]></dc:creator>
            <pubDate>Mon, 16 Aug 2021 10:22:35 GMT</pubDate>
            <atom:updated>2021-08-16T10:22:35.766Z</atom:updated>
            <content:encoded><![CDATA[<h4>A better way to build AudioNodes with Web Components!</h4><figure><img alt="An audio cassette with loose tape" src="https://cdn-images-1.medium.com/max/1024/1*2ORNLoAbKQrfYgknyEubKQ.jpeg" /><figcaption>Photo by <a href="https://unsplash.com/@schluditsch">Daniel Schludi</a> on <a href="https://unsplash.com/search/photos/typing?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><p><a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API">Web Audio API</a> is a rich multimedia feature modern browsers have. It allows us to process audio signal and build amazing applications from fun games to complete digital audio workstations. But working with complex reusable code structures within its original paradigm is difficult. Let’s take a look at basic echo effect example:</p><figure><img alt="Scheme of echo effect AudioNodes graph" src="https://cdn-images-1.medium.com/max/960/1*wCm7aks1gtgjxWLj7fXtiA.png" /></figure><p>Everything happens within <a href="https://developer.mozilla.org/en-US/docs/Web/API/AudioContext">AudioContext</a>. The graph consists of a source node that is linked to <a href="https://developer.mozilla.org/en-US/docs/Web/API/AudioDestinationNode">AudioDestinationNode</a>. It also has a feedback loop of <a href="https://developer.mozilla.org/en-US/docs/Web/API/DelayNode">DelayNode</a> to introduce a delay and <a href="https://developer.mozilla.org/en-US/docs/Web/API/GainNode">GainNode</a> to control how fast echo fades. This loop connects to destination too.</p><p>Settings this up consists of 3 steps:</p><ol><li>Creating nodes</li><li>Configuring parameters</li><li>Linking the graph together</li></ol><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3b7b2635881c3c1dd49d9a460004b390/href">https://medium.com/media/3b7b2635881c3c1dd49d9a460004b390/href</a></iframe><p>With any complex processing block this quickly gets tedious, especially if we want to control the parameters live. This is an imperative approach. We write a set of commands that are executed in order which changes our program state.</p><p>This reminds me a lot of drawing with <a href="https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API">Canvas API</a>. We can manually draw shapes by consecutive commands giving coordinates. There’s another way to draw shapes, even very complex ones, which is <a href="https://developer.mozilla.org/en-US/docs/Web/SVG">SVG</a> — Scalable Vector Graphics. Let’s try to build our own “SVG” for Web Audio API — a declarative XML based way to compose nodes graph, alter parameters and easily reuse code!</p><h3>Preparation</h3><p>We will base our solution on native Web Components. As a proof of concept, let’s create a simple volume control:</p><figure><img alt="AudioNode graph of a volume control: Source — Gain — Destination within AudioContext" src="https://cdn-images-1.medium.com/max/856/1*7tjeZScm6_wKTNYgGYoG5w.png" /></figure><p>Here’s how this graph would look like when our components are ready, with HTML5 audio tag as custom component acting as a source:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2e9a9f86da557ad99f744952cbd058ab/href">https://medium.com/media/2e9a9f86da557ad99f744952cbd058ab/href</a></iframe><p>To be able to link all of those nodes together we are gonna use the <em>Dependency Injection</em> pattern. It’s pretty simple in a nutshell. Parents provide dependencies for children and children can inject a dependency which will be requested from the closest parent who has it.</p><p>For our demo we will use custom DOM events. Because they are synchronous, scoped and it would take just a few lines to implement. I saw this pattern on <a href="https://www.youtube.com/watch?v=6o5zaKHedTE">Justin Fagnani talk</a> and it would work well for our case.</p><p>Since Web Components extend native HTML elements and there is no multiple inheritance in JavaScript — we will make a <em>mixin</em>:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/fcabaaa8a46077cb0e8fa7c388955d78/href">https://medium.com/media/fcabaaa8a46077cb0e8fa7c388955d78/href</a></iframe><p>provide method will take a Map of token keys and provider values. It would listen to a custom event <em>“inject”</em> and if a requested token is present in a given map, it would stop propagation and assign the value from the map to the event.</p><p>And inject method will take a token, dispatch a custom event and return the result.</p><h3>Web Components</h3><p>Our volume control has 4 components.</p><p>1.Topmost component is <strong>Context</strong>. All web components have a connectedCallback method which is called when they are initialized. We will call provide with a map of AudioContext as token and new AudioContext() instance as value. This would allow all children to inject the same audio context:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/73c94e17a5ee3e0060deb2c8ab0bc6d9/href">https://medium.com/media/73c94e17a5ee3e0060deb2c8ab0bc6d9/href</a></iframe><p><strong>2. Source</strong> node will extend HTMLAudioElement. In its connectedCallback it would inject context and create a new MediaElementAudioSourceNode instance. It would put it as AudioNode into the map this element provides:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/597b0113dbf3b41fbfb6878899edbc8a/href">https://medium.com/media/597b0113dbf3b41fbfb6878899edbc8a/href</a></iframe><p><strong>3. Gain </strong>is a little more complex because it has a parameter. We will create a private property with GainNode. In connectedCallback we will inject the parent AudioNode and connect it to our GainNode. We will also provide GainNode as AudioNode for all the children:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1716785a1b6d7c7c851995a19dcbb7af/href">https://medium.com/media/1716785a1b6d7c7c851995a19dcbb7af/href</a></iframe><p>Web components have a static getter that returns an array of possible attributes you can change. In this case it would be gain. When the gain attribute is changed we parse it as float and update AudioParam.</p><blockquote>Don’t forget, it’s a proof of concept so we will ignore param automations for now.</blockquote><p><strong>4. Destination </strong>component is very simple. We just inject the parent AudioNode and AudioContext and connect the node to the destination:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8a948d0ef5c996ce6c6d8fa81ea40ad4/href">https://medium.com/media/8a948d0ef5c996ce6c6d8fa81ea40ad4/href</a></iframe><p>Now all that’s left to do is register our Web Components:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c2d23f76fb64b7db207ca6ab31b6d846/href">https://medium.com/media/c2d23f76fb64b7db207ca6ab31b6d846/href</a></iframe><h3>Result</h3><p>You can check out this custom control here:</p><p><a href="https://waterplea.github.io/declarative-web-audio/">Web Components driven Declarative Web Audio - Proof of concept</a></p><p>Once you start AudioContext you will see an input slider wired to the custom volume control. Native HTML5 audio controls are located below, both are functional.</p><p>While this might not look like much, it outlines the concept. With relatively little code we can convert all Web Audio API features to be used as web components. I’m a very visual person. To me this is so much cleaner and so much easier to understand than vanilla Web Audio API. And I know a lot of musicians are like me too. They are used to build processing chains out of physical blocks, connecting them to one another. So this approach makes sense for the target audience.</p><p>I’m an Angular enthusiast so while I created this Proof of Concept with plain JavaScript, I have made a total conversion of Web Audio API to declarative Angular directives:</p><p><a href="https://github.com/ng-web-apis/audio">GitHub - ng-web-apis/audio: This is a library for declarative use of Web Audio API with Angular</a></p><p>If you are interested in Angular, check out <a href="https://medium.com/its-tinkoff/writing-retrowave-in-angular-4e1ff80798a8">this article</a> covering the library, and <a href="https://medium.com/its-tinkoff/jam-on-your-midi-keyboard-in-angular-2a2c3f91ad75">this follow up</a> about Web MIDI API. With these two libraries I’ve made a browser based Guitar Hero-like game to play with real synthesizer. It also has karaoke mode to sing along and see how well you matched the pitch. You are welcome to give it a shot:</p><p><a href="https://jamigo.app/">Jamigo.app</a></p><p>I hope it was an interesting angle. Declarative approach can make complex tasks much more manageable and easier to wrap your head around.</p><p><em>More content at </em><a href="http://plainenglish.io/"><strong><em>plainenglish.io</em></strong></a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ad98b9b49d7e" width="1" height="1" alt=""><hr><p><a href="https://javascript.plainenglish.io/declarative-web-audio-api-ad98b9b49d7e">Declarative Web Audio API</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>
    </channel>
</rss>