<?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"><channel><title><![CDATA[CodeCraft with Barcioch]]></title><description><![CDATA[CodeCraft with Barcioch]]></description><link>https://blog.procode.pl</link><generator>RSS for Node</generator><lastBuildDate>Thu, 16 Apr 2026 20:08:21 GMT</lastBuildDate><atom:link href="https://blog.procode.pl/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Angular - binding to protected properties]]></title><description><![CDATA[Before Angular@14 we could only bind public properties in views. Even though, the properties were meant to be private to the component, we had to make them public. This led to exposing component internals to the consumer. Now, we can make use of type...]]></description><link>https://blog.procode.pl/angular-binding-to-protected-properties</link><guid isPermaLink="true">https://blog.procode.pl/angular-binding-to-protected-properties</guid><category><![CDATA[Angular]]></category><category><![CDATA[webdev]]></category><dc:creator><![CDATA[Bartosz Szłapak]]></dc:creator><pubDate>Fri, 20 Oct 2023 14:45:46 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1697813069588/7b7743e1-341c-44f2-8d5f-8605e68db7ae.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Before Angular@14 we could only bind public properties in views. Even though, the properties were meant to be private to the component, we had to make them public. This led to exposing component internals to the consumer. Now, we can make use of typescript's <code>protected</code> access modifier to prevent users from accessing internals and still be able to bind those properties in the view.</p>
<h1 id="heading-public-properties">Public properties</h1>
<p>Before Angular@14, if you tried binding <code>protected</code> property in the view, the compiler would complain that the property is not accessible outside class.</p>
<p>In this example, I'm using <code>color</code> setter to prepend a value with the <code>@</code> symbol. It's assigned to the <code>colorWithHash</code> property and displayed in the view.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Component, Input } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;

<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-public-test'</span>,
  standalone: <span class="hljs-literal">true</span>,
  template: <span class="hljs-string">`&lt;span&gt;{{ colorWithHash }}&lt;/span&gt;`</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> PublicTestComponent {
  colorWithHash: <span class="hljs-built_in">string</span> | <span class="hljs-literal">undefined</span>;

  <span class="hljs-meta">@Input</span>() set color(color: <span class="hljs-built_in">string</span>) {
    <span class="hljs-built_in">this</span>.colorWithHash = <span class="hljs-string">`@<span class="hljs-subst">${color}</span>`</span>;
  }
}
</code></pre>
<p>Take a look at the IDE auto-completion - both properties are available.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697724444481/544a9474-6471-4ae1-8516-f6e423546318.png" alt class="image--center mx-auto" /></p>
<p>Since both <code>setter</code> and <code>colorWithHash</code> are public we can access them directly, i.e. by using <code>@ViewChild</code> decorator. Now, I can override internal value not meant to be accessed from the outside.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { AfterViewInit, Component, ViewChild } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { PublicTestComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">'./public-test.component'</span>;

<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-root'</span>,
  standalone: <span class="hljs-literal">true</span>,
  imports: [PublicTestComponent],
  template: <span class="hljs-string">`&lt;app-public-test [color]="'red'" #publicTestComponent&gt;&lt;/app-public-test&gt;`</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AppComponent <span class="hljs-keyword">implements</span> AfterViewInit {
  <span class="hljs-meta">@ViewChild</span>(<span class="hljs-string">'publicTestComponent'</span>) publicTestComponent!: PublicTestComponent;

  ngAfterViewInit(): <span class="hljs-built_in">void</span> {
    <span class="hljs-built_in">this</span>.publicTestComponent.color = <span class="hljs-string">'green'</span>;
    <span class="hljs-built_in">this</span>.publicTestComponent.colorWithHash = <span class="hljs-string">'-1-2-'</span>;
  }
}
</code></pre>
<h1 id="heading-protected-properties">Protected properties</h1>
<p>The component body is almost identical to the previous one. The only difference is the <code>colorWithHash</code> property is now protected.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Component, Input } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;

<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-protected-test'</span>,
  standalone: <span class="hljs-literal">true</span>,
  template: <span class="hljs-string">`&lt;span&gt;{{ colorWithHash }}&lt;/span&gt;`</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> ProtectedTestComponent {
  <span class="hljs-keyword">protected</span> colorWithHash: <span class="hljs-built_in">string</span> | <span class="hljs-literal">undefined</span>;

  <span class="hljs-meta">@Input</span>() set color(color: <span class="hljs-built_in">string</span>) {
    <span class="hljs-built_in">this</span>.colorWithHash = <span class="hljs-string">`@<span class="hljs-subst">${color}</span>`</span>;
  }
}
</code></pre>
<p>Take a look at the IDE auto-completion - only the <code>color</code> setter is available.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697724559159/41ace80c-6cd4-4d0f-ae9a-2907d8006ff4.png" alt class="image--center mx-auto" /></p>
<p>Now, I can't access protected properties and have to use only public ones, which is an expected behavior. The protected value is properly interpolated in the view.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { AfterViewInit, Component, ViewChild } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { ProtectedTestComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">'./protected-test.component'</span>;

<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-root'</span>,
  standalone: <span class="hljs-literal">true</span>,
  imports: [ProtectedTestComponent],
  template: <span class="hljs-string">`&lt;app-protected-test [color]="'red'" #protectedTestComponent&gt;&lt;/app-protected-test&gt;`</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AppComponent <span class="hljs-keyword">implements</span> AfterViewInit {
  <span class="hljs-meta">@ViewChild</span>(<span class="hljs-string">'protectedTestComponent'</span>) protectedTestComponent!: ProtectedTestComponent;

  ngAfterViewInit(): <span class="hljs-built_in">void</span> {
    <span class="hljs-built_in">this</span>.protectedTestComponent.color = <span class="hljs-string">'white'</span>;
  }
}
</code></pre>
<h1 id="heading-footnote">Footnote</h1>
<p>Please do not confuse Typescript's <code>private / protected</code> keywords with the Javascript's native private fields prefixed with the <code>#</code> symbol. These are completely different topics. Nevertheless, there are still ways to access protected or private members. You can always cheat the compiler or iterate through object properties. Protected fields are meant to hide the class internals and remove users' uncertainty about whether to use a certain property or not.</p>
]]></content:encoded></item><item><title><![CDATA[NgRx Standalone API]]></title><description><![CDATA[In this article, I'll show how to use NgRx with Angular Standalone API. If you're not familiar with it, please refer to the https://barcioch.pro/angular-standalone-components-directives-pipes article. The following examples show how to define a store...]]></description><link>https://blog.procode.pl/ngrx-standalone-api</link><guid isPermaLink="true">https://blog.procode.pl/ngrx-standalone-api</guid><category><![CDATA[NgRx]]></category><category><![CDATA[Angular]]></category><category><![CDATA[webdev]]></category><category><![CDATA[#quick start]]></category><dc:creator><![CDATA[Bartosz Szłapak]]></dc:creator><pubDate>Wed, 18 Oct 2023 18:54:26 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1697655151823/9718b92f-3816-475b-9e23-a0a54f98cecb.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this article, I'll show how to use <code>NgRx</code> with <code>Angular Standalone API</code>. If you're not familiar with it, please refer to the https://barcioch.pro/angular-standalone-components-directives-pipes article. The following examples show how to define a store using <code>NgModule</code> and <code>standalone api</code>. It's not a NgRx usage guide, since I'll cover it in a different article.</p>
<h1 id="heading-ngrx-with-ngmodule"><code>NgRx</code> with <code>NgModule</code></h1>
<h2 id="heading-root-store">Root store</h2>
<p>The root store is usually defined in the main <code>AppModule</code>. The <code>forRoot</code> method is used so the whole app can access it.</p>
<pre><code class="lang-typescript"><span class="hljs-meta">@NgModule</span>({
  imports: [
    StoreModule.forRoot({ router: routerReducer, authReducer: authReducer }),
    EffectsModule.forRoot([AuthEffects]),
    StoreRouterConnectingModule.forRoot(),
    StoreDevtoolsModule.instrument()
  ]
})
</code></pre>
<h2 id="heading-feature-store">Feature store</h2>
<p>When dealing with the feature modules I'm using <code>forFeature</code> method.</p>
<pre><code class="lang-typescript"><span class="hljs-meta">@NgModule</span>({
  imports: [
    StoreModule.forFeature(<span class="hljs-string">'featureKey'</span>, featureReducer),
    EffectsModule.forFeature([FeatureEffects]),
  ]
})
</code></pre>
<h1 id="heading-ngrx-with-standalone-api"><code>NgRx</code> with <code>standalone api</code></h1>
<h2 id="heading-root-store-1">Root store</h2>
<p>The root store should be defined within <code>bootstrapApplication</code> function. The store will be provided in the <code>providers</code> instead of <code>NgModule.imports</code>. The equivalents:</p>
<ul>
<li><p><code>StoreModule.forRoot()</code> -&gt; <code>provideStore()</code></p>
</li>
<li><p><code>EffectsModule.forRoot()</code> -&gt; <code>provideEffects()</code></p>
</li>
<li><p><code>StoreRouterConnectingModule.forRoot()</code> -&gt; <code>provideRouterStore()</code></p>
</li>
<li><p><code>StoreDevtoolsModule.instrument()</code> -&gt; <code>provideStoreDevtools()</code></p>
</li>
</ul>
<pre><code class="lang-typescript">bootstrapApplication(AppComponent, {
  providers: [
    provideStore({ router: routerReducer, auth: AuthReducer }),
    provideEffects([AuthEffects]),
    provideRouterStore(),
    provideStoreDevtools(),
  ]
});
</code></pre>
<h2 id="heading-feature-store-1">Feature store</h2>
<p>The feature store has to be defined within the route <code>providers</code> instead of <code>NgModule.imports</code>. The equivalents:</p>
<ul>
<li><p><code>StoreModule.forFeature()</code> -&gt; <code>provideStoreFeature()</code></p>
</li>
<li><p><code>EffectsModule.forFeature()</code> -&gt; <code>provideFeatureEffects()</code></p>
</li>
</ul>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> featureRoutes: Route[] = [
  {
    path: <span class="hljs-string">''</span>,
    providers: [
      provideStoreFeature(<span class="hljs-string">'feature'</span>, featureReducer),
      provideFeatureEffects([FeatureEffects]),
    ],
  },
];
</code></pre>
<p>That's all. Using <code>NgRx</code> standalone api is very simple and the functions are self-explanatory.</p>
]]></content:encoded></item><item><title><![CDATA[Angular standalone components, directives, pipes]]></title><description><![CDATA[The standalone components are getting more and more attention in the Angular world. Even the creators encourage people to use them by default. In this article, I'll show how to use standalone components, directives and pipes.
Setup
If unsure how to s...]]></description><link>https://blog.procode.pl/angular-standalone-components-directives-pipes</link><guid isPermaLink="true">https://blog.procode.pl/angular-standalone-components-directives-pipes</guid><category><![CDATA[Angular]]></category><category><![CDATA[webdev]]></category><category><![CDATA[standalone-components]]></category><category><![CDATA[tips]]></category><dc:creator><![CDATA[Bartosz Szłapak]]></dc:creator><pubDate>Thu, 12 Oct 2023 15:54:22 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1697125954145/b6023d27-473b-48b6-892d-77af50aadfef.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The standalone components are getting more and more attention in the Angular world. Even the creators encourage people to use them by default. In this article, I'll show how to use standalone components, directives and pipes.</p>
<h1 id="heading-setup">Setup</h1>
<p>If unsure how to set up Angular with Jest please refer to the article: https://barcioch.pro/angular-with-jest-setup.</p>
<h1 id="heading-the-difference">The difference</h1>
<p>Before Angular@14 everything had to be defined in the <code>NgModule</code>s. They are contextual containers for the application features. All external modules have to be imported in <code>imports</code> section, providers in <code>providers</code> section and modules' components in <code>declarations</code> section. If any of the module's component is to be used in the different place of the application, it has to be exported in <code>exports</code> section.</p>
<pre><code class="lang-typescript"><span class="hljs-meta">@NgModule</span>({
  declarations: [
    AppComponent,
    InternalComponent,
  ],
  imports: [
    ButtonsModule
  ],
  providers: [
    MyService
  ],
  <span class="hljs-built_in">exports</span>: [
    AppComponent
  ]
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AppModule { }
</code></pre>
<p>With the introduction of standalone features (components, directives, pipes) they became obsolete. To define the feature as standalone, you have to add <code>standalone: true</code> property to the <code>@Component</code>, <code>@Pipe</code> or <code>@Directive</code> decorator. Now, each feature is self-contained and can have its own <code>imports</code> section.</p>
<pre><code class="lang-typescript"><span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-test'</span>,
  standalone: <span class="hljs-literal">true</span>,
  imports: [MyDirective, MyPipe, MyComponent],
  providers: [MyService]
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> TestComponent {

}
</code></pre>
<h1 id="heading-basic-examples">Basic examples</h1>
<h2 id="heading-pipe">Pipe</h2>
<p>This example pipe reverses the order of the passed string.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Pipe, PipeTransform } <span class="hljs-keyword">from</span> <span class="hljs-string">"@angular/core"</span>;

<span class="hljs-meta">@Pipe</span>({
  name: <span class="hljs-string">"reverseText"</span>,
  standalone: <span class="hljs-literal">true</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> ReverseTextPipe <span class="hljs-keyword">implements</span> PipeTransform {
  transform(value: <span class="hljs-built_in">string</span> | <span class="hljs-literal">undefined</span> | <span class="hljs-literal">null</span>): <span class="hljs-built_in">string</span> {
    <span class="hljs-keyword">if</span> (value === <span class="hljs-literal">null</span> || value === <span class="hljs-literal">undefined</span>) {
      <span class="hljs-keyword">return</span> <span class="hljs-string">''</span>;
    }

    <span class="hljs-keyword">return</span> value.split(<span class="hljs-string">''</span>).reverse().join(<span class="hljs-string">''</span>);
  }
}
</code></pre>
<h3 id="heading-test-it">Test it</h3>
<p>The standard pipe test.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { ReverseTextPipe } <span class="hljs-keyword">from</span> <span class="hljs-string">'./reverse-text.pipe'</span>;

describe(<span class="hljs-string">'ReverseTextPipe'</span>, <span class="hljs-function">() =&gt;</span> {
  describe(<span class="hljs-string">'when pipe is created'</span>, <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">let</span> pipe: ReverseTextPipe;

    beforeAll(<span class="hljs-function">() =&gt;</span> {
      pipe = <span class="hljs-keyword">new</span> ReverseTextPipe();
    });

    it.each([
      { input: <span class="hljs-literal">undefined</span>, text: <span class="hljs-string">'undefined'</span> },
      { input: <span class="hljs-literal">null</span>, text: <span class="hljs-string">'null'</span> },
      { input: <span class="hljs-string">''</span>, text: <span class="hljs-string">'empty string'</span> },
    ])(<span class="hljs-string">'should transform $text to empty string'</span>, <span class="hljs-function">(<span class="hljs-params">{ input, text }</span>) =&gt;</span> {
      expect(pipe.transform(input)).toBe(<span class="hljs-string">''</span>);
    });

    it(<span class="hljs-string">'should reverse the "reverse me" text to "em esrever"'</span>, <span class="hljs-function">() =&gt;</span> {
      expect(pipe.transform(<span class="hljs-string">'reverse me'</span>)).toBe(<span class="hljs-string">'em esrever'</span>);
    });
  });
});
</code></pre>
<h2 id="heading-directive">Directive</h2>
<p>This example directive adds <code>data-test-id</code> attribute with static <code>aaa-bb-1</code> value.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Directive, HostBinding, HostListener, inject } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { Logger } <span class="hljs-keyword">from</span> <span class="hljs-string">'../services/logger'</span>;

<span class="hljs-meta">@Directive</span>({
  selector: <span class="hljs-string">'[test-id]'</span>,
  standalone: <span class="hljs-literal">true</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> TestIdDirective {
  <span class="hljs-meta">@HostBinding</span>(<span class="hljs-string">"attr.data-test-id"</span>) testId = <span class="hljs-string">'aaa-bb-1'</span>;
}
</code></pre>
<h3 id="heading-test-it-1">Test it</h3>
<p>The test uses a standalone <code>TestComponent</code> as a wrapper. There is no <code>TestBed.configureTestingModule()</code> call since there is no <code>NgModule</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { ComponentFixture, TestBed } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core/testing'</span>;
<span class="hljs-keyword">import</span> { Component, DebugElement } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { By } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/platform-browser'</span>;
<span class="hljs-keyword">import</span> { TestIdDirective } <span class="hljs-keyword">from</span> <span class="hljs-string">'./test-id.directive'</span>;

describe(<span class="hljs-string">'TestIdDirective'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">let</span> fixture: ComponentFixture&lt;TestComponent&gt;;

  beforeEach(<span class="hljs-function">() =&gt;</span> {
    fixture = TestBed.createComponent(TestComponent);
    fixture.detectChanges();
  });

  describe(<span class="hljs-string">'when component with directive is initialized'</span>, <span class="hljs-function">() =&gt;</span> {
    it(<span class="hljs-string">'should add data-test-id attribute to the element'</span>, <span class="hljs-function">() =&gt;</span> {
      expect(getElement().nativeElement.getAttribute(<span class="hljs-string">'data-test-id'</span>)).toBe(<span class="hljs-string">'aaa-bb-1'</span>);
    });
  });

  <span class="hljs-keyword">const</span> getElement = (): <span class="hljs-function"><span class="hljs-params">DebugElement</span> =&gt;</span> {
    <span class="hljs-keyword">return</span> fixture.debugElement.query(By.css(<span class="hljs-string">'[data-test="element-with-id"]'</span>));
  }
});


<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-test'</span>,
  standalone: <span class="hljs-literal">true</span>,
  imports: [TestIdDirective],
  template: <span class="hljs-string">`
    &lt;div data-test="element-with-id" test-id&gt;&lt;/div&gt;`</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> TestComponent {

}
</code></pre>
<h2 id="heading-component">Component</h2>
<p>A simple component that wraps passed content in <code>&lt;h1&gt;</code> tags.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Component } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;

<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-h1'</span>,
  standalone: <span class="hljs-literal">true</span>,
  template: <span class="hljs-string">`
    &lt;h1 data-test="header-component"&gt;
      &lt;ng-content&gt;&lt;/ng-content&gt;
    &lt;/h1&gt;`</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> HeaderComponent {

}
</code></pre>
<h3 id="heading-test-it-2">Test it</h3>
<p>The test uses a standalone <code>TestComponent</code> as a wrapper. There is no <code>TestBed.configureTestingModule()</code> call since there is no <code>NgModule</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { ComponentFixture, TestBed } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core/testing'</span>;
<span class="hljs-keyword">import</span> { Component, DebugElement } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { By } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/platform-browser'</span>;
<span class="hljs-keyword">import</span> { HeaderComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">'./header.component'</span>;

describe(<span class="hljs-string">'HeaderComponent'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">let</span> fixture: ComponentFixture&lt;HeaderComponent&gt;;

  beforeEach(<span class="hljs-function">() =&gt;</span> {
    fixture = TestBed.createComponent(TestComponent);
    fixture.detectChanges();
  });

  describe(<span class="hljs-string">'when component is initialized'</span>, <span class="hljs-function">() =&gt;</span> {
    it(<span class="hljs-string">'should display header component'</span>, <span class="hljs-function">() =&gt;</span> {
      expect(getElement()).toBeTruthy();
    });

    it(<span class="hljs-string">'should display header component content'</span>, <span class="hljs-function">() =&gt;</span> {
      expect(getElement().nativeElement.textContent).toBe(<span class="hljs-string">'the content'</span>);
    });
  });

  <span class="hljs-keyword">const</span> getElement = (): <span class="hljs-function"><span class="hljs-params">DebugElement</span> =&gt;</span> {
    <span class="hljs-keyword">return</span> fixture.debugElement.query(By.css(<span class="hljs-string">'[data-test="header"]'</span>));
  }
});


<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-test'</span>,
  standalone: <span class="hljs-literal">true</span>,
  imports: [HeaderComponent],
  template: <span class="hljs-string">`
    &lt;app-h1 data-test="header"&gt;the content&lt;/app-h1&gt;`</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> TestComponent {

}
</code></pre>
<h1 id="heading-advanced-example">Advanced example</h1>
<p>With the <code>NgModule</code> is gone (feature module), we no longer have its context. So where to place the providers, store and lazy loading? In the <code>Route</code> or in the <code>Component</code>. Personally, I'm against this approach, but right now, there is no alternative.</p>
<p>In the next examples, I'll be using the standalone <code>DummyComonent</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Component } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { RouterOutlet } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/router'</span>;

<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-dummy'</span>,
  standalone: <span class="hljs-literal">true</span>,
  imports: [RouterOutlet],
  template: <span class="hljs-string">`&lt;router-outlet&gt;&lt;/router-outlet&gt;`</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> DummyComponent {

}
</code></pre>
<h1 id="heading-bootstrapping-standalone-component">Bootstrapping standalone component</h1>
<h2 id="heading-bootstrapping-with-ngmodule">Bootstrapping with <code>NgModule</code></h2>
<p>Bootstrapping with <code>NgModule</code> requires putting a component in <code>bootstrap</code> property. It takes an array, but there is no point in having multiple application entry points.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// app.module.ts</span>

<span class="hljs-keyword">import</span> { NgModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { BrowserModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/platform-browser'</span>;
<span class="hljs-keyword">import</span> { AppComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">'./app.component'</span>;
<span class="hljs-keyword">import</span> { AppRoutingModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'./app-routing.module'</span>;

<span class="hljs-meta">@NgModule</span>({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
  ],
  bootstrap: [AppComponent]
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AppModule { }
</code></pre>
<p>In the <code>imports</code> property there is an imported <code>AppRoutingModule</code>, which contains definitions of initial app routes.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// app-routing.module.ts</span>

<span class="hljs-keyword">import</span> { RouterModule, Routes } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/router'</span>;
<span class="hljs-keyword">import</span> { NgModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { DummyComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">'./components/dummy.component'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> routes: Routes = [
  {
    path: <span class="hljs-string">`dummy`</span>,
    component: DummyComponent,
  },
];

<span class="hljs-meta">@NgModule</span>({
  imports: [RouterModule.forRoot(routes)],
  <span class="hljs-built_in">exports</span>: [RouterModule],
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AppRoutingModule {
}
</code></pre>
<p>The <code>main.ts</code> file bootstraps the application using the <code>AppModule</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// main.ts</span>

<span class="hljs-keyword">import</span> { platformBrowserDynamic } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/platform-browser-dynamic'</span>;
<span class="hljs-keyword">import</span> { AppModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'./app/app.module'</span>;

platformBrowserDynamic().bootstrapModule(AppModule)
  .catch(<span class="hljs-function"><span class="hljs-params">err</span> =&gt;</span> <span class="hljs-built_in">console</span>.error(err));
</code></pre>
<h2 id="heading-bootstrapping-with-standalone-component">Bootstrapping with standalone component</h2>
<p>The bootstrapped component has to be declared as standalone. I'll also add <code>router-outlet</code> and import <code>RouterOutlet</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Component } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { RouterOutlet } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/router'</span>;

<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-root'</span>,
  standalone: <span class="hljs-literal">true</span>,
  template: <span class="hljs-string">'&lt;router-outlet&gt;&lt;/router-outlet&gt;'</span>,
  imports: [RouterOutlet]
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AppComponent {
}
</code></pre>
<p>To achieve the same bootstrapping result with the standalone component, the Angular team provided the new <code>bootstrapApplication</code> function.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// main.ts</span>

<span class="hljs-keyword">import</span> { bootstrapApplication } <span class="hljs-keyword">from</span> <span class="hljs-string">"@angular/platform-browser"</span>;
<span class="hljs-keyword">import</span> { AppComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">'./app/app.component'</span>;
<span class="hljs-keyword">import</span> { provideRouter } <span class="hljs-keyword">from</span> <span class="hljs-string">"@angular/router"</span>;
<span class="hljs-keyword">import</span> { routes } <span class="hljs-keyword">from</span> <span class="hljs-string">'./app/app-routes'</span>;

bootstrapApplication(AppComponent, {
  providers: [
    provideRouter(routes),
  ]
});
</code></pre>
<p>The first argument is our main standalone component. The second is a <code>ApplicationConfig</code> with a <code>providers</code> property. You can provide <code>Router</code> or any other global dependencies that you might need.</p>
<p>The <code>provideRouter</code> function enables <code>Router</code> functionality and tree-shaking, which was not possible before.</p>
<h2 id="heading-lazy-loaded-standalone-components">Lazy-loaded standalone components</h2>
<p>To lazy load a standalone component there's a new route property <code>loadComponent</code> that is very similar to the <code>loadModule</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Routes } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/router'</span>;
<span class="hljs-keyword">import</span> { DummyComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">'./components/dummy.component'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> routes: Routes = [
  {
    path: <span class="hljs-string">`main`</span>,
    component: DummyComponent,
  },
  {
    path: <span class="hljs-string">'lazy-route'</span>,
    loadComponent: <span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'./components/dummy.component'</span>).then(<span class="hljs-function"><span class="hljs-params">c</span> =&gt;</span> c.DummyComponent)
  }
];
</code></pre>
<h2 id="heading-lazy-loaded-routes">Lazy-loaded routes</h2>
<p>The following examples will be achieving the <code>/feature1/feature2</code> routing. Both <code>features</code> are lazy-loaded.</p>
<h3 id="heading-nested-ngmodule-routing">Nested <code>NgModule</code> routing</h3>
<p>When using the <code>feature module</code> we import the <code>feature routing module</code> that imports <code>RouterModule</code> with passed routes. Lazy loading is achieved by utilizing <code>loadChildren</code> property.</p>
<h4 id="heading-application-structure-using-the-ngmodules">Application structure using the <code>NgModules</code></h4>
<p>The <code>AppModule</code> imports the <code>AppRoutingModule</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// app.module.ts</span>

<span class="hljs-keyword">import</span> { NgModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { BrowserModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/platform-browser'</span>;
<span class="hljs-keyword">import</span> { AppComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">'./app.component'</span>;
<span class="hljs-keyword">import</span> { AppRoutingModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'./app-routing.module'</span>;
<span class="hljs-keyword">import</span> { DummyComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">'./components/dummy.component'</span>;

<span class="hljs-meta">@NgModule</span>({
  declarations: [
    AppComponent,
    DummyComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
  ],
  bootstrap: [AppComponent]
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AppModule { }
</code></pre>
<p>The <code>AppRoutingModule</code> defines the lazy route <code>feature1</code> that points to <code>Feature1Module</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// app-routing.module.ts</span>

<span class="hljs-keyword">import</span> { RouterModule, Routes } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/router'</span>;
<span class="hljs-keyword">import</span> { NgModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { DummyComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">'./components/dummy.component'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> routes: Routes = [
  {
    path: <span class="hljs-string">`main`</span>,
    component: DummyComponent,
  },
  {
    path: <span class="hljs-string">'feature1'</span>,
    loadChildren: <span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'./feature1/feature1.module'</span>).then(<span class="hljs-function"><span class="hljs-params">m</span> =&gt;</span> m.Feature1Module)
  }
];

<span class="hljs-meta">@NgModule</span>({
  imports: [RouterModule.forRoot(routes)],
  <span class="hljs-built_in">exports</span>: [RouterModule],
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AppRoutingModule {
}
</code></pre>
<p>The <code>Feature1Module</code> imports <code>Feature1RoutingModule</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// feature1.module.ts</span>

<span class="hljs-keyword">import</span> { NgModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { CommonModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/common'</span>;
<span class="hljs-keyword">import</span> { Feature1Component } <span class="hljs-keyword">from</span> <span class="hljs-string">'./feature1.component'</span>;
<span class="hljs-keyword">import</span> { Feature1RoutingModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'./feature1-routing.module'</span>;

<span class="hljs-meta">@NgModule</span>({
  declarations: [Feature1Component],
  imports: [
    CommonModule,
    Feature1RoutingModule
  ],
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> Feature1Module {
}
</code></pre>
<p>The <code>Feature1RoutingModule</code> points directly to <code>Feature1Component</code> and defines a lazy child route <code>feature2</code> that points to <code>Feature2Module</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// feature1-routing.module</span>

<span class="hljs-keyword">import</span> { RouterModule, Routes } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/router'</span>;
<span class="hljs-keyword">import</span> { NgModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { Feature1Component } <span class="hljs-keyword">from</span> <span class="hljs-string">'./feature1.component'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> routes: Routes = [
  {
    path: <span class="hljs-string">``</span>,
    component: Feature1Component,
    children: [
      {
        path: <span class="hljs-string">'feature2'</span>,
        loadChildren: <span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'./feature2/feature2.module'</span>).then(<span class="hljs-function"><span class="hljs-params">m</span> =&gt;</span> m.Feature2Module)
      }
    ]
  },
];

<span class="hljs-meta">@NgModule</span>({
  imports: [RouterModule.forChild(routes)],
  <span class="hljs-built_in">exports</span>: [RouterModule],
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> Feature1RoutingModule {
}
</code></pre>
<p>The <code>Feature2Module</code> imports <code>Feature2RoutingModule</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { NgModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { CommonModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/common'</span>;
<span class="hljs-keyword">import</span> { Feature2Component } <span class="hljs-keyword">from</span> <span class="hljs-string">'./feature2.component'</span>;
<span class="hljs-keyword">import</span> { Feature2RoutingModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'./feature2-routing.module'</span>;

<span class="hljs-meta">@NgModule</span>({
  declarations: [Feature2Component],
  imports: [
    CommonModule,
    Feature2RoutingModule
  ],
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> Feature2Module {
}
</code></pre>
<p>The <code>Feature2RoutingModule</code> points directly to <code>Feature2Component</code></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { RouterModule, Routes } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/router'</span>;
<span class="hljs-keyword">import</span> { NgModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { Feature2Component } <span class="hljs-keyword">from</span> <span class="hljs-string">'./feature2.component'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> routes: Routes = [
  {
    path: <span class="hljs-string">``</span>,
    component: Feature2Component,
  },
];

<span class="hljs-meta">@NgModule</span>({
  imports: [RouterModule.forChild(routes)],
  <span class="hljs-built_in">exports</span>: [RouterModule],
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> Feature2RoutingModule {
}
</code></pre>
<h3 id="heading-nested-standalone-component-routing">Nested standalone component routing</h3>
<p>With the <code>NgModule</code> is gone all we need to do is import the routes with the old <code>loadChildren</code> property.</p>
<h4 id="heading-application-structure-using-the-standalone-components">Application structure using the <code>standalone components</code></h4>
<p>The <code>main.ts</code> file bootstraps the application with the main routes.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// main.ts</span>

<span class="hljs-keyword">import</span> { bootstrapApplication } <span class="hljs-keyword">from</span> <span class="hljs-string">"@angular/platform-browser"</span>;
<span class="hljs-keyword">import</span> { AppComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">'./app/app.component'</span>;
<span class="hljs-keyword">import</span> { provideRouter } <span class="hljs-keyword">from</span> <span class="hljs-string">"@angular/router"</span>;
<span class="hljs-keyword">import</span> { routes } <span class="hljs-keyword">from</span> <span class="hljs-string">'./app/app-routes'</span>;


bootstrapApplication(AppComponent, {
  providers: [
    provideRouter(routes),
  ]
});
</code></pre>
<p>The main app routes file defines lazy route <code>feature1</code> that points to <code>feature1-routes</code> file.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// app-routes.ts</span>

<span class="hljs-keyword">import</span> { Routes } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/router'</span>;
<span class="hljs-keyword">import</span> { DummyComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">'./components/dummy.component'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> routes: Routes = [
  {
    path: <span class="hljs-string">`main`</span>,
    component: DummyComponent,
  },
  {
    path: <span class="hljs-string">'feature1'</span>,
    loadChildren: <span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'./feature1/feature1-routes'</span>).then(<span class="hljs-function"><span class="hljs-params">r</span> =&gt;</span> r.routes)
  }
];
</code></pre>
<p>The <code>feature1</code> points directly to <code>Feature1Component</code>. Also, it defines the lazy child route <code>feature2</code> that points to <code>feature2-routes</code> file.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// feature1-routes.ts</span>

<span class="hljs-keyword">import</span> { Routes } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/router'</span>;
<span class="hljs-keyword">import</span> { Feature1Component } <span class="hljs-keyword">from</span> <span class="hljs-string">'./feature1.component'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> routes: Routes = [
  {
    path: <span class="hljs-string">``</span>,
    component: Feature1Component,
    children: [
      {
        path: <span class="hljs-string">'feature2'</span>,
        loadChildren: <span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'./feature2/feature2-routes'</span>).then(<span class="hljs-function"><span class="hljs-params">r</span> =&gt;</span> r.routes)
      }
    ]
  },
];
</code></pre>
<p>The <code>feature2</code> points directly to <code>Feature2Component</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// feature2-routes.ts</span>

<span class="hljs-keyword">import</span> { RouterModule, Routes } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/router'</span>;
<span class="hljs-keyword">import</span> { NgModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { Feature2Component } <span class="hljs-keyword">from</span> <span class="hljs-string">'./feature2.component'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> routes: Routes = [
  {
    path: <span class="hljs-string">``</span>,
    component: Feature2Component,
  },
];
</code></pre>
<p>And that's all in terms of routing. The <code>NgModules</code> are gone.</p>
<h3 id="heading-test-it-3">Test it</h3>
<p>To test the routing you still need to use the <code>TestBed</code>. I'm using <code>configureTestingModule</code> method without calling <code>compileComponents()</code> since the latter is not needed. Also, the <code>RouterTestingHarness</code> will come in handy while testing the routes. The <code>routes</code> are imported directly from the main app route file.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { TestBed } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core/testing'</span>;
<span class="hljs-keyword">import</span> { By } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/platform-browser'</span>;
<span class="hljs-keyword">import</span> { RouterTestingHarness } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/router/testing'</span>;
<span class="hljs-keyword">import</span> { provideRouter } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/router'</span>;
<span class="hljs-keyword">import</span> { routes } <span class="hljs-keyword">from</span> <span class="hljs-string">'./app-routes'</span>;


describe(<span class="hljs-string">'App Routing'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">let</span> harness: RouterTestingHarness;

  beforeEach(<span class="hljs-keyword">async</span> () =&gt; {
    TestBed.configureTestingModule({
      providers: [provideRouter(routes)]
    });

    harness = <span class="hljs-keyword">await</span> RouterTestingHarness.create();
  });

  describe(<span class="hljs-string">'when testing module is initialized'</span>, <span class="hljs-function">() =&gt;</span> {
    describe(<span class="hljs-string">'and user enters "/main" route'</span>, <span class="hljs-function">() =&gt;</span> {
      beforeEach(<span class="hljs-keyword">async</span> () =&gt; {
        <span class="hljs-keyword">await</span> harness.navigateByUrl(<span class="hljs-string">'/main'</span>);
      });

      it(<span class="hljs-string">'should render DummyComponent'</span>, <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">const</span> element = harness.fixture.debugElement.query(By.css(<span class="hljs-string">'app-dummy'</span>));
        expect(element).toBeTruthy()
      });
    });

    describe(<span class="hljs-string">'and user enters "/feature1" route'</span>, <span class="hljs-function">() =&gt;</span> {
      beforeEach(<span class="hljs-keyword">async</span> () =&gt; {
        <span class="hljs-keyword">await</span> harness.navigateByUrl(<span class="hljs-string">'/feature1'</span>);
      });

      it(<span class="hljs-string">'should render Feature1Component'</span>, <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">const</span> element = harness.fixture.debugElement.query(By.css(<span class="hljs-string">'app-feature1'</span>));
        expect(element).toBeTruthy()
      });
    });

    describe(<span class="hljs-string">'and user enters "/feature1/feature2" route'</span>, <span class="hljs-function">() =&gt;</span> {
      beforeEach(<span class="hljs-keyword">async</span> () =&gt; {
        <span class="hljs-keyword">await</span> harness.navigateByUrl(<span class="hljs-string">'/feature1/feature2'</span>);
      });

      it(<span class="hljs-string">'should render Feature1Component'</span>, <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">const</span> element = harness.fixture.debugElement.query(By.css(<span class="hljs-string">'app-feature1'</span>));
        expect(element).toBeTruthy()
      });

      it(<span class="hljs-string">'should render Feature2Component'</span>, <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">const</span> element = harness.fixture.debugElement.query(By.css(<span class="hljs-string">'app-feature2'</span>));
        expect(element).toBeTruthy()
      });
    });
  });
});
</code></pre>
<h1 id="heading-providers">Providers</h1>
<p>Without the <code>NgModule</code> <code>providers</code> property, there are two places to declare them unless <code>providedIn: 'root'</code> is used.</p>
<p>1 - Component providers - the component and its all descendants have access to the provider (through <code>Component Injector</code>). Just use the <code>providers</code> property of <code>@Component</code> decorator.</p>
<pre><code class="lang-typescript">  <span class="hljs-keyword">import</span> { Component } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
  <span class="hljs-keyword">import</span> { RouterOutlet } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/router'</span>;

  <span class="hljs-meta">@Component</span>({
    selector: <span class="hljs-string">'app-dummy'</span>,
    standalone: <span class="hljs-literal">true</span>,
    imports: [RouterOutlet],
    providers: [MyService],
    template: <span class="hljs-string">`&lt;router-outlet&gt;&lt;/router-outlet&gt;`</span>,
  })
  <span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> DummyComponent {

  }
</code></pre>
<p>2 - Route providers - all the route descendants have access to the provider (through <code>Route Injector</code>). Just use the <code>providers</code> property of <code>Route</code> interface.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> routes: Routes = [
  {
    providers: [MyService],
    path: <span class="hljs-string">'feature1'</span>,
    loadChildren: <span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'./feature1/feature1-routes'</span>).then(<span class="hljs-function"><span class="hljs-params">r</span> =&gt;</span> r.routes)
  }
]
</code></pre>
<h1 id="heading-source-code">Source code</h1>
<p>https://gitlab.com/barcioch-blog-examples/015-angular-standalone-elements</p>
]]></content:encoded></item><item><title><![CDATA[Understanding Data Passing in Angular Using Router and Component Binding (ComponentInputBinding)]]></title><description><![CDATA[In this article, I'll show how to extract parameters and data from Angular routes using the component input binding feature.
Setup
If unsure how to set up Angular with Jest please refer to the article: https://barcioch.pro/angular-with-jest-setup.
Co...]]></description><link>https://blog.procode.pl/understanding-data-passing-in-angular-using-router-and-component-binding-componentinputbinding</link><guid isPermaLink="true">https://blog.procode.pl/understanding-data-passing-in-angular-using-router-and-component-binding-componentinputbinding</guid><category><![CDATA[Angular]]></category><category><![CDATA[routing]]></category><category><![CDATA[webdev]]></category><category><![CDATA[component input binding]]></category><dc:creator><![CDATA[Bartosz Szłapak]]></dc:creator><pubDate>Fri, 06 Oct 2023 16:00:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1696607890667/4881ee6c-884d-4aae-9cb0-800fdfb0eeff.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this article, I'll show how to extract parameters and data from Angular routes using the component input binding feature.</p>
<h1 id="heading-setup">Setup</h1>
<p>If unsure how to set up Angular with Jest please refer to the article: https://barcioch.pro/angular-with-jest-setup.</p>
<h1 id="heading-component-input-binding">Component Input Binding</h1>
<h2 id="heading-enable-it">Enable it</h2>
<p>There are two ways of enabling this feature.</p>
<ol>
<li>import <code>RouterModule.forRoot</code> and pass the option <code>bindToComponentInputs: true</code> and <code>paramsInheritanceStrategy: 'always'</code></li>
</ol>
<pre><code class="lang-typescript">imports: [
  ...
  RouterModule.forRoot(routes, {bindToComponentInputs: <span class="hljs-literal">true</span>, paramsInheritanceStrategy: <span class="hljs-string">'always'</span>})
]
</code></pre>
<ol>
<li>Use provider function <code>provideRouter</code> and pass the <code>withComponentInputBinding()</code> and <code>withRouterConfig({paramsInheritanceStrategy: 'always'})</code> features</li>
</ol>
<pre><code class="lang-typescript">providers: [
  ...
  provideRouter(routes, withComponentInputBinding(), withRouterConfig({paramsInheritanceStrategy: <span class="hljs-string">'always'</span>}))
]
</code></pre>
<h2 id="heading-use-it">Use it</h2>
<p>To use this feature define component inputs using the well-known <code>@Input</code> decorator.</p>
<pre><code class="lang-typescript"><span class="hljs-meta">@Component</span>({
  [...]
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> TestComponent {
  <span class="hljs-meta">@Input</span>() testId?: <span class="hljs-built_in">string</span>;
  <span class="hljs-meta">@Input</span>() sort?: <span class="hljs-built_in">string</span>;
  <span class="hljs-meta">@Input</span>() secretData?: <span class="hljs-built_in">string</span>;
}
</code></pre>
<p>Also, this component has to be <code>routed</code>. That means, one of the application routes has to point directly to it.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> routes: Routes = [
  {
    path: <span class="hljs-string">'test/:testId'</span>,
    component: TestComponent,
    data: {secretData: <span class="hljs-string">'secret'</span>}
  },
];
</code></pre>
<p>Now, <code>path params</code>, <code>query params</code> and <code>data</code> will be assigned to the inputs by matching their names. If the name collision occurs the route parameters are assigned in the following order: <code>data</code> &gt; <code>path param</code> &gt; <code>query param</code>. Also, they won't override each other, i.e: you cannot override <code>path param</code> with <code>query param</code> and so on.</p>
<h1 id="heading-test-it">Test it</h1>
<h2 id="heading-routes">Routes</h2>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Routes } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/router'</span>;
<span class="hljs-keyword">import</span> { RoutedComponentComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">'../routed-component/routed-component.component'</span>;


<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> routes: Routes = [
  {
    path: <span class="hljs-string">'params/:pathParam'</span>,
    component: RoutedComponentComponent,
    data: {dataParam: <span class="hljs-string">'secret'</span>}
  },
];
</code></pre>
<h2 id="heading-test-components">Test components</h2>
<p>I'll be using two components. Their purpose is to test, that route information is bound only in the the routed components. The non-routed behave as before - you have to pass parameters explicitly.</p>
<p>The routed one:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Component, Input } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;

<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-routed-component'</span>,
  template: <span class="hljs-string">`
    &lt;span data-test="routed-pathParam"&gt;{{ pathParam }}&lt;/span&gt;
    &lt;span data-test="routed-queryParam"&gt;{{ queryParam }}&lt;/span&gt;
    &lt;span data-test="routed-nonExistingParam"&gt;{{ nonExistingParam }}&lt;/span&gt;
    &lt;span data-test="routed-dataParam"&gt;{{ dataParam }}&lt;/span&gt;

    &lt;app-non-routed-component&gt;&lt;/app-non-routed-component&gt;
  `</span>
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> RoutedComponentComponent {
  <span class="hljs-meta">@Input</span>() pathParam?: <span class="hljs-built_in">string</span>;
  <span class="hljs-meta">@Input</span>() nonExistingParam?: <span class="hljs-built_in">string</span>;
  <span class="hljs-meta">@Input</span>() queryParam?: <span class="hljs-built_in">string</span>;
  <span class="hljs-meta">@Input</span>() dataParam?: <span class="hljs-built_in">string</span>;
}
</code></pre>
<p>and non-routed:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Component, Input } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;

<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-non-routed-component'</span>,
  template: <span class="hljs-string">`
    &lt;span data-test="non-routed-pathParam"&gt;{{ pathParam }}&lt;/span&gt;
    &lt;span data-test="non-routed-queryParam"&gt;{{ queryParam }}&lt;/span&gt;
    &lt;span data-test="non-routed-nonExistingParam"&gt;{{ nonExistingParam }}&lt;/span&gt;
    &lt;span data-test="non-routed-dataParam"&gt;{{ dataParam }}&lt;/span&gt;
  `</span>
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> NonRoutedComponentComponent {
  <span class="hljs-meta">@Input</span>() pathParam?: <span class="hljs-built_in">string</span>;
  <span class="hljs-meta">@Input</span>() nonExistingParam?: <span class="hljs-built_in">string</span>;
  <span class="hljs-meta">@Input</span>() queryParam?: <span class="hljs-built_in">string</span>;
  <span class="hljs-meta">@Input</span>() dataParam?: <span class="hljs-built_in">string</span>;
}
</code></pre>
<h2 id="heading-test-suite">Test suite</h2>
<p>The test suite employs <code>RouterTestingHarness</code> to reduce the boilerplate code. The testing procedure is simple: navigate through the routes and verify the bound information from routed and non-routed components. Also, test the name collision.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { provideRouter, withComponentInputBinding, withRouterConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/router'</span>;
<span class="hljs-keyword">import</span> { TestBed } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core/testing'</span>;
<span class="hljs-keyword">import</span> { By } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/platform-browser'</span>;
<span class="hljs-keyword">import</span> { routes } <span class="hljs-keyword">from</span> <span class="hljs-string">'./app-routing'</span>;
<span class="hljs-keyword">import</span> { RouterTestingHarness } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/router/testing'</span>;
<span class="hljs-keyword">import</span> { RoutedComponentComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">'../routed-component/routed-component.component'</span>;
<span class="hljs-keyword">import</span> { NonRoutedComponentComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">'../non-routed-component/non-routed-component.component'</span>;


describe(<span class="hljs-string">'ComponentInputBinding'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">let</span> harness: RouterTestingHarness;

  beforeEach(<span class="hljs-keyword">async</span> () =&gt; {
    TestBed.configureTestingModule({
      declarations: [RoutedComponentComponent, NonRoutedComponentComponent],
      providers: [provideRouter(routes, withComponentInputBinding(), withRouterConfig({ paramsInheritanceStrategy: <span class="hljs-string">'always'</span> }))],
    });

    harness = <span class="hljs-keyword">await</span> RouterTestingHarness.create();
  });

  describe(<span class="hljs-string">'Test standard route params'</span>, <span class="hljs-function">() =&gt;</span> {
    describe(<span class="hljs-string">'when user enters /params/123?queryParam=paramFromQuery url'</span>, <span class="hljs-function">() =&gt;</span> {
      beforeEach(<span class="hljs-keyword">async</span> () =&gt; {
        <span class="hljs-keyword">await</span> harness.navigateByUrl(<span class="hljs-string">'/params/123?queryParam=paramFromQuery'</span>);
        harness.fixture.detectChanges();
      });

      it(<span class="hljs-string">'should display routed component params'</span>, <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">const</span> expected: ComponentParams = {
          pathParam: <span class="hljs-string">'123'</span>,
          queryParam: <span class="hljs-string">'paramFromQuery'</span>,
          nonExistingParam: <span class="hljs-string">''</span>,
          dataParam: <span class="hljs-string">'secret'</span>,
        };
        expect(getRoutedComponentParams()).toEqual(expected);
      });

      it(<span class="hljs-string">'should display empty input values in non-routed component'</span>, <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">const</span> expected: ComponentParams = {
          pathParam: <span class="hljs-string">''</span>,
          queryParam: <span class="hljs-string">''</span>,
          nonExistingParam: <span class="hljs-string">''</span>,
          dataParam: <span class="hljs-string">''</span>,
        };
        expect(getNonRoutedComponentParams()).toEqual(expected);
      });

      describe(<span class="hljs-string">'and user navigates to same route with different pathParam and queryParam (/params/456?queryParam=aNewValue)'</span>, <span class="hljs-function">() =&gt;</span> {
        beforeEach(<span class="hljs-keyword">async</span> () =&gt; {
          <span class="hljs-keyword">await</span> harness.navigateByUrl(<span class="hljs-string">'/params/456?queryParam=aNewValue'</span>);
          harness.fixture.detectChanges();
        });

        it(<span class="hljs-string">'should display routed component params'</span>, <span class="hljs-function">() =&gt;</span> {
          <span class="hljs-keyword">const</span> expected: ComponentParams = {
            pathParam: <span class="hljs-string">'456'</span>,
            queryParam: <span class="hljs-string">'aNewValue'</span>,
            nonExistingParam: <span class="hljs-string">''</span>,
            dataParam: <span class="hljs-string">'secret'</span>,
          };
          expect(getRoutedComponentParams()).toEqual(expected);
        });

        it(<span class="hljs-string">'should display empty input values in non-routed component'</span>, <span class="hljs-function">() =&gt;</span> {
          <span class="hljs-keyword">const</span> expected: ComponentParams = {
            pathParam: <span class="hljs-string">''</span>,
            queryParam: <span class="hljs-string">''</span>,
            nonExistingParam: <span class="hljs-string">''</span>,
            dataParam: <span class="hljs-string">''</span>,
          };
          expect(getNonRoutedComponentParams()).toEqual(expected);
        });
      });
    });
  });

  describe(<span class="hljs-string">'Test name collision'</span>, <span class="hljs-function">() =&gt;</span> {
    describe(<span class="hljs-string">'when user enters url with query params named same as pathParam and dataParam (/params/123?pathParam=pathParamFromQuery&amp;dataParam=dataParamFromQuery)'</span>, <span class="hljs-function">() =&gt;</span> {
      beforeEach(<span class="hljs-keyword">async</span> () =&gt; {
        <span class="hljs-keyword">await</span> harness.navigateByUrl(<span class="hljs-string">'/params/123?pathParam=pathParamFromQuery&amp;dataParam=dataParamFromQuery'</span>);
        harness.fixture.detectChanges();
      });

      it(<span class="hljs-string">'should not override path params and data'</span>, <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">const</span> expected: ComponentParams = {
          pathParam: <span class="hljs-string">'123'</span>,
          queryParam: <span class="hljs-string">''</span>,
          nonExistingParam: <span class="hljs-string">''</span>,
          dataParam: <span class="hljs-string">'secret'</span>,
        };
        expect(getRoutedComponentParams()).toEqual(expected);
      });

      it(<span class="hljs-string">'should display empty input values in non-routed component'</span>, <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">const</span> expected: ComponentParams = {
          pathParam: <span class="hljs-string">''</span>,
          queryParam: <span class="hljs-string">''</span>,
          nonExistingParam: <span class="hljs-string">''</span>,
          dataParam: <span class="hljs-string">''</span>,
        };
        expect(getNonRoutedComponentParams()).toEqual(expected);
      });
    });
  });

  <span class="hljs-keyword">const</span> getRoutedComponentParams = (): <span class="hljs-function"><span class="hljs-params">ComponentParams</span> =&gt;</span> {
    <span class="hljs-keyword">return</span> {
      pathParam: harness.fixture.debugElement.query(By.css(<span class="hljs-string">'[data-test="routed-pathParam"]'</span>)).nativeElement.textContent,
      queryParam: harness.fixture.debugElement.query(By.css(<span class="hljs-string">'[data-test="routed-queryParam"]'</span>)).nativeElement.textContent,
      nonExistingParam: harness.fixture.debugElement.query(By.css(<span class="hljs-string">'[data-test="routed-nonExistingParam"]'</span>)).nativeElement.textContent,
      dataParam: harness.fixture.debugElement.query(By.css(<span class="hljs-string">'[data-test="routed-dataParam"]'</span>)).nativeElement.textContent,
    }
  }

  <span class="hljs-keyword">const</span> getNonRoutedComponentParams = (): <span class="hljs-function"><span class="hljs-params">ComponentParams</span> =&gt;</span> {
    <span class="hljs-keyword">return</span> {
      pathParam: harness.fixture.debugElement.query(By.css(<span class="hljs-string">'[data-test="non-routed-pathParam"]'</span>)).nativeElement.textContent,
      queryParam: harness.fixture.debugElement.query(By.css(<span class="hljs-string">'[data-test="non-routed-queryParam"]'</span>)).nativeElement.textContent,
      nonExistingParam: harness.fixture.debugElement.query(By.css(<span class="hljs-string">'[data-test="non-routed-nonExistingParam"]'</span>)).nativeElement.textContent,
      dataParam: harness.fixture.debugElement.query(By.css(<span class="hljs-string">'[data-test="non-routed-dataParam"]'</span>)).nativeElement.textContent,
    }
  }
});

<span class="hljs-keyword">interface</span> ComponentParams {
  pathParam: <span class="hljs-built_in">string</span>;
  queryParam: <span class="hljs-built_in">string</span>;
  nonExistingParam: <span class="hljs-built_in">string</span>;
  dataParam: <span class="hljs-built_in">string</span>;
}
</code></pre>
<h1 id="heading-source-code">Source code</h1>
<p>https://gitlab.com/barcioch-blog-examples/014-angular-component-input-binding</p>
]]></content:encoded></item><item><title><![CDATA[Getting Started with Angular Route Parameters: A Beginner's Guide to Accessing and Testing Route Parameters]]></title><description><![CDATA[In this article, I'll show how to extract parameters and data from Angular routes using the native ActivatedRoute.
Setup
If unsure how to set up Angular with Jest please refer to the article: https://barcioch.pro/angular-with-jest-setup.
helper pipe
...]]></description><link>https://blog.procode.pl/getting-started-with-angular-route-parameters-a-beginners-guide-to-accessing-and-testing-route-parameters</link><guid isPermaLink="true">https://blog.procode.pl/getting-started-with-angular-route-parameters-a-beginners-guide-to-accessing-and-testing-route-parameters</guid><category><![CDATA[Angular]]></category><category><![CDATA[routing]]></category><category><![CDATA[activatedroute]]></category><category><![CDATA[route parameters]]></category><dc:creator><![CDATA[Bartosz Szłapak]]></dc:creator><pubDate>Thu, 05 Oct 2023 15:17:36 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/lFtttcsx5Vk/upload/8395faadf29eb14a5871759687d07400.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this article, I'll show how to extract parameters and data from Angular routes using the native <code>ActivatedRoute</code>.</p>
<h1 id="heading-setup">Setup</h1>
<p>If unsure how to set up Angular with Jest please refer to the article: https://barcioch.pro/angular-with-jest-setup.</p>
<h2 id="heading-helper-pipe">helper pipe</h2>
<p>I created a very basic pipe to format the <code>Params</code>. It's just for the testing purposes.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Pipe, PipeTransform } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { Params } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/router'</span>;

<span class="hljs-meta">@Pipe</span>({
  name: <span class="hljs-string">'formatParams'</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> FormatParamsPipe <span class="hljs-keyword">implements</span> PipeTransform {

  transform(input: Params): <span class="hljs-built_in">string</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">Object</span>.keys(input).map(<span class="hljs-function"><span class="hljs-params">key</span> =&gt;</span> <span class="hljs-string">`<span class="hljs-subst">${key}</span>:<span class="hljs-subst">${input[key]}</span>`</span>).join(<span class="hljs-string">','</span>);
  }
}
</code></pre>
<h1 id="heading-activatedroute">ActivatedRoute</h1>
<p>The first way of accessing route parameters is through the <code>ActivatedRoute</code>. It can be injected into a component and contains route information associated with a component. Since this class contains a lot of data, we'll be interested only in the following:</p>
<ul>
<li><p>path params</p>
</li>
<li><p>query params</p>
</li>
<li><p>data - custom data assigned to the routes.</p>
</li>
</ul>
<p>These properties are never nullish and are a type of <code>interface Params {[key: string]: any;}</code>. This means that the property names are always of <code>string</code> type but the values can be of <code>any</code> type.</p>
<h2 id="heading-activatedroutesnapshot">ActivatedRouteSnapshot</h2>
<p>The <code>ActivatedRoute.snapshot</code> contains the data from the given moment. This data is read-only and does not change. All the properties are exposed as plain values. That means, if you keep the reference to the snapshot, and then navigate to the same URL with different parameters without re-rendering the component, the values won't change. You'll have to get the newest snapshot from the <code>ActivatedRoute</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Component, inject, OnInit } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { ActivatedRoute } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/router'</span>;

<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-snapshot-data'</span>,
  template: <span class="hljs-string">`
    &lt;div&gt;{{ activatedRoute.snapshot.params | formatParams }}&lt;/div&gt;
    &lt;div&gt;{{ activatedRoute.snapshot.queryParams | formatParams }}&lt;/div&gt;
    &lt;div&gt;{{ activatedRoute.snapshot.data | formatParams }}&lt;/div&gt;
  `</span>
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> SnapshotDataComponent <span class="hljs-keyword">implements</span> OnInit {
  <span class="hljs-keyword">readonly</span> activatedRoute = inject(ActivatedRoute);

  ngOnInit(): <span class="hljs-built_in">void</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-built_in">this</span>.activatedRoute.snapshot.params);
    <span class="hljs-built_in">console</span>.log(<span class="hljs-built_in">this</span>.activatedRoute.snapshot.queryParams);
    <span class="hljs-built_in">console</span>.log(<span class="hljs-built_in">this</span>.activatedRoute.snapshot.data);
  }
}
</code></pre>
<h2 id="heading-observable-properties">Observable properties</h2>
<p>The <code>ActivatedRoute</code> has also 3 observable properties: <code>params</code>, <code>queryParams</code> and <code>data</code>. These properties work as <code>BehaviourSubject</code> so the actual values are immediately emitted upon subscription. The most important fact is that they emit values whenever associated route parameters change.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Component, inject, OnInit } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { ActivatedRoute, Params } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/router'</span>;

<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-observable-data'</span>,
  template: <span class="hljs-string">`
    &lt;div&gt;{{ activatedRoute.params | async | formatParams }}&lt;/div&gt;
    &lt;div&gt;{{ activatedRoute.queryParams | async | formatParams }}&lt;/div&gt;
    &lt;div&gt;{{ activatedRoute.data | async | formatParams }}&lt;/div&gt;
  `</span>
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> ObservableDataComponent <span class="hljs-keyword">implements</span> OnInit {
  <span class="hljs-keyword">readonly</span> activatedRoute = inject(ActivatedRoute);

  ngOnInit(): <span class="hljs-built_in">void</span> {
    <span class="hljs-built_in">this</span>.activatedRoute.params.subscribe(<span class="hljs-function">(<span class="hljs-params">params: Params</span>) =&gt;</span> <span class="hljs-built_in">console</span>.log(params));
    <span class="hljs-built_in">this</span>.activatedRoute.queryParams.subscribe(<span class="hljs-function">(<span class="hljs-params">queryParams: Params</span>) =&gt;</span> <span class="hljs-built_in">console</span>.log(queryParams));
    <span class="hljs-built_in">this</span>.activatedRoute.data.subscribe(<span class="hljs-function">(<span class="hljs-params">data: Params</span>) =&gt;</span> <span class="hljs-built_in">console</span>.log(data));
  }
}
</code></pre>
<p>To access the data in the view I'm using the <code>async</code> pipe. In the component it's a basic subscription. Note, that I'm not unsubscribing from the <code>ActivatedRoute</code> observables. This is one of the exceptions, where it's not needed because Angular takes care of it.</p>
<h2 id="heading-testing-the-activatedroute">Testing the ActivatedRoute</h2>
<p>Before testing let's take a look at the <code>TestComponent</code>. It's displaying:</p>
<ul>
<li><p>observable params, query params, data</p>
</li>
<li><p>current snapshot params, query params, data</p>
</li>
<li><p>initial snapshot params, query params, data</p>
</li>
</ul>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Component, inject } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { CommonModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/common'</span>;
<span class="hljs-keyword">import</span> { ActivatedRoute } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/router'</span>;
<span class="hljs-keyword">import</span> { FormatParamsPipeModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'../format-params/format-params-pipe.module'</span>;

<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-test'</span>,
  template: <span class="hljs-string">`
    &lt;div data-test='observable-params'&gt;{{ activatedRoute.params | async | formatParams }}&lt;/div&gt;
    &lt;div data-test='observable-query-params'&gt;{{ activatedRoute.queryParams | async | formatParams }}&lt;/div&gt;
    &lt;div data-test='observable-data'&gt;{{ activatedRoute.data | async | formatParams }}&lt;/div&gt;

    &lt;div data-test='snapshot-params'&gt;{{ activatedRoute.snapshot?.params | formatParams }}&lt;/div&gt;
    &lt;div data-test='snapshot-query-params'&gt;{{ activatedRoute.snapshot?.queryParams | formatParams }}&lt;/div&gt;
    &lt;div data-test='snapshot-data'&gt;{{ activatedRoute.snapshot?.data | formatParams }}&lt;/div&gt;

    &lt;div data-test='initial-snapshot-params'&gt;{{ initialReference?.params | formatParams }}&lt;/div&gt;
    &lt;div data-test='initial-snapshot-query-params'&gt;{{ initialReference?.queryParams | formatParams }}&lt;/div&gt;
    &lt;div data-test='initial-snapshot-data'&gt;{{ initialReference?.data | formatParams }}&lt;/div&gt;
  `</span>,
  standalone: <span class="hljs-literal">true</span>,
  imports: [CommonModule, FormatParamsPipeModule],
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> TestComponent {
  <span class="hljs-keyword">readonly</span> activatedRoute = inject(ActivatedRoute);
  <span class="hljs-keyword">readonly</span> initialReference = <span class="hljs-built_in">this</span>.activatedRoute.snapshot;
}
</code></pre>
<p>When declaring the routes I'm using <code>paramsInheritanceStrategy: 'always'</code> to make sure that the child components can access all the parents' parameters.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Router } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/router'</span>;
<span class="hljs-keyword">import</span> { ComponentFixture, TestBed } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core/testing'</span>;
<span class="hljs-keyword">import</span> { TestComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">'./modules/test/test.component'</span>;
<span class="hljs-keyword">import</span> { RouterTestingModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/router/testing'</span>;
<span class="hljs-keyword">import</span> { By } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/platform-browser'</span>;
<span class="hljs-keyword">import</span> { Component } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;


describe(<span class="hljs-string">'ActivatedRouteTest'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">let</span> router: Router;
  <span class="hljs-keyword">let</span> fixture: ComponentFixture&lt;RouterOutletComponent&gt;;

  beforeEach(<span class="hljs-keyword">async</span> () =&gt; {
    TestBed.configureTestingModule({
      declarations: [RouterOutletComponent],
      imports: [
        TestComponent,
        RouterTestingModule.withRoutes([
          {
            path: <span class="hljs-string">''</span>,
            component: RouterOutletComponent,
            children: [
              {
                path: <span class="hljs-string">`products`</span>,
                children: [
                  {
                    path: <span class="hljs-string">':productId'</span>,
                    component: TestComponent,
                    data: {
                      secret: <span class="hljs-string">'product-details'</span>,
                    },
                  },
                  {
                    path: <span class="hljs-string">''</span>,
                    data: {
                      secret: <span class="hljs-string">'product-list'</span>,
                    },
                    component: TestComponent,
                  },
                ]
              },
            ]
          }
        ], {paramsInheritanceStrategy: <span class="hljs-string">'always'</span>}),
      ],
    });

    fixture = TestBed.createComponent(RouterOutletComponent);
    router = TestBed.inject(Router);
    router.initialNavigation();
  });

  describe(<span class="hljs-string">'when user enters /products url'</span>, <span class="hljs-function">() =&gt;</span> {
    beforeEach(<span class="hljs-keyword">async</span> () =&gt; {
      <span class="hljs-keyword">await</span> router.navigateByUrl(<span class="hljs-string">'/products'</span>);
      fixture.detectChanges();
      <span class="hljs-keyword">await</span> fixture.whenStable();
    });

    it(<span class="hljs-string">'should display route properties'</span>, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-keyword">const</span> expected: SnapshotDump = {
        params: <span class="hljs-string">''</span>,
        queryParams: <span class="hljs-string">''</span>,
        data: <span class="hljs-string">'secret:product-list'</span>,
        snapshotParams: <span class="hljs-string">''</span>,
        snapshotQueryParams: <span class="hljs-string">''</span>,
        snapshotData: <span class="hljs-string">'secret:product-list'</span>,
        initialSnapshotParams: <span class="hljs-string">''</span>,
        initialSnapshotQueryParams: <span class="hljs-string">''</span>,
        initialSnapshotData: <span class="hljs-string">'secret:product-list'</span>,
      };
      expect(getSnapshotDump()).toEqual(expected);
    });

    describe(<span class="hljs-string">'and user navigates to /products/123 url'</span>, <span class="hljs-function">() =&gt;</span> {
      beforeEach(<span class="hljs-keyword">async</span> () =&gt; {
        <span class="hljs-keyword">await</span> router.navigateByUrl(<span class="hljs-string">'/products/123'</span>);
        fixture.detectChanges();
      });

      it(<span class="hljs-string">'should display route properties'</span>, <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">const</span> expected: SnapshotDump = {
          params: <span class="hljs-string">'productId:123'</span>,
          queryParams: <span class="hljs-string">''</span>,
          data: <span class="hljs-string">'secret:product-details'</span>,
          snapshotParams: <span class="hljs-string">'productId:123'</span>,
          snapshotQueryParams: <span class="hljs-string">''</span>,
          snapshotData: <span class="hljs-string">'secret:product-details'</span>,
          initialSnapshotParams: <span class="hljs-string">'productId:123'</span>,
          initialSnapshotQueryParams: <span class="hljs-string">''</span>,
          initialSnapshotData: <span class="hljs-string">'secret:product-details'</span>,
        };
        expect(getSnapshotDump()).toEqual(expected);
      });

      describe(<span class="hljs-string">'and user navigates to /products/998 url'</span>, <span class="hljs-function">() =&gt;</span> {
        beforeEach(<span class="hljs-keyword">async</span> () =&gt; {
          <span class="hljs-keyword">await</span> router.navigateByUrl(<span class="hljs-string">'/products/998'</span>);
          fixture.detectChanges();
        });

        it(<span class="hljs-string">'should display route properties'</span>, <span class="hljs-function">() =&gt;</span> {
          <span class="hljs-keyword">const</span> expected: SnapshotDump = {
            params: <span class="hljs-string">'productId:998'</span>,
            queryParams: <span class="hljs-string">''</span>,
            data: <span class="hljs-string">'secret:product-details'</span>,
            snapshotParams: <span class="hljs-string">'productId:998'</span>,
            snapshotQueryParams: <span class="hljs-string">''</span>,
            snapshotData: <span class="hljs-string">'secret:product-details'</span>,
            initialSnapshotParams: <span class="hljs-string">'productId:123'</span>,
            initialSnapshotQueryParams: <span class="hljs-string">''</span>,
            initialSnapshotData: <span class="hljs-string">'secret:product-details'</span>,
          };
          expect(getSnapshotDump()).toEqual(expected);
        });

        describe(<span class="hljs-string">'and user navigates to /products/998?param1=val1 url'</span>, <span class="hljs-function">() =&gt;</span> {
          beforeEach(<span class="hljs-keyword">async</span> () =&gt; {
            <span class="hljs-keyword">await</span> router.navigateByUrl(<span class="hljs-string">'/products/998?param1=val1'</span>);
            fixture.detectChanges();
          });

          it(<span class="hljs-string">'should display route properties'</span>, <span class="hljs-function">() =&gt;</span> {
            <span class="hljs-keyword">const</span> expected: SnapshotDump = {
              params: <span class="hljs-string">'productId:998'</span>,
              queryParams: <span class="hljs-string">'param1:val1'</span>,
              data: <span class="hljs-string">'secret:product-details'</span>,
              snapshotParams: <span class="hljs-string">'productId:998'</span>,
              snapshotQueryParams: <span class="hljs-string">'param1:val1'</span>,
              snapshotData: <span class="hljs-string">'secret:product-details'</span>,
              initialSnapshotParams: <span class="hljs-string">'productId:123'</span>,
              initialSnapshotQueryParams: <span class="hljs-string">''</span>,
              initialSnapshotData: <span class="hljs-string">'secret:product-details'</span>,
            };
            expect(getSnapshotDump()).toEqual(expected);
          });

          describe(<span class="hljs-string">'and user navigates to /products/998?param2=val2 url'</span>, <span class="hljs-function">() =&gt;</span> {
            beforeEach(<span class="hljs-keyword">async</span> () =&gt; {
              <span class="hljs-keyword">await</span> router.navigateByUrl(<span class="hljs-string">'/products/998?param2=val2'</span>);
              fixture.detectChanges();
            });

            it(<span class="hljs-string">'should display route properties'</span>, <span class="hljs-function">() =&gt;</span> {
              <span class="hljs-keyword">const</span> expected: SnapshotDump = {
                params: <span class="hljs-string">'productId:998'</span>,
                queryParams: <span class="hljs-string">'param2:val2'</span>,
                data: <span class="hljs-string">'secret:product-details'</span>,
                snapshotParams: <span class="hljs-string">'productId:998'</span>,
                snapshotQueryParams: <span class="hljs-string">'param2:val2'</span>,
                snapshotData: <span class="hljs-string">'secret:product-details'</span>,
                initialSnapshotParams: <span class="hljs-string">'productId:123'</span>,
                initialSnapshotQueryParams: <span class="hljs-string">''</span>,
                initialSnapshotData: <span class="hljs-string">'secret:product-details'</span>,
              };
              expect(getSnapshotDump()).toEqual(expected);
            });
          });
        });
      });
    });
  });

  <span class="hljs-keyword">const</span> getSnapshotDump = (): <span class="hljs-function"><span class="hljs-params">SnapshotDump</span> =&gt;</span> {
    <span class="hljs-keyword">return</span> {
      params: fixture.debugElement.query(By.css(<span class="hljs-string">'[data-test="observable-params"]'</span>)).nativeElement.textContent,
      queryParams: fixture.debugElement.query(By.css(<span class="hljs-string">'[data-test="observable-query-params"]'</span>)).nativeElement.textContent,
      data: fixture.debugElement.query(By.css(<span class="hljs-string">'[data-test="observable-data"]'</span>)).nativeElement.textContent,
      snapshotParams: fixture.debugElement.query(By.css(<span class="hljs-string">'[data-test="snapshot-params"]'</span>)).nativeElement.textContent,
      snapshotQueryParams: fixture.debugElement.query(By.css(<span class="hljs-string">'[data-test="snapshot-query-params"]'</span>)).nativeElement.textContent,
      snapshotData: fixture.debugElement.query(By.css(<span class="hljs-string">'[data-test="snapshot-data"]'</span>)).nativeElement.textContent,
      initialSnapshotParams: fixture.debugElement.query(By.css(<span class="hljs-string">'[data-test="initial-snapshot-params"]'</span>)).nativeElement.textContent,
      initialSnapshotQueryParams: fixture.debugElement.query(By.css(<span class="hljs-string">'[data-test="initial-snapshot-query-params"]'</span>)).nativeElement.textContent,
      initialSnapshotData: fixture.debugElement.query(By.css(<span class="hljs-string">'[data-test="initial-snapshot-data"]'</span>)).nativeElement.textContent,
    }
  }
});


<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'router-outlet-dummy'</span>,
  template: <span class="hljs-string">'&lt;router-outlet&gt;&lt;/router-outlet&gt;'</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> RouterOutletComponent {
}

<span class="hljs-keyword">interface</span> SnapshotDump {
  params: <span class="hljs-built_in">string</span>;
  queryParams: <span class="hljs-built_in">string</span>;
  data: <span class="hljs-built_in">string</span>;
  snapshotParams: <span class="hljs-built_in">string</span>;
  snapshotQueryParams: <span class="hljs-built_in">string</span>;
  snapshotData: <span class="hljs-built_in">string</span>;
  initialSnapshotParams: <span class="hljs-built_in">string</span>;
  initialSnapshotQueryParams: <span class="hljs-built_in">string</span>;
  initialSnapshotData: <span class="hljs-built_in">string</span>;
}
</code></pre>
<p>In this test, I'm navigating between routes and validating the parameters. The test confirms that the params and snapshot params are up-to-date when accessing the activated route. Also confirmed, the initial snapshot parameters don't change upon navigation.</p>
<h1 id="heading-source-code">Source code</h1>
<p>https://gitlab.com/barcioch-blog-examples/013-angular-extracting-parameters-from-activated-route</p>
]]></content:encoded></item><item><title><![CDATA[Angular Observable Unsubscription: Preventing Memory Leaks in Your Application]]></title><description><![CDATA[A memory leak is a common problem unless dealt properly with the subscriptions. Whenever we subscribe to the observable a subscription is created. If we don't unsubscribe, then the subscription will stay in the memory and run whenever a new value is ...]]></description><link>https://blog.procode.pl/angular-observable-unsubscription-preventing-memory-leaks-in-your-application</link><guid isPermaLink="true">https://blog.procode.pl/angular-observable-unsubscription-preventing-memory-leaks-in-your-application</guid><category><![CDATA[Angular]]></category><category><![CDATA[observables]]></category><category><![CDATA[Memory Leak]]></category><category><![CDATA[subscriptions]]></category><dc:creator><![CDATA[Bartosz Szłapak]]></dc:creator><pubDate>Mon, 02 Oct 2023 14:56:14 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1696255120743/3620aaca-4a3b-4049-a009-3c5183f238ad.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A memory leak is a common problem unless dealt properly with the subscriptions. Whenever we subscribe to the observable a subscription is created. If we don't unsubscribe, then the subscription will stay in the memory and run whenever a new value is received. This can be a serious problem if we no longer need it or can reference it. That's why managing them is crucial for the stability and performance of the application. Since most of this article is related to Angular, all the examples will be also based on it.</p>
<p>Also, please note that I'll be dealing with cold observables only. Cold observables start emitting values only when subscribed to.</p>
<h1 id="heading-setup">Setup</h1>
<p>If unsure how to set up Angular with Jest please refer to the article: https://barcioch.pro/angular-with-jest-setup. Also, install the <code>@ngneat/until-destroy</code> library <code>npm install @ngneat/until-destroy</code></p>
<h1 id="heading-managing-the-subscriptions">Managing the subscriptions</h1>
<h2 id="heading-unsubscribe-method">unsubscribe method</h2>
<p>The most basic way to unsubscribe is to keep track of the subscriptions and then unsubscribe when needed. Take a look at the following component.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Component, inject, OnDestroy, OnInit } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { interval, Subscription, tap } <span class="hljs-keyword">from</span> <span class="hljs-string">'rxjs'</span>;
<span class="hljs-keyword">import</span> { LoggerService } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../services/logger.service'</span>;

<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-unsubscribe-example'</span>,
  template: <span class="hljs-string">''</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> UnsubscribeExampleComponent <span class="hljs-keyword">implements</span> OnInit, OnDestroy {
  <span class="hljs-keyword">readonly</span> interval = <span class="hljs-number">1000</span>;
  <span class="hljs-keyword">private</span> subscription: Subscription | <span class="hljs-literal">undefined</span>;
  <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> logger = inject(LoggerService);

  ngOnDestroy(): <span class="hljs-built_in">void</span> {
    <span class="hljs-built_in">this</span>.subscription?.unsubscribe();
  }

  ngOnInit(): <span class="hljs-built_in">void</span> {
    <span class="hljs-built_in">this</span>.subscription = interval(<span class="hljs-built_in">this</span>.interval).pipe(tap(<span class="hljs-function"><span class="hljs-params">value</span> =&gt;</span> {
      <span class="hljs-built_in">this</span>.logger.log(<span class="hljs-string">`value:<span class="hljs-subst">${ value }</span>`</span>);
    })).subscribe();
  }
}
</code></pre>
<ul>
<li><p>when the component is initialized (<code>ngOnInit</code>)</p>
<ul>
<li><p>inject <code>LoggerService</code> - does nothing, just used for testing purposes</p>
</li>
<li><p>subscribe to RxJS <code>interval</code> that emits values every 1 second</p>
</li>
<li><p>call the <code>tap</code> operator and call <code>LoggerService.log</code> method</p>
</li>
<li><p>store the subscription reference in <code>this.subscription</code> property</p>
</li>
</ul>
</li>
<li><p>when the component is destroyed (<code>ngOnDestroy</code>)</p>
<ul>
<li>unsubscribe by calling <code>this.subscription?.unsubscribe()</code></li>
</ul>
</li>
</ul>
<p>The assumptions are</p>
<ul>
<li><p>values should be emitted after component initialization by an interval of 1000 ms</p>
</li>
<li><p>after the component is destroyed no more values should be emitted</p>
</li>
</ul>
<h3 id="heading-test-it">Test it</h3>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { ComponentFixture, fakeAsync, TestBed, tick } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core/testing'</span>;
<span class="hljs-keyword">import</span> { UnsubscribeExampleComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">'./unsubscribe-example.component'</span>;
<span class="hljs-keyword">import</span> { LoggerService } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../services/logger.service'</span>;


describe(<span class="hljs-string">'UnsubscribeExampleComponent'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">let</span> fixture: ComponentFixture&lt;UnsubscribeExampleComponent&gt;;
  <span class="hljs-keyword">let</span> logger: LoggerService;

  beforeEach(<span class="hljs-function">() =&gt;</span> {
    TestBed.configureTestingModule({
      declarations: [UnsubscribeExampleComponent]
    })

    logger = TestBed.inject(LoggerService);
    jest.spyOn(logger, <span class="hljs-string">'log'</span>);
    fixture = TestBed.createComponent(UnsubscribeExampleComponent);
  });

  describe(<span class="hljs-string">'when component is initialized'</span>, <span class="hljs-function">() =&gt;</span> {
    it(<span class="hljs-string">'should not call logger'</span>, <span class="hljs-function">() =&gt;</span> {
      fixture.detectChanges()
      expect(logger.log).toBeCalledTimes(<span class="hljs-number">0</span>);
    });

    describe(<span class="hljs-string">'and time passes by 1000ms and 5000ms after component destruction'</span>, <span class="hljs-function">() =&gt;</span> {
      it(<span class="hljs-string">'should call logger'</span>, fakeAsync(<span class="hljs-function">() =&gt;</span> {
        fixture.detectChanges();
        tick(fixture.componentInstance.interval);
        fixture.destroy();
        tick(fixture.componentInstance.interval * <span class="hljs-number">5</span>);
        expect(logger.log).toBeCalledTimes(<span class="hljs-number">1</span>);
        expect(logger.log).toHaveBeenNthCalledWith(<span class="hljs-number">1</span>, <span class="hljs-string">'value:0'</span>);
      }));
    });

    describe(<span class="hljs-string">`and time passes by 3000ms and 5000ms after component destruction`</span>, <span class="hljs-function">() =&gt;</span> {
      it(<span class="hljs-string">'should call logger'</span>, fakeAsync(<span class="hljs-function">() =&gt;</span> {
        fixture.detectChanges();
        tick(fixture.componentInstance.interval * <span class="hljs-number">3</span>);
        fixture.destroy();
        tick(fixture.componentInstance.interval * <span class="hljs-number">5</span>);
        expect(logger.log).toBeCalledTimes(<span class="hljs-number">3</span>);
        expect(logger.log).toHaveBeenNthCalledWith(<span class="hljs-number">1</span>, <span class="hljs-string">'value:0'</span>);
        expect(logger.log).toHaveBeenNthCalledWith(<span class="hljs-number">2</span>, <span class="hljs-string">'value:1'</span>);
        expect(logger.log).toHaveBeenNthCalledWith(<span class="hljs-number">3</span>, <span class="hljs-string">'value:2'</span>);
      }));
    });
  });
});
</code></pre>
<p>Parts of these tests require a bit of explanation.</p>
<p>To test intervals within Angular you have to</p>
<ul>
<li><p>write all tests within <code>fakeAsync</code> zone (because all the <code>periodic timers</code> have to be started and canceled within it)</p>
</li>
<li><p>have some mechanism for canceling timers (or you'll get <code># periodic timer(s) still in the queue</code> error) - in the current example I'm using <code>ngOnDestroy</code> to unsubscribe</p>
</li>
</ul>
<p>The first calls to <code>fixture.detectChanges()</code> are inside <code>it</code> body. This is due to the <code>interval</code> starting within <code>ngOnInit</code>. You have to run change detection to run the lifecycle hooks.</p>
<p>Also, I'm calling <code>tick(fixture.componentInstance.interval * 5)</code> after <code>fixture.destroy()</code> to pass the time by 5 seconds after the component is destroyed. Only then I run the expectations to check the <code>Logger.log</code> method calls. This way I can validate that no value is emitted after the subscription was canceled.</p>
<h2 id="heading-rxjs-operators">RxJS operators</h2>
<p>These operators are responsible for completing the observables but work differently. One of their advantages is that we no longer have to keep the <code>subscription</code> reference and manually call <code>unsubscribe</code>.</p>
<h3 id="heading-first">first</h3>
<p>This operator without any argument emits the very first value and completes the observable.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Component, inject, OnInit } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { first, interval, tap } <span class="hljs-keyword">from</span> <span class="hljs-string">'rxjs'</span>;
<span class="hljs-keyword">import</span> { LoggerService } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../services/logger.service'</span>;

<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-first-operator-example'</span>,
  template: <span class="hljs-string">''</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> FirstOperatorExampleComponent <span class="hljs-keyword">implements</span> OnInit {
  <span class="hljs-keyword">readonly</span> interval = <span class="hljs-number">1000</span>;
  <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> logger = inject(LoggerService);

  ngOnInit(): <span class="hljs-built_in">void</span> {
    interval(<span class="hljs-built_in">this</span>.interval).pipe(
      first(),
      tap(<span class="hljs-function"><span class="hljs-params">value</span> =&gt;</span> {
        <span class="hljs-built_in">this</span>.logger.log(<span class="hljs-string">`value:<span class="hljs-subst">${ value }</span>`</span>);
      })).subscribe();
  }
}
</code></pre>
<h4 id="heading-test-it-1">Test it</h4>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { ComponentFixture, fakeAsync, TestBed, tick } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core/testing'</span>;
<span class="hljs-keyword">import</span> { FirstOperatorExampleComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">'./first-operator-example.component'</span>;
<span class="hljs-keyword">import</span> { LoggerService } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../services/logger.service'</span>;


describe(<span class="hljs-string">'FirstOperatorExampleComponent'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">let</span> fixture: ComponentFixture&lt;FirstOperatorExampleComponent&gt;;
  <span class="hljs-keyword">let</span> logger: LoggerService;

  beforeEach(<span class="hljs-function">() =&gt;</span> {
    TestBed.configureTestingModule({
      declarations: [FirstOperatorExampleComponent]
    })

    logger = TestBed.inject(LoggerService);
    jest.spyOn(logger, <span class="hljs-string">'log'</span>);
    fixture = TestBed.createComponent(FirstOperatorExampleComponent);
  });

  describe(<span class="hljs-string">'when component is initialized'</span>, <span class="hljs-function">() =&gt;</span> {
    it(<span class="hljs-string">'should not call logger'</span>, <span class="hljs-function">() =&gt;</span> {
      fixture.detectChanges()
      expect(logger.log).toBeCalledTimes(<span class="hljs-number">0</span>);
    });

    describe(<span class="hljs-string">'and time passes by 1000ms and 5000ms after component destruction'</span>, <span class="hljs-function">() =&gt;</span> {
      it(<span class="hljs-string">'should call logger'</span>, fakeAsync(<span class="hljs-function">() =&gt;</span> {
        fixture.detectChanges();
        tick(fixture.componentInstance.interval);
        fixture.destroy();
        tick(fixture.componentInstance.interval * <span class="hljs-number">5</span>);
        expect(logger.log).toBeCalledTimes(<span class="hljs-number">1</span>);
        expect(logger.log).toHaveBeenNthCalledWith(<span class="hljs-number">1</span>, <span class="hljs-string">'value:0'</span>);
      }));
    });

    describe(<span class="hljs-string">`and time passes by 3000ms and 5000ms after component destruction`</span>, <span class="hljs-function">() =&gt;</span> {
      it(<span class="hljs-string">'should call logger'</span>, fakeAsync(<span class="hljs-function">() =&gt;</span> {
        fixture.detectChanges();
        tick(fixture.componentInstance.interval * <span class="hljs-number">3</span>);
        fixture.destroy();
        tick(fixture.componentInstance.interval * <span class="hljs-number">5</span>);
        expect(logger.log).toBeCalledTimes(<span class="hljs-number">1</span>);
        expect(logger.log).toHaveBeenNthCalledWith(<span class="hljs-number">1</span>, <span class="hljs-string">'value:0'</span>);
      }));
    });
  });
});
</code></pre>
<p>The test is almost identical to the previous one. Just changed the expected number of calls.</p>
<h3 id="heading-firstpredicate">first(predicate)</h3>
<p>The first argument is a <code>predicate</code>. The first time it evaluates to <code>true</code> the operator emits the value and completes the observable.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Component, inject, OnInit } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { first, <span class="hljs-keyword">from</span>, tap } <span class="hljs-keyword">from</span> <span class="hljs-string">'rxjs'</span>;
<span class="hljs-keyword">import</span> { LoggerService } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../services/logger.service'</span>;

<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-first-operator-with-predicate-example'</span>,
  template: <span class="hljs-string">''</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> FirstOperatorWithPredicateExampleComponent <span class="hljs-keyword">implements</span> OnInit {
  <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> logger = inject(LoggerService);

  ngOnInit(): <span class="hljs-built_in">void</span> {
    <span class="hljs-keyword">const</span> source = [<span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>, <span class="hljs-number">6</span>, <span class="hljs-number">7</span>, <span class="hljs-number">8</span>];
    <span class="hljs-keyword">from</span>(source).pipe(
      first(<span class="hljs-function">(<span class="hljs-params">value: <span class="hljs-built_in">number</span></span>) =&gt;</span> value &gt;= <span class="hljs-number">4</span>),
      tap(<span class="hljs-function"><span class="hljs-params">value</span> =&gt;</span> {
        <span class="hljs-built_in">this</span>.logger.log(<span class="hljs-string">`value:<span class="hljs-subst">${ value }</span>`</span>);
      })).subscribe();
  }
}
</code></pre>
<p>The <code>from</code> operator takes the source array of 9 numbers and emits them. The first value that is greater or equal to <code>4</code> is logged and the observable completes.</p>
<h4 id="heading-test-it-2">Test it</h4>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { ComponentFixture, TestBed } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core/testing'</span>;
<span class="hljs-keyword">import</span> { FirstOperatorWithPredicateExampleComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">'./first-operator-with-predicate-example.component'</span>;
<span class="hljs-keyword">import</span> { LoggerService } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../services/logger.service'</span>;


describe(<span class="hljs-string">'FirstOperatorWithPredicateExampleComponent'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">let</span> fixture: ComponentFixture&lt;FirstOperatorWithPredicateExampleComponent&gt;;
  <span class="hljs-keyword">let</span> logger: LoggerService;

  beforeEach(<span class="hljs-function">() =&gt;</span> {
    TestBed.configureTestingModule({
      declarations: [FirstOperatorWithPredicateExampleComponent]
    })

    logger = TestBed.inject(LoggerService);
    jest.spyOn(logger, <span class="hljs-string">'log'</span>);
    fixture = TestBed.createComponent(FirstOperatorWithPredicateExampleComponent);
  });

  describe(<span class="hljs-string">'when component is initialized'</span>, <span class="hljs-function">() =&gt;</span> {
    it(<span class="hljs-string">'should call logger'</span>, <span class="hljs-function">() =&gt;</span> {
      fixture.detectChanges();
      fixture.destroy();
      expect(logger.log).toBeCalledTimes(<span class="hljs-number">1</span>);
      expect(logger.log).toHaveBeenNthCalledWith(<span class="hljs-number">1</span>, <span class="hljs-string">'value:4'</span>);
    });
  });
});
</code></pre>
<p>This test creates, detects changes and destroys the component. After that, the expectations verify the logged value.</p>
<h3 id="heading-take">take</h3>
<p>This operator required exactly one argument - the number of values to emit before the observable is closed.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Component, inject, OnInit } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { first, <span class="hljs-keyword">from</span>, take, tap } <span class="hljs-keyword">from</span> <span class="hljs-string">'rxjs'</span>;
<span class="hljs-keyword">import</span> { LoggerService } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../services/logger.service'</span>;

<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-take-operator-example'</span>,
  template: <span class="hljs-string">''</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> TakeOperatorExampleComponent <span class="hljs-keyword">implements</span> OnInit {
  <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> logger = inject(LoggerService);

  ngOnInit(): <span class="hljs-built_in">void</span> {
    <span class="hljs-keyword">const</span> source = [<span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>, <span class="hljs-number">6</span>, <span class="hljs-number">7</span>, <span class="hljs-number">8</span>];
    <span class="hljs-keyword">from</span>(source).pipe(
      take(<span class="hljs-number">3</span>),
      tap(<span class="hljs-function"><span class="hljs-params">value</span> =&gt;</span> {
        <span class="hljs-built_in">this</span>.logger.log(<span class="hljs-string">`value:<span class="hljs-subst">${ value }</span>`</span>);
      })).subscribe();
  }
}
</code></pre>
<p>The <code>from</code> operator takes the source array of 9 numbers and emits them. The first 3 values (0, 1, 2) are logged and the observable completes.</p>
<h4 id="heading-test-it-3">Test it</h4>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { ComponentFixture, TestBed } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core/testing'</span>;
<span class="hljs-keyword">import</span> { TakeOperatorExampleComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">'./take-operator-example.component'</span>;
<span class="hljs-keyword">import</span> { LoggerService } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../services/logger.service'</span>;


describe(<span class="hljs-string">'TakeOperatorExampleComponent'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">let</span> fixture: ComponentFixture&lt;TakeOperatorExampleComponent&gt;;
  <span class="hljs-keyword">let</span> logger: LoggerService;

  beforeEach(<span class="hljs-function">() =&gt;</span> {
    TestBed.configureTestingModule({
      declarations: [TakeOperatorExampleComponent]
    })

    logger = TestBed.inject(LoggerService);
    jest.spyOn(logger, <span class="hljs-string">'log'</span>);
    fixture = TestBed.createComponent(TakeOperatorExampleComponent);
  });

  describe(<span class="hljs-string">'when component is initialized'</span>, <span class="hljs-function">() =&gt;</span> {
    it(<span class="hljs-string">'should call logger'</span>, <span class="hljs-function">() =&gt;</span> {
      fixture.detectChanges();
      fixture.destroy();
      expect(logger.log).toBeCalledTimes(<span class="hljs-number">3</span>);
      expect(logger.log).toHaveBeenNthCalledWith(<span class="hljs-number">1</span>, <span class="hljs-string">'value:0'</span>);
      expect(logger.log).toHaveBeenNthCalledWith(<span class="hljs-number">2</span>, <span class="hljs-string">'value:1'</span>);
      expect(logger.log).toHaveBeenNthCalledWith(<span class="hljs-number">3</span>, <span class="hljs-string">'value:2'</span>);
    });
  });
});
</code></pre>
<p>This test creates, detects changes and destroys the component. After that, the expectations verify the logged values.</p>
<h3 id="heading-takeuntil">takeUntil</h3>
<p>This operator requires exactly one argument to work - the notifier which is an observable. The operator will be emitting values until the notifier emits <code>truthy</code> value. When it happens, the observable completes.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Component, inject, OnDestroy, OnInit } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { interval, Subject, takeUntil, tap } <span class="hljs-keyword">from</span> <span class="hljs-string">'rxjs'</span>;
<span class="hljs-keyword">import</span> { LoggerService } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../services/logger.service'</span>;

<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-take-until-operator-example'</span>,
  template: <span class="hljs-string">''</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> TakeUntilOperatorExampleComponent <span class="hljs-keyword">implements</span> OnInit, OnDestroy {
  <span class="hljs-keyword">readonly</span> interval = <span class="hljs-number">1000</span>;
  <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> logger = inject(LoggerService);
  <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> unsubscribe$ = <span class="hljs-keyword">new</span> Subject&lt;<span class="hljs-built_in">void</span>&gt;();

  ngOnInit(): <span class="hljs-built_in">void</span> {
    interval(<span class="hljs-built_in">this</span>.interval).pipe(
      tap(<span class="hljs-function"><span class="hljs-params">value</span> =&gt;</span> {
        <span class="hljs-built_in">this</span>.logger.log(<span class="hljs-string">`value:<span class="hljs-subst">${ value }</span>`</span>);
      }),
      takeUntil(<span class="hljs-built_in">this</span>.unsubscribe$)
    ).subscribe();
  }

  ngOnDestroy(): <span class="hljs-built_in">void</span> {
    <span class="hljs-built_in">this</span>.unsubscribe$.next();
    <span class="hljs-built_in">this</span>.unsubscribe$.complete();
  }
}
</code></pre>
<p>In this example, I created a notifier <code>private readonly unsubscribe$ = new Subject&lt;void&gt;()</code>. The component implements the <code>OnDestroy</code> interface and, within <code>ngOnDestroy</code> method, the notifier emits a value <code>this.unsubscribe$.next()</code> and completes <code>this.unsubscribe$.complete()</code>.</p>
<p>The main observable contains <code>takeUntil(this.unsubscribe$)</code> operator that takes the notifier. It's a good practice to place this operator on the last position of the operator list.</p>
<h4 id="heading-test-it-4">Test it</h4>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { ComponentFixture, fakeAsync, TestBed, tick } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core/testing'</span>;
<span class="hljs-keyword">import</span> { TakeUntilOperatorExampleComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">'./take-until-operator-example.component'</span>;
<span class="hljs-keyword">import</span> { LoggerService } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../services/logger.service'</span>;


describe(<span class="hljs-string">'TakeUntilOperatorExampleComponent'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">let</span> fixture: ComponentFixture&lt;TakeUntilOperatorExampleComponent&gt;;
  <span class="hljs-keyword">let</span> logger: LoggerService;

  beforeEach(<span class="hljs-function">() =&gt;</span> {
    TestBed.configureTestingModule({
      declarations: [TakeUntilOperatorExampleComponent]
    })

    logger = TestBed.inject(LoggerService);
    jest.spyOn(logger, <span class="hljs-string">'log'</span>);
    fixture = TestBed.createComponent(TakeUntilOperatorExampleComponent);
  });

  describe(<span class="hljs-string">'when component is initialized'</span>, <span class="hljs-function">() =&gt;</span> {
    it(<span class="hljs-string">'should not call logger'</span>, <span class="hljs-function">() =&gt;</span> {
      fixture.detectChanges()
      expect(logger.log).toBeCalledTimes(<span class="hljs-number">0</span>);
    });

    describe(<span class="hljs-string">'and time passes by 1000ms and 5000ms after component destruction'</span>, <span class="hljs-function">() =&gt;</span> {
      it(<span class="hljs-string">'should call logger'</span>, fakeAsync(<span class="hljs-function">() =&gt;</span> {
        fixture.detectChanges();
        tick(fixture.componentInstance.interval);
        fixture.destroy();
        tick(fixture.componentInstance.interval * <span class="hljs-number">5</span>);
        expect(logger.log).toBeCalledTimes(<span class="hljs-number">1</span>);
        expect(logger.log).toHaveBeenNthCalledWith(<span class="hljs-number">1</span>, <span class="hljs-string">'value:0'</span>);
      }));
    });

    describe(<span class="hljs-string">`and time passes by 3000ms and 5000ms after component destruction`</span>, <span class="hljs-function">() =&gt;</span> {
      it(<span class="hljs-string">'should call logger'</span>, fakeAsync(<span class="hljs-function">() =&gt;</span> {
        fixture.detectChanges();
        tick(fixture.componentInstance.interval * <span class="hljs-number">3</span>);
        fixture.destroy();
        tick(fixture.componentInstance.interval * <span class="hljs-number">5</span>);
        expect(logger.log).toBeCalledTimes(<span class="hljs-number">3</span>);
        expect(logger.log).toHaveBeenNthCalledWith(<span class="hljs-number">1</span>, <span class="hljs-string">'value:0'</span>);
        expect(logger.log).toHaveBeenNthCalledWith(<span class="hljs-number">2</span>, <span class="hljs-string">'value:1'</span>);
        expect(logger.log).toHaveBeenNthCalledWith(<span class="hljs-number">3</span>, <span class="hljs-string">'value:2'</span>);
      }));
    });
  });
});
</code></pre>
<p>As in previous examples, I'm verifying the calls to <code>LoggerService</code>. When the component gets destroyed there should be no more calls to the service.</p>
<h3 id="heading-takewhile">takeWhile</h3>
<p>This operator takes a <code>predicate</code> as an argument. As long as the <code>predicate</code> returns <code>truthy</code> value the operator emits the source value. Otherwise, it completes the observable.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Component, inject, OnInit } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { interval, takeWhile, tap } <span class="hljs-keyword">from</span> <span class="hljs-string">'rxjs'</span>;
<span class="hljs-keyword">import</span> { LoggerService } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../services/logger.service'</span>;

<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-take-while-operator-example'</span>,
  template: <span class="hljs-string">''</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> TakeWhileOperatorExampleComponent <span class="hljs-keyword">implements</span> OnInit {
  <span class="hljs-keyword">readonly</span> interval = <span class="hljs-number">1000</span>;
  <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> logger = inject(LoggerService);

  ngOnInit(): <span class="hljs-built_in">void</span> {
    interval(<span class="hljs-built_in">this</span>.interval).pipe(
      takeWhile(<span class="hljs-function"><span class="hljs-params">value</span> =&gt;</span> value &lt;= <span class="hljs-number">3</span>),
      tap(<span class="hljs-function"><span class="hljs-params">value</span> =&gt;</span> {
        <span class="hljs-built_in">this</span>.logger.log(<span class="hljs-string">`value:<span class="hljs-subst">${ value }</span>`</span>);
      }),
    ).subscribe();
  }
}
</code></pre>
<p>In this example, the values are emitted until the value is lesser or equal to 3.</p>
<h4 id="heading-test-it-5">Test it</h4>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { ComponentFixture, fakeAsync, TestBed, tick } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core/testing'</span>;
<span class="hljs-keyword">import</span> { TakeWhileOperatorExampleComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">'./take-while-operator-example.component'</span>;
<span class="hljs-keyword">import</span> { LoggerService } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../services/logger.service'</span>;


describe(<span class="hljs-string">'TakeWhileOperatorExampleComponent'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">let</span> fixture: ComponentFixture&lt;TakeWhileOperatorExampleComponent&gt;;
  <span class="hljs-keyword">let</span> logger: LoggerService;

  beforeEach(<span class="hljs-function">() =&gt;</span> {
    TestBed.configureTestingModule({
      declarations: [TakeWhileOperatorExampleComponent]
    })

    logger = TestBed.inject(LoggerService);
    jest.spyOn(logger, <span class="hljs-string">'log'</span>);
    fixture = TestBed.createComponent(TakeWhileOperatorExampleComponent);
  });

  describe(<span class="hljs-string">'when component is initialized'</span>, <span class="hljs-function">() =&gt;</span> {
    it(<span class="hljs-string">'should not call logger'</span>, <span class="hljs-function">() =&gt;</span> {
      fixture.detectChanges()
      expect(logger.log).toBeCalledTimes(<span class="hljs-number">0</span>);
    });

    describe(<span class="hljs-string">`and time passes by 10000ms and 5000ms after component destruction`</span>, <span class="hljs-function">() =&gt;</span> {
      it(<span class="hljs-string">'should call logger'</span>, fakeAsync(<span class="hljs-function">() =&gt;</span> {
        fixture.detectChanges();
        tick(fixture.componentInstance.interval * <span class="hljs-number">10</span>);
        fixture.destroy();
        tick(fixture.componentInstance.interval * <span class="hljs-number">5</span>);
        expect(logger.log).toBeCalledTimes(<span class="hljs-number">4</span>);
        expect(logger.log).toHaveBeenNthCalledWith(<span class="hljs-number">1</span>, <span class="hljs-string">'value:0'</span>);
        expect(logger.log).toHaveBeenNthCalledWith(<span class="hljs-number">2</span>, <span class="hljs-string">'value:1'</span>);
        expect(logger.log).toHaveBeenNthCalledWith(<span class="hljs-number">3</span>, <span class="hljs-string">'value:2'</span>);
        expect(logger.log).toHaveBeenNthCalledWith(<span class="hljs-number">4</span>, <span class="hljs-string">'value:3'</span>);
      }));
    });
  });
});
</code></pre>
<h3 id="heading-find">find</h3>
<p>This operator is similar to the native Array <code>find</code> method. It takes a <code>predicate</code> as an argument. When the predicate returns <code>truthy</code> value, the source value is emitted and the observable completes.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Component, inject, OnInit } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { find, interval, tap } <span class="hljs-keyword">from</span> <span class="hljs-string">'rxjs'</span>;
<span class="hljs-keyword">import</span> { LoggerService } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../services/logger.service'</span>;

<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-find-operator-example'</span>,
  template: <span class="hljs-string">''</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> FindOperatorExampleComponent <span class="hljs-keyword">implements</span> OnInit {
  <span class="hljs-keyword">readonly</span> interval = <span class="hljs-number">1000</span>;
  <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> logger = inject(LoggerService);

  ngOnInit(): <span class="hljs-built_in">void</span> {
    interval(<span class="hljs-built_in">this</span>.interval).pipe(
      find(<span class="hljs-function"><span class="hljs-params">value</span> =&gt;</span> value === <span class="hljs-number">4</span>),
      tap(<span class="hljs-function"><span class="hljs-params">value</span> =&gt;</span> {
        <span class="hljs-built_in">this</span>.logger.log(<span class="hljs-string">`value:<span class="hljs-subst">${ value }</span>`</span>);
      }),
    ).subscribe();
  }
}
</code></pre>
<h4 id="heading-test-it-6">Test it</h4>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { ComponentFixture, fakeAsync, TestBed, tick } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core/testing'</span>;
<span class="hljs-keyword">import</span> { FindOperatorExampleComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">'./find-operator-example.component'</span>;
<span class="hljs-keyword">import</span> { LoggerService } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../services/logger.service'</span>;


describe(<span class="hljs-string">'FindOperatorExampleComponent'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">let</span> fixture: ComponentFixture&lt;FindOperatorExampleComponent&gt;;
  <span class="hljs-keyword">let</span> logger: LoggerService;

  beforeEach(<span class="hljs-function">() =&gt;</span> {
    TestBed.configureTestingModule({
      declarations: [FindOperatorExampleComponent]
    })

    logger = TestBed.inject(LoggerService);
    jest.spyOn(logger, <span class="hljs-string">'log'</span>);
    fixture = TestBed.createComponent(FindOperatorExampleComponent);
  });

  describe(<span class="hljs-string">'when component is initialized'</span>, <span class="hljs-function">() =&gt;</span> {
    it(<span class="hljs-string">'should not call logger'</span>, <span class="hljs-function">() =&gt;</span> {
      fixture.detectChanges()
      expect(logger.log).toBeCalledTimes(<span class="hljs-number">0</span>);
    });

    describe(<span class="hljs-string">`and time passes by 10000ms and 5000ms after component destruction`</span>, <span class="hljs-function">() =&gt;</span> {
      it(<span class="hljs-string">'should call logger'</span>, fakeAsync(<span class="hljs-function">() =&gt;</span> {
        fixture.detectChanges();
        tick(fixture.componentInstance.interval * <span class="hljs-number">10</span>);
        fixture.destroy();
        tick(fixture.componentInstance.interval * <span class="hljs-number">5</span>);
        expect(logger.log).toBeCalledTimes(<span class="hljs-number">1</span>);
        expect(logger.log).toHaveBeenNthCalledWith(<span class="hljs-number">1</span>, <span class="hljs-string">'value:4'</span>);
      }));
    });
  });
});
</code></pre>
<p>The test is expecting a single call to <code>LoggerService</code> with <code>value:4</code> value.</p>
<h3 id="heading-find-index">find index</h3>
<p>This operator is similar to the native Array <code>findIndex</code> method. It takes a <code>predicate</code> as an argument. When the predicate returns <code>truthy</code> value, the index is emitted and the observable completes.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Component, inject, OnInit } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { findIndex, interval, map, tap } <span class="hljs-keyword">from</span> <span class="hljs-string">'rxjs'</span>;
<span class="hljs-keyword">import</span> { LoggerService } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../services/logger.service'</span>;

<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-find-index-operator-example'</span>,
  template: <span class="hljs-string">''</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> FindIndexOperatorExampleComponent <span class="hljs-keyword">implements</span> OnInit {
  <span class="hljs-keyword">readonly</span> interval = <span class="hljs-number">1000</span>;
  <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> logger = inject(LoggerService);

  ngOnInit(): <span class="hljs-built_in">void</span> {
    interval(<span class="hljs-built_in">this</span>.interval).pipe(
      map(<span class="hljs-function"><span class="hljs-params">value</span> =&gt;</span> value * <span class="hljs-number">3</span>),
      findIndex(<span class="hljs-function"><span class="hljs-params">value</span> =&gt;</span> value === <span class="hljs-number">15</span>),
      tap(<span class="hljs-function"><span class="hljs-params">value</span> =&gt;</span> {
        <span class="hljs-built_in">this</span>.logger.log(<span class="hljs-string">`value:<span class="hljs-subst">${ value }</span>`</span>);
      }),
    ).subscribe();
  }
}
</code></pre>
<p>In this example, the source value is multiplied by 3 to differentiate it from the value index.</p>
<h4 id="heading-test-it-7">Test it</h4>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { ComponentFixture, fakeAsync, TestBed, tick } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core/testing'</span>;
<span class="hljs-keyword">import</span> { FindIndexOperatorExampleComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">'./find-index-operator-example.component'</span>;
<span class="hljs-keyword">import</span> { LoggerService } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../services/logger.service'</span>;


describe(<span class="hljs-string">'FindIndexOperatorExampleComponent'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">let</span> fixture: ComponentFixture&lt;FindIndexOperatorExampleComponent&gt;;
  <span class="hljs-keyword">let</span> logger: LoggerService;

  beforeEach(<span class="hljs-function">() =&gt;</span> {
    TestBed.configureTestingModule({
      declarations: [FindIndexOperatorExampleComponent]
    })

    logger = TestBed.inject(LoggerService);
    jest.spyOn(logger, <span class="hljs-string">'log'</span>);
    fixture = TestBed.createComponent(FindIndexOperatorExampleComponent);
  });

  describe(<span class="hljs-string">'when component is initialized'</span>, <span class="hljs-function">() =&gt;</span> {
    it(<span class="hljs-string">'should not call logger'</span>, <span class="hljs-function">() =&gt;</span> {
      fixture.detectChanges()
      expect(logger.log).toBeCalledTimes(<span class="hljs-number">0</span>);
    });

    describe(<span class="hljs-string">`and time passes by 10000ms and 5000ms after component destruction`</span>, <span class="hljs-function">() =&gt;</span> {
      it(<span class="hljs-string">'should call logger'</span>, fakeAsync(<span class="hljs-function">() =&gt;</span> {
        fixture.detectChanges();
        tick(fixture.componentInstance.interval * <span class="hljs-number">10</span>);
        fixture.destroy();
        tick(fixture.componentInstance.interval * <span class="hljs-number">5</span>);
        expect(logger.log).toBeCalledTimes(<span class="hljs-number">1</span>);
        expect(logger.log).toHaveBeenNthCalledWith(<span class="hljs-number">1</span>, <span class="hljs-string">'value:5'</span>);
      }));
    });
  });
});
</code></pre>
<h3 id="heading-async-pipe">async pipe</h3>
<p>The built-in Angular <code>async</code> pipe takes care of subscribing and unsubscribing. The unsubscription happens when the element gets destroyed.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Component, inject, OnInit } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { first, interval, Observable, tap } <span class="hljs-keyword">from</span> <span class="hljs-string">'rxjs'</span>;
<span class="hljs-keyword">import</span> { LoggerService } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../services/logger.service'</span>;

<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-async-pipe-example'</span>,
  template: <span class="hljs-string">'&lt;span class="value"&gt;{{ value$ | async }}&lt;/span&gt;'</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AsyncPipeExampleComponent <span class="hljs-keyword">implements</span> OnInit {
  <span class="hljs-keyword">readonly</span> interval = <span class="hljs-number">1000</span>;
  <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> logger = inject(LoggerService);
  value$: Observable&lt;<span class="hljs-built_in">number</span>&gt; | <span class="hljs-literal">undefined</span>;

  ngOnInit(): <span class="hljs-built_in">void</span> {
    <span class="hljs-built_in">this</span>.value$ = interval(<span class="hljs-built_in">this</span>.interval).pipe(
      tap(<span class="hljs-function"><span class="hljs-params">value</span> =&gt;</span> {
        <span class="hljs-built_in">this</span>.logger.log(<span class="hljs-string">`value:<span class="hljs-subst">${ value }</span>`</span>);
      }));
  }
}
</code></pre>
<p>In this example, I created <code>value$</code> observable that starts emitting values as soon as the <code>async</code> pipe subscribes. I'm also using the interpolation, so the emitted value will be rendered within the <code>span</code>.</p>
<h4 id="heading-test-it-8">Test it</h4>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { ComponentFixture, fakeAsync, TestBed, tick } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core/testing'</span>;
<span class="hljs-keyword">import</span> { AsyncPipeExampleComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">'./async-pipe-example.component'</span>;
<span class="hljs-keyword">import</span> { LoggerService } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../services/logger.service'</span>;
<span class="hljs-keyword">import</span> { By } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/platform-browser'</span>;


describe(<span class="hljs-string">'AsyncPipeExampleComponent'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">let</span> fixture: ComponentFixture&lt;AsyncPipeExampleComponent&gt;;
  <span class="hljs-keyword">let</span> logger: LoggerService;

  beforeEach(<span class="hljs-function">() =&gt;</span> {
    TestBed.configureTestingModule({
      declarations: [AsyncPipeExampleComponent]
    })

    logger = TestBed.inject(LoggerService);
    jest.spyOn(logger, <span class="hljs-string">'log'</span>);
    fixture = TestBed.createComponent(AsyncPipeExampleComponent);
  });

  describe(<span class="hljs-string">'when component is initialized'</span>, <span class="hljs-function">() =&gt;</span> {
    it(<span class="hljs-string">'should not call logger'</span>, <span class="hljs-function">() =&gt;</span> {
      fixture.detectChanges()
      expect(logger.log).toBeCalledTimes(<span class="hljs-number">0</span>);
    });

    it(<span class="hljs-string">'should not display any value'</span>, <span class="hljs-function">() =&gt;</span> {
      expect(getValue()).toBe(<span class="hljs-string">''</span>);
    });

    describe(<span class="hljs-string">'and time passes by 1000ms and 5000ms after component destruction'</span>, <span class="hljs-function">() =&gt;</span> {
      it(<span class="hljs-string">'should call logger and display value'</span>, fakeAsync(<span class="hljs-function">() =&gt;</span> {
        fixture.detectChanges();
        expect(getValue()).toBe(<span class="hljs-string">''</span>);

        tick(fixture.componentInstance.interval);
        fixture.detectChanges();
        expect(getValue()).toBe(<span class="hljs-string">'0'</span>);

        fixture.destroy();
        tick(fixture.componentInstance.interval * <span class="hljs-number">5</span>);
        expect(logger.log).toBeCalledTimes(<span class="hljs-number">1</span>);
        expect(logger.log).toHaveBeenNthCalledWith(<span class="hljs-number">1</span>, <span class="hljs-string">'value:0'</span>);
      }));
    });

    describe(<span class="hljs-string">`and time passes by 3000ms and 5000ms after component destruction`</span>, <span class="hljs-function">() =&gt;</span> {
      it(<span class="hljs-string">'should call logger'</span>, fakeAsync(<span class="hljs-function">() =&gt;</span> {
        fixture.detectChanges();
        expect(getValue()).toBe(<span class="hljs-string">''</span>);
        tick(fixture.componentInstance.interval * <span class="hljs-number">3</span>);
        fixture.detectChanges();
        expect(getValue()).toBe(<span class="hljs-string">'2'</span>);

        fixture.destroy();
        tick(fixture.componentInstance.interval * <span class="hljs-number">5</span>);
        expect(logger.log).toBeCalledTimes(<span class="hljs-number">3</span>);
        expect(logger.log).toHaveBeenNthCalledWith(<span class="hljs-number">1</span>, <span class="hljs-string">'value:0'</span>);
        expect(logger.log).toHaveBeenNthCalledWith(<span class="hljs-number">2</span>, <span class="hljs-string">'value:1'</span>);
        expect(logger.log).toHaveBeenNthCalledWith(<span class="hljs-number">3</span>, <span class="hljs-string">'value:2'</span>);
      }));
    });
  });

  <span class="hljs-keyword">const</span> getValue = (): <span class="hljs-built_in">string</span> | <span class="hljs-function"><span class="hljs-params">undefined</span>  =&gt;</span> {
    <span class="hljs-keyword">return</span> fixture.debugElement.query(By.css(<span class="hljs-string">'.value'</span>))?.nativeElement.textContent;
  }
});
</code></pre>
<p>In addition to previous checks of the <code>LoggerService</code> calls I'm also verifying the span's content (identified by <code>value</code> css class).</p>
<h3 id="heading-ngneatuntil-destroy-library"><code>@ngneat/until-destroy</code> library</h3>
<p>The creators of this library call it <code>a neat way to unsubscribe from observables when the component destroyed</code>. And that's exactly what it does.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Component, inject, OnInit } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { interval, tap } <span class="hljs-keyword">from</span> <span class="hljs-string">'rxjs'</span>;
<span class="hljs-keyword">import</span> { LoggerService } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../services/logger.service'</span>;
<span class="hljs-keyword">import</span> { UntilDestroy, untilDestroyed } <span class="hljs-keyword">from</span> <span class="hljs-string">'@ngneat/until-destroy'</span>;

<span class="hljs-meta">@UntilDestroy</span>()
<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-ngneat-until-destroy-example'</span>,
  template: <span class="hljs-string">''</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> NgneatUntilDestroyExampleComponent <span class="hljs-keyword">implements</span> OnInit {
  <span class="hljs-keyword">readonly</span> interval = <span class="hljs-number">1000</span>;
  <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> logger = inject(LoggerService);

  ngOnInit(): <span class="hljs-built_in">void</span> {
    interval(<span class="hljs-built_in">this</span>.interval).pipe(
      tap(<span class="hljs-function"><span class="hljs-params">value</span> =&gt;</span> {
        <span class="hljs-built_in">this</span>.logger.log(<span class="hljs-string">`value:<span class="hljs-subst">${ value }</span>`</span>);
      }),
      untilDestroyed(<span class="hljs-built_in">this</span>)
    ).subscribe();
  }
}
</code></pre>
<p>The first thing to do is to decorate the component with <code>@UntilDestroy</code> decorator. It has to be done before the <code>@Component</code> decorator to work properly. Next, when using an observable just use the <code>untilDestroy</code> operator and pass <code>this</code> reference <code>untilDestroyed(this)</code>.</p>
<p>The <code>@UntilDestroy</code> decorator accepts arguments and allows for more control over the unsubscribing (i.e. can automatically unsubscribe from properties) but it's out of this article's scope.</p>
<h4 id="heading-test-it-9">Test it</h4>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { ComponentFixture, fakeAsync, TestBed, tick } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core/testing'</span>;
<span class="hljs-keyword">import</span> { NgneatUntilDestroyExampleComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">'./ngneat-until-destroy-example.component'</span>;
<span class="hljs-keyword">import</span> { LoggerService } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../services/logger.service'</span>;


describe(<span class="hljs-string">'NgneatUntilDestroyExampleComponent'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">let</span> fixture: ComponentFixture&lt;NgneatUntilDestroyExampleComponent&gt;;
  <span class="hljs-keyword">let</span> logger: LoggerService;

  beforeEach(<span class="hljs-function">() =&gt;</span> {
    TestBed.configureTestingModule({
      declarations: [NgneatUntilDestroyExampleComponent]
    })

    logger = TestBed.inject(LoggerService);
    jest.spyOn(logger, <span class="hljs-string">'log'</span>);
    fixture = TestBed.createComponent(NgneatUntilDestroyExampleComponent);
  });

  describe(<span class="hljs-string">'when component is initialized'</span>, <span class="hljs-function">() =&gt;</span> {
    it(<span class="hljs-string">'should not call logger'</span>, <span class="hljs-function">() =&gt;</span> {
      fixture.detectChanges()
      expect(logger.log).toBeCalledTimes(<span class="hljs-number">0</span>);
    });

    describe(<span class="hljs-string">'and time passes by 1000ms and 5000ms after component destruction'</span>, <span class="hljs-function">() =&gt;</span> {
      it(<span class="hljs-string">'should call logger'</span>, fakeAsync(<span class="hljs-function">() =&gt;</span> {
        fixture.detectChanges();
        tick(fixture.componentInstance.interval);
        fixture.destroy();
        tick(fixture.componentInstance.interval * <span class="hljs-number">5</span>);
        expect(logger.log).toBeCalledTimes(<span class="hljs-number">1</span>);
        expect(logger.log).toHaveBeenNthCalledWith(<span class="hljs-number">1</span>, <span class="hljs-string">'value:0'</span>);
      }));
    });

    describe(<span class="hljs-string">`and time passes by 3000ms and 5000ms after component destruction`</span>, <span class="hljs-function">() =&gt;</span> {
      it(<span class="hljs-string">'should call logger'</span>, fakeAsync(<span class="hljs-function">() =&gt;</span> {
        fixture.detectChanges();
        tick(fixture.componentInstance.interval * <span class="hljs-number">3</span>);
        fixture.destroy();
        tick(fixture.componentInstance.interval * <span class="hljs-number">5</span>);
        expect(logger.log).toBeCalledTimes(<span class="hljs-number">3</span>);
        expect(logger.log).toHaveBeenNthCalledWith(<span class="hljs-number">1</span>, <span class="hljs-string">'value:0'</span>);
        expect(logger.log).toHaveBeenNthCalledWith(<span class="hljs-number">2</span>, <span class="hljs-string">'value:1'</span>);
        expect(logger.log).toHaveBeenNthCalledWith(<span class="hljs-number">3</span>, <span class="hljs-string">'value:2'</span>);
      }));
    });
  });
});
</code></pre>
<h1 id="heading-a-common-pitfall">A common pitfall</h1>
<p>The RxJS operators like <code>take, first, takeWhile, find, findIndex</code> should be always used with a safety belt like <code>takeUntil</code> or <code>untilDestroyed</code> operator. If the component gets destroyed, and you assume, that the observable will always emit at least one value, but it doesn't happen, the subscription will live in the memory. That's the <code>memory leak</code>.</p>
<p>Create a test service</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Subject } <span class="hljs-keyword">from</span> <span class="hljs-string">'rxjs'</span>;
<span class="hljs-keyword">import</span> { Injectable } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;

<span class="hljs-meta">@Injectable</span>()
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> MyService {
  <span class="hljs-keyword">private</span> values$$ = <span class="hljs-keyword">new</span> Subject&lt;<span class="hljs-built_in">number</span>&gt;();
  value$ = <span class="hljs-built_in">this</span>.values$$.asObservable();

  sendValue(value: <span class="hljs-built_in">number</span>): <span class="hljs-built_in">void</span> {
    <span class="hljs-built_in">this</span>.values$$.next(value);
  }
}
</code></pre>
<p>and a component</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Component, inject, OnInit } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { first, tap } <span class="hljs-keyword">from</span> <span class="hljs-string">'rxjs'</span>;
<span class="hljs-keyword">import</span> { LoggerService } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../services/logger.service'</span>;
<span class="hljs-keyword">import</span> { MyService } <span class="hljs-keyword">from</span> <span class="hljs-string">'./my-service'</span>;

<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-pitfall-example'</span>,
  template: <span class="hljs-string">''</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> PitfallExampleComponent <span class="hljs-keyword">implements</span> OnInit {
  <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> logger = inject(LoggerService);
  <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> myService = inject(MyService);

  ngOnInit(): <span class="hljs-built_in">void</span> {
    <span class="hljs-built_in">this</span>.myService.value$.pipe(
      first(<span class="hljs-function"><span class="hljs-params">value</span> =&gt;</span> value &lt; <span class="hljs-number">0</span>),
      tap(<span class="hljs-function"><span class="hljs-params">value</span> =&gt;</span> <span class="hljs-built_in">this</span>.logger.log(<span class="hljs-string">`value:<span class="hljs-subst">${ value }</span>`</span>)),
    ).subscribe();
  }
}
</code></pre>
<p>In this example, I'm subscribing to <code>MySerice.value$</code> observable that emits passed numbers. I used <code>first</code> operator <code>first(value =&gt; value &lt; 0)</code> that takes the first value lesser than 0. Now, when the component gets destroyed, this subscription will stay in the memory since there were no circumstances to unsubscribe.</p>
<h2 id="heading-test-it-10">Test it</h2>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { ComponentFixture, TestBed } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core/testing'</span>;
<span class="hljs-keyword">import</span> { PitfallExampleComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">'./pitfall-example.component'</span>;
<span class="hljs-keyword">import</span> { LoggerService } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../services/logger.service'</span>;
<span class="hljs-keyword">import</span> { MyService } <span class="hljs-keyword">from</span> <span class="hljs-string">'./my-service'</span>;


describe(<span class="hljs-string">'PitfallExampleComponent'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">let</span> fixture: ComponentFixture&lt;PitfallExampleComponent&gt;;
  <span class="hljs-keyword">let</span> logger: LoggerService;
  <span class="hljs-keyword">let</span> myService: MyService;

  beforeEach(<span class="hljs-function">() =&gt;</span> {
    TestBed.configureTestingModule({
      declarations: [PitfallExampleComponent],
      providers: [MyService]
    })

    myService = TestBed.inject(MyService);
    logger = TestBed.inject(LoggerService);
    jest.spyOn(logger, <span class="hljs-string">'log'</span>);
    fixture = TestBed.createComponent(PitfallExampleComponent);
  });

  describe(<span class="hljs-string">'when component is initialized'</span>, <span class="hljs-function">() =&gt;</span> {
    it(<span class="hljs-string">'should not call logger'</span>, <span class="hljs-function">() =&gt;</span> {
      fixture.detectChanges()
      expect(logger.log).toBeCalledTimes(<span class="hljs-number">0</span>);
    });

    describe(<span class="hljs-string">'and the value "1" is passed to service'</span>, <span class="hljs-function">() =&gt;</span> {
      beforeEach(<span class="hljs-function">() =&gt;</span> {
        myService.sendValue(<span class="hljs-number">1</span>);
        fixture.detectChanges();
      });

      it(<span class="hljs-string">'should not call logger'</span>, <span class="hljs-function">() =&gt;</span> {
        expect(logger.log).toBeCalledTimes(<span class="hljs-number">0</span>);
      });

      describe(<span class="hljs-string">'and component gets destroyed'</span>, <span class="hljs-function">() =&gt;</span> {
        beforeEach(<span class="hljs-function">() =&gt;</span> {
          fixture.destroy();
          fixture.detectChanges();
        });

        it(<span class="hljs-string">'should not call logger'</span>, <span class="hljs-function">() =&gt;</span> {
          expect(logger.log).toBeCalledTimes(<span class="hljs-number">0</span>);
        });

        describe(<span class="hljs-string">'and the value "-2" is passed to the service'</span>, <span class="hljs-function">() =&gt;</span> {
          beforeEach(<span class="hljs-function">() =&gt;</span> {
            myService.sendValue(<span class="hljs-number">-2</span>);
            fixture.detectChanges();
          });

          it(<span class="hljs-string">'should call logger'</span>, <span class="hljs-function">() =&gt;</span> {
            expect(logger.log).toBeCalledTimes(<span class="hljs-number">1</span>);
            expect(logger.log).toHaveBeenNthCalledWith(<span class="hljs-number">1</span>, <span class="hljs-string">'value:-2'</span>);
          });
        });
      });
    });
  });
});
</code></pre>
<p>The test verifies the memory leak by the following steps</p>
<ul>
<li><p>initializes component</p>
</li>
<li><p>sends value <code>1</code></p>
</li>
<li><p>verifies that <code>Logger.log</code> was not called</p>
</li>
<li><p>destroys the component</p>
</li>
<li><p>sends value <code>-2</code></p>
</li>
<li><p>verifies that <code>Logger.log</code> was called once with <code>value:-2</code> argument</p>
</li>
</ul>
<p>To fix that problem you could use <code>takeUntil</code> or <code>untilDestroyed</code>. The first one has to be somehow tied to <code>ngOnDestroy</code> hook (like in the example). The second requires just annotating the component. Nevertheless, the memory leak is a common problem and should be always kept in mind.</p>
<h1 id="heading-source-code">Source code</h1>
<p>https://gitlab.com/barcioch-blog-examples/012-angular-rxjs-unsubscribing</p>
]]></content:encoded></item><item><title><![CDATA[RxJS log value pipe]]></title><description><![CDATA[Sometimes, instead of using debugger keyword, you'll just want to display the values in the console. When using RxJS you'll probably use quick&dirty tap(value => console.log(value)). There's nothing wrong with it, but it can be done a little nicer an...]]></description><link>https://blog.procode.pl/rxjs-log-value-pipe</link><guid isPermaLink="true">https://blog.procode.pl/rxjs-log-value-pipe</guid><category><![CDATA[RxJS]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[pipe]]></category><category><![CDATA[logging]]></category><category><![CDATA[operator]]></category><dc:creator><![CDATA[Bartosz Szłapak]]></dc:creator><pubDate>Tue, 26 Sep 2023 17:20:50 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1695725055474/a708230e-2a60-4ade-801a-8e8878d11b7b.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Sometimes, instead of using <code>debugger</code> keyword, you'll just want to display the values in the console. When using RxJS you'll probably use quick&amp;dirty <code>tap(value =&gt; console.log(value))</code>. There's nothing wrong with it, but it can be done a little nicer and manageable. I'll create a custom RxJS operator to achieve this.</p>
<h1 id="heading-setup">Setup</h1>
<p>Requirements: Node, npm, npx</p>
<p>Install dependencies</p>
<pre><code class="lang-shell">npm i jest@29 @types/jest@29 ts-jest rxjs typescript
</code></pre>
<p>Init <code>ts-jest</code></p>
<pre><code class="lang-shell">npx ts-jest config:init
</code></pre>
<h1 id="heading-logvalue-operator">logValue operator</h1>
<p>This operator uses the native <code>tap</code> operator to call the <code>console.log()</code>. Simple as that.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { MonoTypeOperatorFunction } <span class="hljs-keyword">from</span> <span class="hljs-string">'rxjs'</span>;
<span class="hljs-keyword">import</span> { tap } <span class="hljs-keyword">from</span> <span class="hljs-string">'rxjs/operators'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">logValue</span>&lt;<span class="hljs-title">T</span>&gt;(<span class="hljs-params"></span>): <span class="hljs-title">MonoTypeOperatorFunction</span>&lt;<span class="hljs-title">T</span>&gt; </span>{
  <span class="hljs-keyword">return</span> tap((value: T): <span class="hljs-function"><span class="hljs-params">void</span> =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(value);
  });
}
</code></pre>
<p>So why use it anyway? There are a couple of advantages:</p>
<ul>
<li><p>easily find usages of this operator instead of searching the whole project for <code>console.log</code> string</p>
</li>
<li><p>easily replace <code>console.log</code> with any other logging mechanism</p>
</li>
<li><p>easily add some logic to enable/disable logging (i.e. use environment variable)</p>
</li>
<li><p>less code and ease of use</p>
</li>
</ul>
<h2 id="heading-test-it">Test it</h2>
<p>The test performs the following operations:</p>
<ul>
<li><p>spies on <code>console.log</code> method</p>
</li>
<li><p>emits single and multiple values in the stream</p>
</li>
<li><p>tests the number of calls and the arguments of the spy</p>
</li>
</ul>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { <span class="hljs-keyword">from</span>, <span class="hljs-keyword">of</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'rxjs'</span>;
<span class="hljs-keyword">import</span> { logValue } <span class="hljs-keyword">from</span> <span class="hljs-string">'./log-value.operator'</span>;

describe(<span class="hljs-string">'Test logValue operator'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">let</span> spy: jest.SpyInstance;

  beforeEach(<span class="hljs-function">() =&gt;</span> {
    spy = jest.spyOn(<span class="hljs-built_in">console</span>, <span class="hljs-string">'log'</span>);
  });

  afterEach(<span class="hljs-function">() =&gt;</span> {
    spy.mockClear();
  });

  describe(<span class="hljs-string">'Test single logValue call'</span>, <span class="hljs-function">() =&gt;</span> {
    beforeEach(<span class="hljs-function">() =&gt;</span> {
      <span class="hljs-keyword">of</span>(<span class="hljs-string">'the string'</span>).pipe(logValue()).subscribe();
    });

    it(<span class="hljs-string">'should call console.log once'</span>, <span class="hljs-function">() =&gt;</span> {
      expect(<span class="hljs-built_in">console</span>.log).toHaveBeenCalledTimes(<span class="hljs-number">1</span>);
      expect(<span class="hljs-built_in">console</span>.log).toHaveBeenNthCalledWith(<span class="hljs-number">1</span>, <span class="hljs-string">'the string'</span>);
    });
  });

  describe(<span class="hljs-string">'Test multiple logValue calls'</span>, <span class="hljs-function">() =&gt;</span> {
    beforeEach(<span class="hljs-function">() =&gt;</span> {
      <span class="hljs-keyword">const</span> data = [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>];
      <span class="hljs-keyword">from</span>(data).pipe(logValue()).subscribe();
    });

    it(<span class="hljs-string">'should call console.log 5 times'</span>, <span class="hljs-function">() =&gt;</span> {
      expect(<span class="hljs-built_in">console</span>.log).toHaveBeenCalledTimes(<span class="hljs-number">5</span>);
      expect(<span class="hljs-built_in">console</span>.log).toHaveBeenNthCalledWith(<span class="hljs-number">1</span>, <span class="hljs-number">1</span>);
      expect(<span class="hljs-built_in">console</span>.log).toHaveBeenNthCalledWith(<span class="hljs-number">2</span>, <span class="hljs-number">2</span>);
      expect(<span class="hljs-built_in">console</span>.log).toHaveBeenNthCalledWith(<span class="hljs-number">3</span>, <span class="hljs-number">3</span>);
      expect(<span class="hljs-built_in">console</span>.log).toHaveBeenNthCalledWith(<span class="hljs-number">4</span>, <span class="hljs-number">4</span>);
      expect(<span class="hljs-built_in">console</span>.log).toHaveBeenNthCalledWith(<span class="hljs-number">5</span>, <span class="hljs-number">5</span>);
    });
  });
});
</code></pre>
<p>Run <code>npx jest</code> and check the results. By default, you'll get some <code>console.log</code> outputs on the screen, but the final result should be similar to:</p>
<pre><code class="lang-shell"> PASS  src/log-value.operator.spec.ts
  Test logValue operator
    Test single logValue call
      ✓ should call console.log once
    Test multiple logValue calls
      ✓ should call console.log 5 times

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Null, nullish, nullable]]></title><description><![CDATA[In most programming languages there is only one way of defining whether the value is set or not. That value is generally considered null but named differently. But the Javascript went one step further and introduced two of them: null and undefined. U...]]></description><link>https://blog.procode.pl/null-nullish-nullable</link><guid isPermaLink="true">https://blog.procode.pl/null-nullish-nullable</guid><category><![CDATA[Null]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[RxJS]]></category><category><![CDATA[nullable]]></category><dc:creator><![CDATA[Bartosz Szłapak]]></dc:creator><pubDate>Fri, 22 Sep 2023 12:55:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1695547256970/67574248-2d28-425a-a99e-411a39521dd0.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In most programming languages there is only one way of defining whether the value is set or not. That value is generally considered <code>null</code> but named differently. But the Javascript went one step further and introduced two of them: <code>null</code> and <code>undefined</code>. Usually, they are considered in the same fashion - both are treated as <strong>nullable</strong> values which means there is no difference (for the developer) which of them is assigned. In this article, I'll show how to make your life easier by introducing functions, Angular pipes and RxJS operator to handle them.</p>
<h1 id="heading-setup">Setup</h1>
<p>Requirements: Node, npm, npx</p>
<p>Install dependencies</p>
<pre><code class="lang-shell">npm i jest@29 @types/jest@29 ts-jest rxjs typescript
</code></pre>
<h1 id="heading-basic-null-checks">Basic null checks</h1>
<p>The most basic null check can be done in a few ways.</p>
<p>The first one is a loose comparison. Comparing a nullable value directly to the <code>null</code> or <code>undefined</code> results in <code>true</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">if</span> (value == <span class="hljs-literal">null</span>) {
  <span class="hljs-comment">// true if value is null or undefined</span>
}

<span class="hljs-keyword">if</span> (value == <span class="hljs-literal">undefined</span>) {
  <span class="hljs-comment">// true if value is null or undefined</span>
}
</code></pre>
<p>When enforcing strict equality the code becomes more tedious.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">if</span> (value === <span class="hljs-literal">null</span> || value === <span class="hljs-literal">undefined</span>) {
  <span class="hljs-comment">// true if value is null or undefined</span>
}
</code></pre>
<h1 id="heading-types-and-helpers">Types and helpers</h1>
<p>Let's create the types and helper functions.</p>
<p><code>Nullable&lt;T&gt;</code> - a union of <code>current variable type</code>, <code>null</code> and <code>undefined</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> Nullable&lt;T&gt; = T | <span class="hljs-literal">null</span> | <span class="hljs-literal">undefined</span>;
</code></pre>
<p><code>Nullish</code> - a union of <code>null</code> and <code>undefined</code> types.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> Nullish = <span class="hljs-literal">null</span> | <span class="hljs-literal">undefined</span>;
</code></pre>
<p><code>NonNullish&lt;T&gt;</code> - a <code>current variable type</code> with <code>null</code> and <code>undefined</code> types excluded. The <code>Exclude</code> comes from standard Typescript's utility types. You can also use the Typescript's <code>NonNullable&lt;T&gt;</code> type but I prefer my definition for more readability.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> NonNullish&lt;T&gt; = Exclude&lt;T, <span class="hljs-literal">null</span> | <span class="hljs-literal">undefined</span>&gt;;
</code></pre>
<p><code>isNullish</code> function - checks whether the value is <code>null</code> or <code>undefined</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> isNullish = (value: unknown): value is Nullish =&gt; value === <span class="hljs-literal">null</span> || value === <span class="hljs-literal">undefined</span>;
</code></pre>
<p><code>isNonNullish</code> function - checks whether the value is different from <code>null</code> and <code>undefined</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> isNonNullish = (value: unknown): value is NonNullish&lt;unknown&gt; =&gt; value !== <span class="hljs-literal">null</span> &amp;&amp; value !== <span class="hljs-literal">undefined</span>;
</code></pre>
<p>The file with definitions might look like this</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// nullable.ts</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> Nullable&lt;T&gt; = T | <span class="hljs-literal">null</span> | <span class="hljs-literal">undefined</span>;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> Nullish = <span class="hljs-literal">null</span> | <span class="hljs-literal">undefined</span>;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> NonNullish&lt;T&gt; = Exclude&lt;T, <span class="hljs-literal">null</span> | <span class="hljs-literal">undefined</span>&gt;;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> isNullish = (value: unknown): value is Nullish =&gt; value === <span class="hljs-literal">null</span> || value === <span class="hljs-literal">undefined</span>;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> isNonNullish = (value: unknown): value is NonNullish&lt;unknown&gt; =&gt; value !== <span class="hljs-literal">null</span> &amp;&amp; value !== <span class="hljs-literal">undefined</span>;
</code></pre>
<h2 id="heading-test-it">Test it</h2>
<p>Prepare the datasets, pass the values to the functions and check the result.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// nullable.spec.ts</span>
<span class="hljs-keyword">import</span> { isNonNullish, isNullish } <span class="hljs-keyword">from</span> <span class="hljs-string">'./nullable'</span>;

<span class="hljs-keyword">const</span> isNullishDataset: { key: <span class="hljs-built_in">string</span>; value: <span class="hljs-built_in">any</span>; expectedResult: <span class="hljs-built_in">boolean</span> }[] = [
  { key: <span class="hljs-string">'null'</span>, value: <span class="hljs-literal">null</span>, expectedResult: <span class="hljs-literal">true</span> },
  { key: <span class="hljs-string">'undefined'</span>, value: <span class="hljs-literal">undefined</span>, expectedResult: <span class="hljs-literal">true</span> },
  { key: <span class="hljs-string">'string290'</span>, value: <span class="hljs-string">'string290'</span>, expectedResult: <span class="hljs-literal">false</span> },
  { key: <span class="hljs-string">'""'</span>, value: <span class="hljs-string">''</span>, expectedResult: <span class="hljs-literal">false</span> },
  { key: <span class="hljs-string">'false'</span>, value: <span class="hljs-literal">false</span>, expectedResult: <span class="hljs-literal">false</span> },
  { key: <span class="hljs-string">'true'</span>, value: <span class="hljs-literal">true</span>, expectedResult: <span class="hljs-literal">false</span> },
  { key: <span class="hljs-string">'1'</span>, value: <span class="hljs-number">1</span>, expectedResult: <span class="hljs-literal">false</span> },
  { key: <span class="hljs-string">'-1'</span>, value: <span class="hljs-number">-1</span>, expectedResult: <span class="hljs-literal">false</span> },
  { key: <span class="hljs-string">'0'</span>, value: <span class="hljs-number">0</span>, expectedResult: <span class="hljs-literal">false</span> },
  { key: <span class="hljs-string">'Infinity'</span>, value: <span class="hljs-literal">Infinity</span>, expectedResult: <span class="hljs-literal">false</span> },
  { key: <span class="hljs-string">'-Infinity'</span>, value: -<span class="hljs-literal">Infinity</span>, expectedResult: <span class="hljs-literal">false</span> },
  { key: <span class="hljs-string">'[]'</span>, value: [], expectedResult: <span class="hljs-literal">false</span> },
  { key: <span class="hljs-string">'["e1", 23]'</span>, value: [<span class="hljs-string">'e1'</span>, <span class="hljs-number">23</span>], expectedResult: <span class="hljs-literal">false</span> },
  { key: <span class="hljs-string">'{}'</span>, value: {}, expectedResult: <span class="hljs-literal">false</span> },
  { key: <span class="hljs-string">'{prop1: false, prop2: 90}'</span>, value: { prop1: <span class="hljs-literal">false</span>, prop2: <span class="hljs-number">90</span> }, expectedResult: <span class="hljs-literal">false</span> },
  {
    key: <span class="hljs-string">'() =&gt; {}'</span>, value: <span class="hljs-function">() =&gt;</span> {
    }, expectedResult: <span class="hljs-literal">false</span>
  },
  { key: <span class="hljs-string">'NaN'</span>, value: <span class="hljs-literal">NaN</span>, expectedResult: <span class="hljs-literal">false</span> },
  { key: <span class="hljs-string">'new Error()'</span>, value: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(), expectedResult: <span class="hljs-literal">false</span> },
];


describe(<span class="hljs-string">'Test isNullish'</span>, <span class="hljs-function">() =&gt;</span> {
  it.each(isNullishDataset)(<span class="hljs-string">'value: $key'</span>, <span class="hljs-function">(<span class="hljs-params">{ value, expectedResult }</span>) =&gt;</span> {
    expect(isNullish(value)).toEqual(expectedResult);
  });
});

<span class="hljs-keyword">const</span> isNonNullishDataset: { key: <span class="hljs-built_in">string</span>; value: <span class="hljs-built_in">any</span>; expectedResult: <span class="hljs-built_in">boolean</span> }[] = [
  { key: <span class="hljs-string">'null'</span>, value: <span class="hljs-literal">null</span>, expectedResult: <span class="hljs-literal">false</span> },
  { key: <span class="hljs-string">'undefined'</span>, value: <span class="hljs-literal">undefined</span>, expectedResult: <span class="hljs-literal">false</span> },
  { key: <span class="hljs-string">'string290'</span>, value: <span class="hljs-string">'string290'</span>, expectedResult: <span class="hljs-literal">true</span> },
  { key: <span class="hljs-string">'""'</span>, value: <span class="hljs-string">''</span>, expectedResult: <span class="hljs-literal">true</span> },
  { key: <span class="hljs-string">'false'</span>, value: <span class="hljs-literal">false</span>, expectedResult: <span class="hljs-literal">true</span> },
  { key: <span class="hljs-string">'true'</span>, value: <span class="hljs-literal">true</span>, expectedResult: <span class="hljs-literal">true</span> },
  { key: <span class="hljs-string">'1'</span>, value: <span class="hljs-number">1</span>, expectedResult: <span class="hljs-literal">true</span> },
  { key: <span class="hljs-string">'-1'</span>, value: <span class="hljs-number">-1</span>, expectedResult: <span class="hljs-literal">true</span> },
  { key: <span class="hljs-string">'0'</span>, value: <span class="hljs-number">0</span>, expectedResult: <span class="hljs-literal">true</span> },
  { key: <span class="hljs-string">'Infinity'</span>, value: <span class="hljs-literal">Infinity</span>, expectedResult: <span class="hljs-literal">true</span> },
  { key: <span class="hljs-string">'-Infinity'</span>, value: -<span class="hljs-literal">Infinity</span>, expectedResult: <span class="hljs-literal">true</span> },
  { key: <span class="hljs-string">'[]'</span>, value: [], expectedResult: <span class="hljs-literal">true</span> },
  { key: <span class="hljs-string">'["e1", 23]'</span>, value: [<span class="hljs-string">'e1'</span>, <span class="hljs-number">23</span>], expectedResult: <span class="hljs-literal">true</span> },
  { key: <span class="hljs-string">'{}'</span>, value: {}, expectedResult: <span class="hljs-literal">true</span> },
  { key: <span class="hljs-string">'{prop1: false, prop2: 90}'</span>, value: { prop1: <span class="hljs-literal">false</span>, prop2: <span class="hljs-number">90</span> }, expectedResult: <span class="hljs-literal">true</span> },
  {
    key: <span class="hljs-string">'() =&gt; {}'</span>, value: <span class="hljs-function">() =&gt;</span> {
    }, expectedResult: <span class="hljs-literal">true</span>
  },
  { key: <span class="hljs-string">'NaN'</span>, value: <span class="hljs-literal">NaN</span>, expectedResult: <span class="hljs-literal">true</span> },
  { key: <span class="hljs-string">'new Error()'</span>, value: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(), expectedResult: <span class="hljs-literal">true</span> },
];

describe(<span class="hljs-string">'Test isNonNullish'</span>, <span class="hljs-function">() =&gt;</span> {
  it.each(isNonNullishDataset)(<span class="hljs-string">'value: $key'</span>, <span class="hljs-function">(<span class="hljs-params">{ value, expectedResult }</span>) =&gt;</span> {
    expect(isNonNullish(value)).toEqual(expectedResult);
  });
});
</code></pre>
<h1 id="heading-rxjs-operator-to-filter-nullish-values">RxJS operator to filter nullish values</h1>
<p>The operator</p>
<ul>
<li><p>invokes native <code>filter</code> operator on the observable</p>
</li>
<li><p>checks if the value is non-nullish with the previously defined function <code>isNonNullish</code></p>
</li>
<li><p>returns the source observable</p>
</li>
</ul>
<p><code>filterNullish</code> - a wrapper function that returns <code>OperatorFunction</code> which is one of the RxJS's operator interfaces. The filtering:</p>
<ul>
<li><p>uses native <code>filter</code> operator</p>
</li>
<li><p>tells the compiler that the returned type is <code>NonNullish&lt;T&gt;</code></p>
</li>
<li><p>invokes and returns the result of previously defined <code>isNonNullish</code> function</p>
</li>
</ul>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { filter, OperatorFunction } <span class="hljs-keyword">from</span> <span class="hljs-string">'rxjs'</span>;
<span class="hljs-keyword">import</span> { isNonNullish, NonNullish } <span class="hljs-keyword">from</span> <span class="hljs-string">'./nullable'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">filterNullable</span>&lt;<span class="hljs-title">T</span>&gt;(<span class="hljs-params"></span>): <span class="hljs-title">OperatorFunction</span>&lt;<span class="hljs-title">T</span>, <span class="hljs-title">NonNullish</span>&lt;<span class="hljs-title">T</span>&gt;&gt; </span>{
  <span class="hljs-keyword">return</span> filter((value: T): value is NonNullish&lt;T&gt; =&gt; isNonNullish(value));
}
</code></pre>
<h2 id="heading-test-it-1">Test it</h2>
<p>The test is more complex</p>
<ul>
<li><p>define <code>inputValues</code> to test the operator</p>
</li>
<li><p>use <code>jest.useFakeTimers()</code> to "control the time"</p>
</li>
<li><p>within the test</p>
<ul>
<li><p>create observable from test values <code>from(inputValues)</code></p>
</li>
<li><p>use the operator <code>filterNullable()</code> inside the pipe</p>
</li>
<li><p>accumulate all the values in the array by using <code>reduce</code> operator</p>
</li>
</ul>
<pre><code class="lang-typescript">reduce(<span class="hljs-function">(<span class="hljs-params">acc, val</span>) =&gt;</span> {
  acc.push(val);

  <span class="hljs-keyword">return</span> acc;
}, [] <span class="hljs-keyword">as</span> <span class="hljs-built_in">any</span>[])
</code></pre>
<ul>
<li>subscribe to the result and run the <code>expect</code></li>
</ul>
<pre><code class="lang-typescript">subscribe(<span class="hljs-function"><span class="hljs-params">result</span> =&gt;</span> {
  expect(result).toEqual(expectedResult);
})
</code></pre>
<ul>
<li>advance timers <code>jest.advanceTimersToNextTimer()</code> allowing observable to be processed</li>
</ul>
</li>
</ul>
<p>The whole test</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// filter-nullish-operator.spec.ts</span>

<span class="hljs-keyword">import</span> { <span class="hljs-keyword">from</span>, reduce } <span class="hljs-keyword">from</span> <span class="hljs-string">'rxjs'</span>;
<span class="hljs-keyword">import</span> { filterNullish } <span class="hljs-keyword">from</span> <span class="hljs-string">'./filter-nullish-operator'</span>;

<span class="hljs-keyword">const</span> inputValues = [
  <span class="hljs-literal">null</span>,
  <span class="hljs-literal">undefined</span>,
  <span class="hljs-string">'string290'</span>,
  <span class="hljs-string">''</span>,
  <span class="hljs-literal">false</span>,
  <span class="hljs-literal">true</span>,
  <span class="hljs-number">1</span>,
  <span class="hljs-number">-1</span>,
  <span class="hljs-number">0</span>,
  <span class="hljs-literal">Infinity</span>,
  -<span class="hljs-literal">Infinity</span>,
  [],
  [
    <span class="hljs-string">'e1'</span>,
    <span class="hljs-number">23</span>,
  ],
  {},
  {
    <span class="hljs-string">'prop1'</span>: <span class="hljs-literal">false</span>,
    <span class="hljs-string">'prop2'</span>: <span class="hljs-number">90</span>,
  },
  <span class="hljs-literal">NaN</span>,
];

<span class="hljs-keyword">const</span> expectedResult = [
  <span class="hljs-string">'string290'</span>,
  <span class="hljs-string">''</span>,
  <span class="hljs-literal">false</span>,
  <span class="hljs-literal">true</span>,
  <span class="hljs-number">1</span>,
  <span class="hljs-number">-1</span>,
  <span class="hljs-number">0</span>,
  <span class="hljs-literal">Infinity</span>,
  -<span class="hljs-literal">Infinity</span>,
  [],
  [
    <span class="hljs-string">'e1'</span>,
    <span class="hljs-number">23</span>,
  ],
  {},
  {
    <span class="hljs-string">'prop1'</span>: <span class="hljs-literal">false</span>,
    <span class="hljs-string">'prop2'</span>: <span class="hljs-number">90</span>,
  },
  <span class="hljs-literal">NaN</span>,
];

jest.useFakeTimers();

describe(<span class="hljs-string">'Test filterNullish operator'</span>, <span class="hljs-function">() =&gt;</span> {
  it(<span class="hljs-string">'should filter nullish values'</span>, <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">from</span>(inputValues).pipe(
      filterNullish(),
      reduce(<span class="hljs-function">(<span class="hljs-params">acc, val</span>) =&gt;</span> {
        acc.push(val);

        <span class="hljs-keyword">return</span> acc;
      }, [] <span class="hljs-keyword">as</span> <span class="hljs-built_in">any</span>[]),
    ).subscribe(<span class="hljs-function"><span class="hljs-params">result</span> =&gt;</span> {
      expect(result).toEqual(expectedResult);
    })

    jest.advanceTimersToNextTimer();
  });
});
</code></pre>
<h1 id="heading-source-code">Source code</h1>
<p>https://gitlab.com/barcioch-blog-examples/010-null-nullish-nullable</p>
]]></content:encoded></item><item><title><![CDATA[Angular @Input and @Output testing]]></title><description><![CDATA[In this article, I'll show how to test Angular component with inputs and outputs.
Setup
If unsure how to set up Angular with Jest please refer to the article: https://barcioch.pro/angular-with-jest-setup
Component
Create a component with one input an...]]></description><link>https://blog.procode.pl/angular-input-and-output-testing</link><guid isPermaLink="true">https://blog.procode.pl/angular-input-and-output-testing</guid><category><![CDATA[Angular]]></category><category><![CDATA[Testing]]></category><category><![CDATA[input output]]></category><category><![CDATA[components]]></category><dc:creator><![CDATA[Bartosz Szłapak]]></dc:creator><pubDate>Mon, 18 Sep 2023 15:01:37 GMT</pubDate><content:encoded><![CDATA[<p>In this article, I'll show how to test Angular component with inputs and outputs.</p>
<h1 id="heading-setup">Setup</h1>
<p>If unsure how to set up Angular with Jest please refer to the article: https://barcioch.pro/angular-with-jest-setup</p>
<h1 id="heading-component">Component</h1>
<p>Create a component with one input and one output:</p>
<ul>
<li><p>the component takes a <code>title</code> value and displays it in <code>div</code></p>
</li>
<li><p>when user clicks <code>button</code> the value from the <code>title</code> field is emitted through <code>buttonClick</code> EventEmitter.</p>
</li>
</ul>
<pre><code class="lang-typescript"><span class="hljs-comment">// test.component.ts</span>

<span class="hljs-keyword">import</span> { Component, EventEmitter, Input, Output } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;

<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-test'</span>,
  templateUrl: <span class="hljs-string">'./test.component.html'</span>,
  styleUrls: [<span class="hljs-string">'./test.component.css'</span>]
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> TestComponent {
  <span class="hljs-meta">@Input</span>() title = <span class="hljs-string">''</span>;
  <span class="hljs-meta">@Output</span>() buttonClick = <span class="hljs-keyword">new</span> EventEmitter&lt;<span class="hljs-built_in">string</span>&gt;();

  click(): <span class="hljs-built_in">void</span> {
    <span class="hljs-built_in">this</span>.buttonClick.emit(<span class="hljs-built_in">this</span>.title);
  }
}
</code></pre>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- test.component.html --&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"title"</span>&gt;</span>{{ title }}<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">button</span>
    <span class="hljs-attr">type</span>=<span class="hljs-string">"button"</span>
    (<span class="hljs-attr">click</span>)=<span class="hljs-string">"click()"</span>
    <span class="hljs-attr">class</span>=<span class="hljs-string">"button"</span>&gt;</span>Button<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
</code></pre>
<h1 id="heading-test">Test</h1>
<p>To test a component I'll wrap it in the <code>wrapper</code> component, so it will work as used in the real application. It will also make it easy to pass the values and validate outputs.</p>
<pre><code class="lang-typescript"><span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-dummy'</span>,
  template: <span class="hljs-string">`&lt;app-test [title]="title" (buttonClick)="buttonClicked($event)"&gt;&lt;/app-test&gt;`</span>,
  styleUrls: [<span class="hljs-string">'./test.component.css'</span>]
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> DummyComponent {
  title?: <span class="hljs-built_in">string</span>;

  click(): <span class="hljs-built_in">void</span> {  }

  buttonClicked(value: <span class="hljs-built_in">string</span>): <span class="hljs-built_in">void</span> {  }
}
</code></pre>
<p>The test works as follows:</p>
<ul>
<li>declare all needed components</li>
</ul>
<pre><code class="lang-typescript">declarations: [
  TestComponent,
  DummyComponent,
],
</code></pre>
<ul>
<li>create the fixture and component reference</li>
</ul>
<pre><code class="lang-typescript">fixture = TestBed.createComponent(DummyComponent);
component = fixture.componentInstance;
</code></pre>
<ul>
<li>spy on <code>buttonClicked</code> method</li>
</ul>
<pre><code class="lang-typescript">jest.spyOn(component, <span class="hljs-string">'buttonClicked'</span>);
</code></pre>
<ul>
<li>test component default settings</li>
</ul>
<pre><code class="lang-typescript">describe(<span class="hljs-string">'when no title is passed '</span>, <span class="hljs-function">() =&gt;</span> {
  it(<span class="hljs-string">'should display empty title'</span>, <span class="hljs-function">() =&gt;</span> {
    expect(getTitle().nativeElement.innerHTML).toBe(<span class="hljs-string">''</span>);
  });

  describe(<span class="hljs-string">'and user clicks button'</span>, <span class="hljs-function">() =&gt;</span> {
    beforeEach(<span class="hljs-function">() =&gt;</span> {
      getButton().nativeElement.click();
      fixture.detectChanges();
    });

    it(<span class="hljs-string">'should emit empty string'</span>, <span class="hljs-function">() =&gt;</span> {
      expect(component.buttonClicked).toHaveBeenCalledTimes(<span class="hljs-number">1</span>);
      expect(component.buttonClicked).toHaveBeenCalledWith(<span class="hljs-string">''</span>);
    });
  });
});
</code></pre>
<p>Note that I'm clicking the <code>HTML Button</code> element</p>
<pre><code class="lang-typescript">getButton().nativeElement.click();
</code></pre>
<p>and validating the wrapper's method call</p>
<pre><code class="lang-typescript">expect(component.buttonClicked).toHaveBeenCalledTimes(<span class="hljs-number">1</span>);
expect(component.buttonClicked).toHaveBeenCalledWith(<span class="hljs-string">''</span>);
</code></pre>
<p>Test the passed value and output</p>
<pre><code class="lang-typescript">  describe(<span class="hljs-string">'when "my title" value is passed as "title"'</span>, <span class="hljs-function">() =&gt;</span> {
    beforeEach(<span class="hljs-function">() =&gt;</span> {
      component.title = <span class="hljs-string">'my title'</span>;
      fixture.detectChanges();
    });

    it(<span class="hljs-string">'should display "my title"'</span>, <span class="hljs-function">() =&gt;</span> {
      expect(getTitle().nativeElement.innerHTML).toBe(<span class="hljs-string">'my title'</span>);
    });

    describe(<span class="hljs-string">'and user clicks button'</span>, <span class="hljs-function">() =&gt;</span> {
      beforeEach(<span class="hljs-function">() =&gt;</span> {
        getButton().nativeElement.click();
        fixture.detectChanges();
      });

      it(<span class="hljs-string">'should emit empty string'</span>, <span class="hljs-function">() =&gt;</span> {
        expect(component.buttonClicked).toHaveBeenCalledTimes(<span class="hljs-number">1</span>);
        expect(component.buttonClicked).toHaveBeenCalledWith(<span class="hljs-string">'my title'</span>);
      });
    });
  });
</code></pre>
<p>Set value directly on the wrapper and run change detection</p>
<pre><code class="lang-typescript">component.title = <span class="hljs-string">'my title'</span>;
fixture.detectChanges();
</code></pre>
<p>The rest is similar to the previous test.</p>
<p>Full test:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { ComponentFixture, TestBed } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core/testing'</span>;
<span class="hljs-keyword">import</span> { TestComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">'./test.component'</span>;
<span class="hljs-keyword">import</span> { Component, DebugElement } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { By } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/platform-browser'</span>;

describe(<span class="hljs-string">'TestComponent'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">let</span> fixture: ComponentFixture&lt;DummyComponent&gt;;
  <span class="hljs-keyword">let</span> component: DummyComponent;

  beforeEach(<span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">await</span> TestBed.configureTestingModule({
      declarations: [
        TestComponent,
        DummyComponent,
      ],
    }).compileComponents();

    fixture = TestBed.createComponent(DummyComponent);
    component = fixture.componentInstance;
    jest.spyOn(component, <span class="hljs-string">'buttonClicked'</span>);
  });


  describe(<span class="hljs-string">'when no title is passed '</span>, <span class="hljs-function">() =&gt;</span> {
    it(<span class="hljs-string">'should display empty title'</span>, <span class="hljs-function">() =&gt;</span> {
      expect(getTitle().nativeElement.innerHTML).toBe(<span class="hljs-string">''</span>);
    });

    describe(<span class="hljs-string">'and user clicks button'</span>, <span class="hljs-function">() =&gt;</span> {
      beforeEach(<span class="hljs-function">() =&gt;</span> {
        getButton().nativeElement.click();
        fixture.detectChanges();
      });

      it(<span class="hljs-string">'should emit empty string'</span>, <span class="hljs-function">() =&gt;</span> {
        expect(component.buttonClicked).toHaveBeenCalledTimes(<span class="hljs-number">1</span>);
        expect(component.buttonClicked).toHaveBeenCalledWith(<span class="hljs-string">''</span>);
      });
    });
  });

  describe(<span class="hljs-string">'when "my title" value is passed as "title"'</span>, <span class="hljs-function">() =&gt;</span> {
    beforeEach(<span class="hljs-function">() =&gt;</span> {
      component.title = <span class="hljs-string">'my title'</span>;
      fixture.detectChanges();
    });

    it(<span class="hljs-string">'should display "my title"'</span>, <span class="hljs-function">() =&gt;</span> {
      expect(getTitle().nativeElement.innerHTML).toBe(<span class="hljs-string">'my title'</span>);
    });

    describe(<span class="hljs-string">'and user clicks button'</span>, <span class="hljs-function">() =&gt;</span> {
      beforeEach(<span class="hljs-function">() =&gt;</span> {
        getButton().nativeElement.click();
        fixture.detectChanges();
      });

      it(<span class="hljs-string">'should emit empty string'</span>, <span class="hljs-function">() =&gt;</span> {
        expect(component.buttonClicked).toHaveBeenCalledTimes(<span class="hljs-number">1</span>);
        expect(component.buttonClicked).toHaveBeenCalledWith(<span class="hljs-string">'my title'</span>);
      });
    });
  });

  <span class="hljs-keyword">const</span> getTitle = (): <span class="hljs-function"><span class="hljs-params">DebugElement</span> =&gt;</span> {
    <span class="hljs-keyword">return</span> fixture.debugElement.query(By.css(<span class="hljs-string">'.title'</span>));
  }

  <span class="hljs-keyword">const</span> getButton = (): <span class="hljs-function"><span class="hljs-params">DebugElement</span> =&gt;</span> {
    <span class="hljs-keyword">return</span> fixture.debugElement.query(By.css(<span class="hljs-string">'.button'</span>));
  }
});

<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-dummy'</span>,
  template: <span class="hljs-string">`
    &lt;app-test [title]="title" (buttonClick)="buttonClicked($event)"&gt;&lt;/app-test&gt;`</span>,
  styleUrls: [<span class="hljs-string">'./test.component.css'</span>]
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> DummyComponent {
  title: <span class="hljs-built_in">string</span> = <span class="hljs-string">''</span>;

  buttonClicked(value: <span class="hljs-built_in">string</span>): <span class="hljs-built_in">void</span> { }
}
</code></pre>
<h1 id="heading-source-code">Source code</h1>
<p>https://gitlab.com/barcioch-blog-examples/series-angular-testing-07-component-input-output-testing</p>
]]></content:encoded></item><item><title><![CDATA[Deep clone object - JSON.stringify/parse, fast-copy, structuredClone]]></title><description><![CDATA[Everyone, at a certain point in a developer's career, is going to search the internet for "how to make a deep copy of an object". I'll focus on "data objects" which can be defined by:

no methods, setters or getters, only properties

properties can b...]]></description><link>https://blog.procode.pl/deep-clone-object-jsonstringifyparse-fast-copy-structuredclone</link><guid isPermaLink="true">https://blog.procode.pl/deep-clone-object-jsonstringifyparse-fast-copy-structuredclone</guid><category><![CDATA[deep clone]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Deep copy]]></category><dc:creator><![CDATA[Bartosz Szłapak]]></dc:creator><pubDate>Sun, 17 Sep 2023 10:37:58 GMT</pubDate><content:encoded><![CDATA[<p>Everyone, at a certain point in a developer's career, is going to search the internet for "how to make a deep copy of an object". I'll focus on "data objects" which can be defined by:</p>
<ul>
<li><p>no methods, setters or getters, only properties</p>
</li>
<li><p>properties can be nested (arrays, objects)</p>
</li>
<li><p>all properties are "cloneable", i.e. they can't be <code>Error</code>, <code>Promise</code>, <code>Function</code>, <code>undefined</code> and some other types</p>
</li>
<li><p>no circular references</p>
</li>
</ul>
<p>I'll show three ways to do so. There are of course hundreds of other ways but these three should be more than enough for data objects. In the end, I'll write some tests in Jest to verify the objects' equality.</p>
<h1 id="heading-setup">Setup</h1>
<p>Requirements:</p>
<ul>
<li><p>node 17</p>
</li>
<li><p>npm 8</p>
</li>
<li><p>npx</p>
</li>
</ul>
<p>Initialize <strong>npm</strong> with default settings</p>
<pre><code class="lang-bash">npm init -y
</code></pre>
<p>Install dependencies</p>
<pre><code class="lang-bash">npm i jest@29 @types/jest@29 ts-jest@29 typescript@5 fast-copy
</code></pre>
<p>Initialize the default configuration of <strong>ts-jest</strong></p>
<pre><code class="lang-bash">npx ts-jest config:init
</code></pre>
<h1 id="heading-simple-examples">Simple examples</h1>
<h2 id="heading-jsonstringify-jsonparse">JSON.stringify, JSON.parse</h2>
<p>Documentation:</p>
<ul>
<li><p>https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify</p>
</li>
<li><p>https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse</p>
</li>
</ul>
<p>These methods do exactly what the names suggest. First, we call <code>JSON.stringify()</code> method that converts a value to plain JSON format (the returned value is a string). Then we call <code>JSON.parse()</code> method to parse the JSON string into a plain javascript object. In this process, all the references and class types are lost. In terms of speed that's the slowest solution and should only be used when performance is not a big deal.</p>
<p>Usage:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> variable = { myProp: [<span class="hljs-number">1</span>, <span class="hljs-string">'two'</span>] };
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'original:'</span>, variable);
<span class="hljs-keyword">const</span> clone = <span class="hljs-built_in">JSON</span>.parse(<span class="hljs-built_in">JSON</span>.stringify(variable));
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'cloned:  '</span>, clone);
</code></pre>
<h2 id="heading-fast-copy">fast-copy</h2>
<p>Documentation: https://github.com/planttheidea/fast-copy</p>
<p>This library has a few methods, but I'll focus on <code>copy()</code>. This method takes a single argument and returns a cloned value. One of the advantages this library has is that it tries to retain the source class type which the other two libraries don't.</p>
<p>Usage</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> copy <span class="hljs-keyword">from</span> <span class="hljs-string">'fast-copy'</span>;

<span class="hljs-keyword">const</span> variable = { myProp: [<span class="hljs-number">1</span>, <span class="hljs-string">'two'</span>] };
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'original:'</span>, variable);
<span class="hljs-keyword">const</span> clone = copy(variable)
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'cloned:  '</span>, clone);
</code></pre>
<h2 id="heading-structuredclone">structuredClone</h2>
<p>Documentation: https://developer.mozilla.org/en-US/docs/Web/API/structuredClone</p>
<p>This built-in function is available only in the newest browsers (https://caniuse.com/?search=structuredClone) and by default in Node 17+. Its usage is very simple: just call the function and pass a value to clone it.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> variable = { myProp: [<span class="hljs-number">1</span>, <span class="hljs-string">'two'</span>] };
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'original:'</span>, variable);
<span class="hljs-keyword">const</span> clone = structuredClone(variable)
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'cloned:  '</span>, clone);
</code></pre>
<h2 id="heading-the-result">The result</h2>
<p>All the previous examples were quite simple. Each time the result should be the same:</p>
<pre><code class="lang-text">original: { myProp: [ 1, 'two' ] }
cloned:   { myProp: [ 1, 'two' ] }
</code></pre>
<h1 id="heading-complex-example">Complex example</h1>
<p>Create an example to check multiple values</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// index.js </span>

<span class="hljs-keyword">import</span> copy <span class="hljs-keyword">from</span> <span class="hljs-string">'fast-copy'</span>;

<span class="hljs-keyword">class</span> TestClass {
  <span class="hljs-built_in">number</span>: <span class="hljs-built_in">number</span>;
  <span class="hljs-built_in">string</span>: <span class="hljs-built_in">string</span>;
  array: unknown[];
  <span class="hljs-built_in">object</span>: <span class="hljs-built_in">object</span>;
  <span class="hljs-keyword">class</span>: TestClass;
}

<span class="hljs-keyword">const</span> simpleObject = { propNumber: <span class="hljs-number">256</span>, propString: <span class="hljs-string">'a string prop'</span> };

<span class="hljs-keyword">const</span> otherTestClass = <span class="hljs-keyword">new</span> TestClass();
otherTestClass.array = [<span class="hljs-number">2</span>, <span class="hljs-string">'3'</span>, <span class="hljs-string">'five'</span>, [], simpleObject, <span class="hljs-literal">null</span>, <span class="hljs-literal">undefined</span>];
otherTestClass.string = <span class="hljs-string">'a new string'</span>;
otherTestClass.number = <span class="hljs-number">-88</span>;
otherTestClass.object = { prop2: <span class="hljs-string">'val2'</span> };

<span class="hljs-keyword">const</span> myTestClass = <span class="hljs-keyword">new</span> TestClass();
myTestClass.array = [<span class="hljs-number">1</span>, <span class="hljs-string">'2'</span>, <span class="hljs-string">'three'</span>, [], simpleObject, <span class="hljs-literal">null</span>, <span class="hljs-literal">undefined</span>];
myTestClass.string = <span class="hljs-string">'a string'</span>;
myTestClass.number = <span class="hljs-number">99</span>;
myTestClass.object = { prop: <span class="hljs-string">'val'</span> };
myTestClass.class = otherTestClass;

<span class="hljs-keyword">const</span> values = [
  { key: <span class="hljs-string">'null'</span>, value: <span class="hljs-literal">null</span> },
  { key: <span class="hljs-string">'false'</span>, value: <span class="hljs-literal">false</span> },
  { key: <span class="hljs-string">'true'</span>, value: <span class="hljs-literal">true</span> },
  { key: <span class="hljs-string">'0'</span>, value: <span class="hljs-number">0</span> },
  { key: <span class="hljs-string">'-10'</span>, value: <span class="hljs-number">-10</span> },
  { key: <span class="hljs-string">'99'</span>, value: <span class="hljs-number">99</span> },
  { key: <span class="hljs-string">'0.1'</span>, value: <span class="hljs-number">0.1</span> },
  { key: <span class="hljs-string">'-1.0009'</span>, value: <span class="hljs-number">-1.0009</span> },
  { key: <span class="hljs-string">'Infinity'</span>, value: <span class="hljs-literal">Infinity</span> },
  { key: <span class="hljs-string">'\'\''</span>, value: <span class="hljs-string">''</span> },
  { key: <span class="hljs-string">'a string'</span>, value: <span class="hljs-string">'a string'</span> },
  { key: <span class="hljs-string">'[]'</span>, value: [] },
  { key: <span class="hljs-string">'[1,\'2\',\'three\', [], {prop: \'val\'}]'</span>, value: [<span class="hljs-number">1</span>, <span class="hljs-string">'2'</span>, <span class="hljs-string">'three'</span>, [], { prop: <span class="hljs-string">'val'</span> }] },
  { key: <span class="hljs-string">'{}'</span>, value: {} },
  { key: <span class="hljs-string">'myTestClass'</span>, value: myTestClass }
]

<span class="hljs-keyword">class</span> Result {

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params">input: <span class="hljs-built_in">any</span>, jsonStringifyParse: <span class="hljs-built_in">any</span>, fastCopy: <span class="hljs-built_in">any</span>, structuredClone: <span class="hljs-built_in">any</span></span>) {
    <span class="hljs-built_in">this</span>.input = input;
    <span class="hljs-built_in">this</span>.jsonStringifyParse = jsonStringifyParse;
    <span class="hljs-built_in">this</span>.fastCopy = fastCopy;
    <span class="hljs-built_in">this</span>.structuredClone = structuredClone;
  }

  input: <span class="hljs-built_in">any</span>;
  jsonStringifyParse: <span class="hljs-built_in">any</span>;
  fastCopy: <span class="hljs-built_in">any</span>;
  structuredClone: <span class="hljs-built_in">any</span>;
}

<span class="hljs-keyword">const</span> group = {};

values.forEach(<span class="hljs-function"><span class="hljs-params">value</span> =&gt;</span> {
  group[value.key] = <span class="hljs-keyword">new</span> Result(
    value.value,
    <span class="hljs-built_in">JSON</span>.parse(<span class="hljs-built_in">JSON</span>.stringify(value.value)),
    copy(value.value),
    structuredClone(value.value)
  );
})

<span class="hljs-built_in">console</span>.table(group, [<span class="hljs-string">'jsonStringifyParse'</span>, <span class="hljs-string">'fastCopy'</span>, <span class="hljs-string">'structuredClone'</span>])
<span class="hljs-built_in">console</span>.dir(group, {depth: <span class="hljs-literal">null</span>})
</code></pre>
<p>Run it</p>
<pre><code class="lang-shell">npx tsc index.ts &amp;&amp; node index.js
</code></pre>
<p>The output of <code>console.table()</code></p>
<pre><code class="lang-shell">┌────────────────────────────────────┬───────────────────────────────────────┬───────────────────────────────────────┬───────────────────────────────────────┐
│              (index)               │          jsonStringifyParse           │               fastCopy                │            structuredClone            │
├────────────────────────────────────┼───────────────────────────────────────┼───────────────────────────────────────┼───────────────────────────────────────┤
│                 0                  │                   0                   │                   0                   │                   0                   │
│                 99                 │                  99                   │                  99                   │                  99                   │
│                null                │                 null                  │                 null                  │                 null                  │
│               false                │                 false                 │                 false                 │                 false                 │
│                true                │                 true                  │                 true                  │                 true                  │
│                -10                 │                  -10                  │                  -10                  │                  -10                  │
│                0.1                 │                  0.1                  │                  0.1                  │                  0.1                  │
│              -1.0009               │                -1.0009                │                -1.0009                │                -1.0009                │
│              Infinity              │                 null                  │               Infinity                │               Infinity                │
│                 ''                 │                  ''                   │                  ''                   │                  ''                   │
│              a string              │              'a string'               │              'a string'               │              'a string'               │
│                 []                 │                  []                   │                  []                   │                  []                   │
│ [1,'2','three', [], {prop: 'val'}] │ [ 1, '2', 'three', ... 2 more items ] │ [ 1, '2', 'three', ... 2 more items ] │ [ 1, '2', 'three', ... 2 more items ] │
│                 {}                 │                  {}                   │                  {}                   │                  {}                   │
│            myTestClass             │               [Object]                │              [TestClass]              │               [Object]                │
└────────────────────────────────────┴───────────────────────────────────────┴───────────────────────────────────────┴───────────────────────────────────────┘
</code></pre>
<p>A quick look can tell if the values match and where the class types were retained. Since <code>console.table()</code> does not expand objects, the <code>console.dir()</code> can be used. To expand variables without the nesting limit pass <code>{depth: null}</code> as the second argument.</p>
<p>The output of <code>console.dir()</code></p>
<pre><code class="lang-shell">{
  '0': Result {
    input: 0,
    jsonStringifyParse: 0,
    fastCopy: 0,
    structuredClone: 0
  },
  '99': Result {
    input: 99,
    jsonStringifyParse: 99,
    fastCopy: 99,
    structuredClone: 99
  },
  null: Result {
    input: null,
    jsonStringifyParse: null,
    fastCopy: null,
    structuredClone: null
  },
  false: Result {
    input: false,
    jsonStringifyParse: false,
    fastCopy: false,
    structuredClone: false
  },
  true: Result {
    input: true,
    jsonStringifyParse: true,
    fastCopy: true,
    structuredClone: true
  },
  '-10': Result {
    input: -10,
    jsonStringifyParse: -10,
    fastCopy: -10,
    structuredClone: -10
  },
  '0.1': Result {
    input: 0.1,
    jsonStringifyParse: 0.1,
    fastCopy: 0.1,
    structuredClone: 0.1
  },
  '-1.0009': Result {
    input: -1.0009,
    jsonStringifyParse: -1.0009,
    fastCopy: -1.0009,
    structuredClone: -1.0009
  },
  Infinity: Result {
    input: Infinity,
    jsonStringifyParse: null,
    fastCopy: Infinity,
    structuredClone: Infinity
  },
  "''": Result {
    input: '',
    jsonStringifyParse: '',
    fastCopy: '',
    structuredClone: ''
  },
  'a string': Result {
    input: 'a string',
    jsonStringifyParse: 'a string',
    fastCopy: 'a string',
    structuredClone: 'a string'
  },
  '[]': Result {
    input: [],
    jsonStringifyParse: [],
    fastCopy: [],
    structuredClone: []
  },
  "[1,'2','three', [], {prop: 'val'}]": Result {
    input: [ 1, '2', 'three', [], { prop: 'val' } ],
    jsonStringifyParse: [ 1, '2', 'three', [], { prop: 'val' } ],
    fastCopy: [ 1, '2', 'three', [], { prop: 'val' } ],
    structuredClone: [ 1, '2', 'three', [], { prop: 'val' } ]
  },
  '{}': Result {
    input: {},
    jsonStringifyParse: {},
    fastCopy: {},
    structuredClone: {}
  },
  myTestClass: Result {
    input: TestClass {
      array: [
        1,
        '2',
        'three',
        [],
        { propNumber: 256, propString: 'a string prop' },
        null,
        undefined
      ],
      string: 'a string',
      number: 99,
      object: { prop: 'val' },
      class: TestClass {
        array: [
          2,
          '3',
          'five',
          [],
          { propNumber: 256, propString: 'a string prop' },
          null,
          undefined
        ],
        string: 'a new string',
        number: -88,
        object: { prop2: 'val2' }
      }
    },
    jsonStringifyParse: {
      array: [
        1,
        '2',
        'three',
        [],
        { propNumber: 256, propString: 'a string prop' },
        null,
        null
      ],
      string: 'a string',
      number: 99,
      object: { prop: 'val' },
      class: {
        array: [
          2,
          '3',
          'five',
          [],
          { propNumber: 256, propString: 'a string prop' },
          null,
          null
        ],
        string: 'a new string',
        number: -88,
        object: { prop2: 'val2' }
      }
    },
    fastCopy: TestClass {
      array: [
        1,
        '2',
        'three',
        [],
        { propNumber: 256, propString: 'a string prop' },
        null,
        undefined
      ],
      string: 'a string',
      number: 99,
      object: { prop: 'val' },
      class: TestClass {
        array: [
          2,
          '3',
          'five',
          [],
          { propNumber: 256, propString: 'a string prop' },
          null,
          undefined
        ],
        string: 'a new string',
        number: -88,
        object: { prop2: 'val2' }
      }
    },
    structuredClone: {
      array: [
        1,
        '2',
        'three',
        [],
        { propNumber: 256, propString: 'a string prop' },
        null,
        undefined
      ],
      string: 'a string',
      number: 99,
      object: { prop: 'val' },
      class: {
        array: [
          2,
          '3',
          'five',
          [],
          { propNumber: 256, propString: 'a string prop' },
          null,
          undefined
        ],
        string: 'a new string',
        number: -88,
        object: { prop2: 'val2' }
      }
    }
  }
}
</code></pre>
<p>Now, you can visually compare all the outputs and tell if that's the desired output.</p>
<h1 id="heading-testing">Testing</h1>
<p>Nevertheless, a solid test case is surely needed. I'll use the same input data.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// index.spec.ts</span>

<span class="hljs-keyword">import</span> copy <span class="hljs-keyword">from</span> <span class="hljs-string">'fast-copy'</span>;

<span class="hljs-keyword">class</span> TestClass {
  <span class="hljs-built_in">number</span>: <span class="hljs-built_in">number</span>;
  <span class="hljs-built_in">string</span>: <span class="hljs-built_in">string</span>;
  array: unknown[];
  <span class="hljs-built_in">object</span>: <span class="hljs-built_in">object</span>;
  <span class="hljs-keyword">class</span>: TestClass;
}

<span class="hljs-keyword">const</span> simpleObject = { propNumber: <span class="hljs-number">256</span>, propString: <span class="hljs-string">'a string prop'</span> };

<span class="hljs-keyword">const</span> otherTestClass = <span class="hljs-keyword">new</span> TestClass();
otherTestClass.array = [<span class="hljs-number">2</span>, <span class="hljs-string">'3'</span>, <span class="hljs-string">'five'</span>, [], simpleObject, <span class="hljs-literal">null</span>, <span class="hljs-literal">undefined</span>];
otherTestClass.string = <span class="hljs-string">'a new string'</span>;
otherTestClass.number = <span class="hljs-number">-88</span>;
otherTestClass.object = { prop2: <span class="hljs-string">'val2'</span> };

<span class="hljs-keyword">const</span> myTestClass = <span class="hljs-keyword">new</span> TestClass();
myTestClass.array = [<span class="hljs-number">1</span>, <span class="hljs-string">'2'</span>, <span class="hljs-string">'three'</span>, [], simpleObject, <span class="hljs-literal">null</span>, <span class="hljs-literal">undefined</span>];
myTestClass.string = <span class="hljs-string">'a string'</span>;
myTestClass.number = <span class="hljs-number">99</span>;
myTestClass.object = { prop: <span class="hljs-string">'val'</span> };
myTestClass.class = otherTestClass;

<span class="hljs-keyword">const</span> values = [
  { key: <span class="hljs-string">'null'</span>, value: <span class="hljs-literal">null</span> },
  { key: <span class="hljs-string">'false'</span>, value: <span class="hljs-literal">false</span> },
  { key: <span class="hljs-string">'true'</span>, value: <span class="hljs-literal">true</span> },
  { key: <span class="hljs-string">'0'</span>, value: <span class="hljs-number">0</span> },
  { key: <span class="hljs-string">'-10'</span>, value: <span class="hljs-number">-10</span> },
  { key: <span class="hljs-string">'99'</span>, value: <span class="hljs-number">99</span> },
  { key: <span class="hljs-string">'0.1'</span>, value: <span class="hljs-number">0.1</span> },
  { key: <span class="hljs-string">'-1.0009'</span>, value: <span class="hljs-number">-1.0009</span> },
  { key: <span class="hljs-string">'Infinity'</span>, value: <span class="hljs-literal">Infinity</span> },
  { key: <span class="hljs-string">'\'\''</span>, value: <span class="hljs-string">''</span> },
  { key: <span class="hljs-string">'a string'</span>, value: <span class="hljs-string">'a string'</span> },
  { key: <span class="hljs-string">'[]'</span>, value: [] },
  { key: <span class="hljs-string">'[1,\'2\',\'three\', [], {prop: \'val\'}]'</span>, value: [<span class="hljs-number">1</span>, <span class="hljs-string">'2'</span>, <span class="hljs-string">'three'</span>, [], { prop: <span class="hljs-string">'val'</span> }] },
  { key: <span class="hljs-string">'{}'</span>, value: {} },
  { key: <span class="hljs-string">'myTestClass'</span>, value: myTestClass }
]

<span class="hljs-keyword">const</span> jsonStringifyParseDatasets = values.map(<span class="hljs-function"><span class="hljs-params">value</span> =&gt;</span> {
  <span class="hljs-keyword">return</span> {
    key: value.key,
    input: value.value,
    result: <span class="hljs-built_in">JSON</span>.parse(<span class="hljs-built_in">JSON</span>.stringify(value.value))
  };
});

<span class="hljs-keyword">const</span> fastCopyDatasets = values.map(<span class="hljs-function"><span class="hljs-params">value</span> =&gt;</span> {
  <span class="hljs-keyword">return</span> {
    key: value.key,
    input: value.value,
    result: copy(value.value)
  };
});


<span class="hljs-keyword">const</span> structuredCloneDatasets = values.map(<span class="hljs-function"><span class="hljs-params">value</span> =&gt;</span> {
  <span class="hljs-keyword">return</span> {
    key: value.key,
    input: value.value,
    result: structuredClone(value.value)
  };
});

describe(<span class="hljs-string">'jsonStringifyParse'</span>, <span class="hljs-function">() =&gt;</span> {
  it.each(jsonStringifyParseDatasets)(<span class="hljs-string">'value: $key'</span>, <span class="hljs-function">(<span class="hljs-params">{key, input, result}</span>) =&gt;</span> {
    expect(result).toEqual(input)
  });
});

describe(<span class="hljs-string">'fastCopy'</span>, <span class="hljs-function">() =&gt;</span> {
  it.each(fastCopyDatasets)(<span class="hljs-string">'value: $key'</span>, <span class="hljs-function">(<span class="hljs-params">{key, input, result}</span>) =&gt;</span> {
    expect(result).toEqual(input)
  });
});

describe(<span class="hljs-string">'structuredClone'</span>, <span class="hljs-function">() =&gt;</span> {
  it.each(structuredCloneDatasets)(<span class="hljs-string">'value: $key'</span>, <span class="hljs-function">(<span class="hljs-params">{key, input, result}</span>) =&gt;</span> {
    expect(result).toEqual(input)
  });
});
</code></pre>
<p>Run it</p>
<pre><code class="lang-bash">npx jest index.spec.ts
</code></pre>
<p>The result is</p>
<pre><code class="lang-plaintext"> FAIL  ./index.spec.ts
  jsonStringifyParse
    ✓ value: null (2 ms)
    ✓ value: false
    ✓ value: true (1 ms)
    ✓ value: 0
    ✓ value: -10
    ✓ value: 99
    ✓ value: 0.1
    ✓ value: -1.0009 (1 ms)
    ✕ value: Infinity (1 ms)
    ✓ value: '' (1 ms)
    ✓ value: a string
    ✓ value: [] (1 ms)
    ✓ value: [1,'2','three', [], {prop: 'val'}]
    ✓ value: {} (1 ms)
    ✕ value: myTestClass (5 ms)
  fastCopy
    ✓ value: null
    ✓ value: false (1 ms)
    ✓ value: true
    ✓ value: 0
    ✓ value: -10
    ✓ value: 99
    ✓ value: 0.1
    ✓ value: -1.0009
    ✓ value: Infinity
    ✓ value: '' (1 ms)
    ✓ value: a string
    ✓ value: []
    ✓ value: [1,'2','three', [], {prop: 'val'}]
    ✓ value: {} (1 ms)
    ✓ value: myTestClass
  structuredClone
    ✓ value: null
    ✓ value: false (1 ms)
    ✓ value: true
    ✓ value: 0
    ✓ value: -10
    ✓ value: 99
    ✓ value: 0.1
    ✓ value: -1.0009
    ✓ value: Infinity
    ✓ value: ''
    ✓ value: a string (1 ms)
    ✓ value: []
    ✓ value: [1,'2','three', [], {prop: 'val'}]
    ✓ value: {}
    ✓ value: myTestClass (1 ms)

  ● jsonStringifyParse › value: Infinity

    expect(received).toEqual(expected) // deep equality

    Expected: Infinity
    Received: null

      69 | describe('jsonStringifyParse', () =&gt; {
      70 |   it.each(jsonStringifyParseDatasets)('value: $key', ({key, input, result}) =&gt; {
    &gt; 71 |     expect(result).toEqual(input)
         |                    ^
      72 |   });
      73 | });
      74 |

      at index.spec.ts:71:20

  ● jsonStringifyParse › value: myTestClass

    expect(received).toEqual(expected) // deep equality

    - Expected  - 4
    + Received  + 4

    @@ -1,30 +1,30 @@
    - TestClass {
    + Object {
        "array": Array [
          1,
          "2",
          "three",
          Array [],
          Object {
            "propNumber": 256,
            "propString": "a string prop",
          },
    +     null,
          null,
    -     undefined,
        ],
    -   "class": TestClass {
    +   "class": Object {
          "array": Array [
            2,
            "3",
            "five",
            Array [],
            Object {
              "propNumber": 256,
              "propString": "a string prop",
            },
            null,
    -       undefined,
    +       null,
          ],
          "number": -88,
          "object": Object {
            "prop2": "val2",
          },

      69 | describe('jsonStringifyParse', () =&gt; {
      70 |   it.each(jsonStringifyParseDatasets)('value: $key', ({key, input, result}) =&gt; {
    &gt; 71 |     expect(result).toEqual(input)
         |                    ^
      72 |   });
      73 | });
      74 |

      at index.spec.ts:71:20

Test Suites: 1 failed, 1 total
Tests:       2 failed, 43 passed, 45 total
</code></pre>
<p>As you can see the 2 tests failed while using <code>json stringify / parse</code> method:</p>
<ul>
<li><p>the <code>undefined</code> value was converted to null</p>
</li>
<li><p>the <code>Infinity</code> value was converted to null</p>
</li>
<li><p>the <code>TestClass</code> type was converted back to <code>Object</code> type</p>
</li>
</ul>
<p>These errors are caused due to the lack of support of these types by JSON format.</p>
]]></content:encoded></item><item><title><![CDATA[Angular - custom *ngIf directive]]></title><description><![CDATA[In this article, I'll show how to create a custom structural directive that works similarly to Angular's ngIf. The goal is to create a directive that checks if a current user has required permissions by passing them to the directive. The directive sh...]]></description><link>https://blog.procode.pl/angular-custom-ngif-directive</link><guid isPermaLink="true">https://blog.procode.pl/angular-custom-ngif-directive</guid><category><![CDATA[Angular]]></category><category><![CDATA[directive]]></category><category><![CDATA[custom]]></category><category><![CDATA[ngIf]]></category><dc:creator><![CDATA[Bartosz Szłapak]]></dc:creator><pubDate>Thu, 14 Sep 2023 15:57:53 GMT</pubDate><content:encoded><![CDATA[<p>In this article, I'll show how to create a custom structural directive that works similarly to Angular's <code>ngIf</code>. The goal is to create a directive that checks if a current user has required permissions by passing them to the directive. The directive should also allow passing <code>else</code> template reference whenever the directive condition fails.</p>
<h1 id="heading-setup">Setup</h1>
<p>If unsure how to set up Angular with Jest please refer to the article: https://barcioch.pro/angular-with-jest-setup</p>
<h1 id="heading-permissions">Permissions</h1>
<p>Define available permissions in the <code>Permission</code> enum.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// permission.enum.ts</span>

<span class="hljs-keyword">export</span> <span class="hljs-built_in">enum</span> Permission {
  login = <span class="hljs-string">'login'</span>,
  dashboard = <span class="hljs-string">'dashboard'</span>,
  orders = <span class="hljs-string">'orders'</span>,
  users = <span class="hljs-string">'users'</span>,
  admin = <span class="hljs-string">'admin'</span>,
}
</code></pre>
<p>Create a simple service for permission validation. The service takes as a first argument in the constructor the current users' permissions.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// permission.service.ts</span>

<span class="hljs-keyword">import</span> { Injectable } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { Permission } <span class="hljs-keyword">from</span> <span class="hljs-string">'./permission.enum'</span>;

<span class="hljs-meta">@Injectable</span>()
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> PermissionService {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> permissions: Permission[]</span>) {
  }

  hasPermission(permission: Permission): <span class="hljs-built_in">boolean</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.permissions.includes(permission);
  }
}
</code></pre>
<h1 id="heading-directive">Directive</h1>
<p>Start with creating an empty directive with required dependencies.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// has-permission.directive.ts</span>

<span class="hljs-keyword">import</span> { Directive, TemplateRef, ViewContainerRef } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { PermissionService } <span class="hljs-keyword">from</span> <span class="hljs-string">'./permission.service'</span>;

<span class="hljs-meta">@Directive</span>({
  selector: <span class="hljs-string">'[hasPermission]'</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> HasPermissionDirective {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params">
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> viewContainer: ViewContainerRef,
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> templateRef: TemplateRef&lt;unknown&gt;,
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> permissionService: PermissionService
  </span>) {
  }
}
</code></pre>
<p>There are 3 dependencies required for this directive to work:</p>
<ul>
<li><p><code>ViewContainerRef</code> - a reference to the parent view</p>
</li>
<li><p><code>TemplateRef&lt;unknown&gt;</code> - a reference to the element's view that the directive is connected to</p>
</li>
<li><p><code>PermissionService</code> - service for checking users' permissions</p>
</li>
</ul>
<p>The user needs to pass permission directly to the directive in the following manner:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// component</span>
...
<span class="hljs-keyword">readonly</span> permissions = Permission;
...
</code></pre>
<pre><code class="lang-xml"><span class="hljs-comment">&lt;!-- view --&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">div</span> *<span class="hljs-attr">hasPermission</span>=<span class="hljs-string">"permissions.login"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<p>To handle directive inputs use <code>@Input()</code> decorator. The input methods should be prefixed with directive selector (<code>hasPermission</code> in this case). The default input should be named exactly as the directive selector.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">private</span> hasCurrentPermission = <span class="hljs-literal">false</span>;

<span class="hljs-meta">@Input</span>() set hasPermission(permission: Permission) {
  <span class="hljs-built_in">this</span>.hasCurrentPermission = <span class="hljs-built_in">this</span>.permissionService.hasPermission(permission);
  <span class="hljs-built_in">this</span>.displayTemplate();
}

<span class="hljs-keyword">private</span> displayTemplate(): <span class="hljs-built_in">void</span> {
  <span class="hljs-built_in">this</span>.viewContainer.clear();

  <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.hasCurrentPermission) {
    <span class="hljs-built_in">this</span>.viewContainer.createEmbeddedView(<span class="hljs-built_in">this</span>.templateRef);
  }
}
</code></pre>
<p>I created a setter <code>hasPermission</code>. It uses the <code>PermissionService</code> to check the permission and stores the result in the private property <code>hasCurrentPermission</code>. Next, the <code>displayTemplate</code> method is called. First, it clears the current view (removes the content) <code>this.viewContainer.clear()</code>. Then, it checks the <code>hasCurrentPermission</code> property and if it's <code>true</code> then it renders the element that the directive is attached to <code>this.viewContainer.createEmbeddedView(this.templateRef)</code>.</p>
<p>Next, let's support the <code>else</code> keyword. The <code>else</code> should point to the <code>ng-template</code> reference.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> *<span class="hljs-attr">hasPermission</span>=<span class="hljs-string">"permissions.login; else noPermission"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">ng-template</span> #<span class="hljs-attr">noPermission</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>Access denied<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ng-template</span>&gt;</span>
</code></pre>
<p>To handle the <code>else</code> functionality we need to add the new input called <code>hasPermissionElse</code>. It's argument's type is the <code>TemplateRef</code> because we will be passing the template reference. Note, that the name consists of two parts:</p>
<ul>
<li><p>prefix: directive selector <code>hasPermission</code></p>
</li>
<li><p>suffix: the name used in the view <code>else</code></p>
</li>
</ul>
<pre><code class="lang-typescript"><span class="hljs-keyword">private</span> elseTemplateRef: TemplateRef&lt;unknown&gt;;

<span class="hljs-meta">@Input</span>() set hasPermissionElse(templateRef: TemplateRef&lt;unknown&gt;) {
  <span class="hljs-built_in">this</span>.elseTemplateRef = templateRef;
  <span class="hljs-built_in">this</span>.displayTemplate();
}

<span class="hljs-keyword">private</span> displayTemplate(): <span class="hljs-built_in">void</span> {
  <span class="hljs-built_in">this</span>.viewContainer.clear();

  <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.hasCurrentPermission) {
    <span class="hljs-built_in">this</span>.viewContainer.createEmbeddedView(<span class="hljs-built_in">this</span>.templateRef);
    <span class="hljs-keyword">return</span>;
  }

  <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.elseTemplateRef) {
    <span class="hljs-built_in">this</span>.viewContainer.createEmbeddedView(<span class="hljs-built_in">this</span>.elseTemplateRef);
  }
}
</code></pre>
<p>The <code>hasPermissionElse</code> setter takes and stores the <code>else</code> template reference and then it calls <code>displayTemplate()</code> method. The <code>displayTemplate</code> method has the following modifications:</p>
<ul>
<li><p>added <code>return</code> to <code>this.hasCurrentPermission</code> condition check</p>
</li>
<li><p>added <code>this.elseTemplateRef</code> condition check. If the user has no permission and the <code>else</code> template reference was passed we render this template</p>
</li>
</ul>
<p>The whole directive looks as follows:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// has-permission.directive.ts</span>

<span class="hljs-keyword">import</span> { Directive, Input, TemplateRef, ViewContainerRef } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { PermissionService } <span class="hljs-keyword">from</span> <span class="hljs-string">'./permission.service'</span>;
<span class="hljs-keyword">import</span> { Permission } <span class="hljs-keyword">from</span> <span class="hljs-string">'./permission.enum'</span>;

<span class="hljs-meta">@Directive</span>({
  selector: <span class="hljs-string">'[hasPermission]'</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> HasPermissionDirective {
  <span class="hljs-keyword">private</span> elseTemplateRef: TemplateRef&lt;unknown&gt;;
  <span class="hljs-keyword">private</span> hasCurrentPermission = <span class="hljs-literal">false</span>;

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params">
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> viewContainer: ViewContainerRef,
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> templateRef: TemplateRef&lt;unknown&gt;,
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> permissionService: PermissionService
  </span>) {
  }

  <span class="hljs-meta">@Input</span>() set hasPermissionElse(templateRef: TemplateRef&lt;unknown&gt;) {
    <span class="hljs-built_in">this</span>.elseTemplateRef = templateRef;
    <span class="hljs-built_in">this</span>.displayTemplate();
  }

  <span class="hljs-meta">@Input</span>() set hasPermission(permission: Permission) {
    <span class="hljs-built_in">this</span>.hasCurrentPermission = <span class="hljs-built_in">this</span>.permissionService.hasPermission(permission);
    <span class="hljs-built_in">this</span>.displayTemplate();
  }

  <span class="hljs-keyword">private</span> displayTemplate(): <span class="hljs-built_in">void</span> {
    <span class="hljs-built_in">this</span>.viewContainer.clear();

    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.hasCurrentPermission) {
      <span class="hljs-built_in">this</span>.viewContainer.createEmbeddedView(<span class="hljs-built_in">this</span>.templateRef);
      <span class="hljs-keyword">return</span>;
    }

    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.elseTemplateRef) {
      <span class="hljs-built_in">this</span>.viewContainer.createEmbeddedView(<span class="hljs-built_in">this</span>.elseTemplateRef);
    }
  }
}
</code></pre>
<h1 id="heading-testing">Testing</h1>
<p>A simple test that checks all possible combinations of directive inputs.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { ComponentFixture, TestBed } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core/testing'</span>;
<span class="hljs-keyword">import</span> { Component } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { Permission } <span class="hljs-keyword">from</span> <span class="hljs-string">'./permission.enum'</span>;
<span class="hljs-keyword">import</span> { CommonModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/common'</span>;
<span class="hljs-keyword">import</span> { HasPermissionModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'./has-permission.module'</span>;
<span class="hljs-keyword">import</span> { PermissionService } <span class="hljs-keyword">from</span> <span class="hljs-string">'./permission.service'</span>;
<span class="hljs-keyword">import</span> { By } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/platform-browser'</span>;

describe(<span class="hljs-string">'HasPermissionDirective'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">let</span> fixture: ComponentFixture&lt;TestComponent&gt;;

  beforeEach(<span class="hljs-function">() =&gt;</span> {
    TestBed.configureTestingModule({
      imports: [
        CommonModule,
        HasPermissionModule
      ],
      declarations: [TestComponent],
      providers: [
        {
          provide: PermissionService,
          useValue: <span class="hljs-keyword">new</span> PermissionService([Permission.dashboard, Permission.users])
        }
      ]
    });

    fixture = TestBed.createComponent(TestComponent);
    fixture.detectChanges()
  });

  describe(<span class="hljs-string">'when the test component is initialized'</span>, <span class="hljs-function">() =&gt;</span> {
    describe(<span class="hljs-string">'and user has set "dashboard" and "users" permissions'</span>, <span class="hljs-function">() =&gt;</span> {
      it(<span class="hljs-string">'should not display "orders" element'</span>, <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">const</span> element = fixture.debugElement.query(By.css(<span class="hljs-string">'#orders'</span>));
        expect(element).toBeFalsy();
      });

      it(<span class="hljs-string">'should display "dashboard" element'</span>, <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">const</span> element = fixture.debugElement.query(By.css(<span class="hljs-string">'#dashboard'</span>));
        expect(element).toBeTruthy();
      });

      it(<span class="hljs-string">'should not display "admin" element'</span>, <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">const</span> element = fixture.debugElement.query(By.css(<span class="hljs-string">'#admin'</span>));
        expect(element).toBeFalsy();
      });

      it(<span class="hljs-string">'should display "no-access-admin" element'</span>, <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">const</span> element = fixture.debugElement.query(By.css(<span class="hljs-string">'#no-access-admin'</span>));
        expect(element).toBeTruthy();
      });

      it(<span class="hljs-string">'should display "no-access-dashboard" element'</span>, <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">const</span> element = fixture.debugElement.query(By.css(<span class="hljs-string">'#admin'</span>));
        expect(element).toBeFalsy();
      });


      it(<span class="hljs-string">'should display "users" element'</span>, <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">const</span> element = fixture.debugElement.query(By.css(<span class="hljs-string">'#users'</span>));
        expect(element).toBeTruthy();
      });

      it(<span class="hljs-string">'should display "no-access-users" element'</span>, <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">const</span> element = fixture.debugElement.query(By.css(<span class="hljs-string">'#no-access-users'</span>));
        expect(element).toBeFalsy();
      });

    });
  });
});


<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-test'</span>,
  template: <span class="hljs-string">`
    &lt;div id="orders" *hasPermission="permissions.orders"&gt;&lt;/div&gt;

    &lt;div id="dashboard" *hasPermission="permissions.dashboard"&gt;&lt;/div&gt;

    &lt;div id="admin" *hasPermission="permissions.admin; else noAccessAdmin"&gt;&lt;/div&gt;

    &lt;ng-template #noAccessAdmin&gt;
      &lt;div id="no-access-admin"&gt;&lt;/div&gt;
    &lt;/ng-template&gt;

    &lt;div id="users" *hasPermission="permissions.users; else noAccessUsers"&gt;&lt;/div&gt;

    &lt;ng-template #noAccessUsers&gt;
      &lt;div id="no-access-users"&gt;&lt;/div&gt;
    &lt;/ng-template&gt;
  `</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> TestComponent {
  <span class="hljs-keyword">readonly</span> permissions = Permission;
}
</code></pre>
<p>Run jest</p>
<pre><code class="lang-bash">npx jest
</code></pre>
<p>and you should see all tests passed</p>
<pre><code class="lang-text">PASS  src/app/permissions/has-permission.spec.ts
  HasPermissionDirective
    when the test component is initialized
      and user has set "dashboard" and "users" permissions
        ✓ should not display "orders" element (52 ms)
        ✓ should display "dashboard" element (8 ms)
        ✓ should not display "admin" element (6 ms)
        ✓ should display "no-access-admin" element (6 ms)
        ✓ should display "no-access-dashboard" element (5 ms)
        ✓ should display "users" element (5 ms)
        ✓ should display "no-access-users" element (4 ms)

Test Suites: 1 passed, 1 total
Tests:       7 passed, 7 total
Snapshots:   0 total
Time:        0.836 s, estimated 2 s
</code></pre>
<h1 id="heading-source-code">Source code</h1>
<p>https://gitlab.com/barcioch-blog-examples/008-angular-custom-ngif</p>
]]></content:encoded></item><item><title><![CDATA[Angular directive testing]]></title><description><![CDATA[In this article, I'll show how to test Angular directive. To test it I'll create appCloneElement directive. Its purpose is to duplicate HTML elements given n times.
Setup
If unsure how to set up Angular with Jest please refer to the article: https://...]]></description><link>https://blog.procode.pl/angular-directive-testing</link><guid isPermaLink="true">https://blog.procode.pl/angular-directive-testing</guid><category><![CDATA[Angular]]></category><category><![CDATA[Testing]]></category><category><![CDATA[directive]]></category><category><![CDATA[Jest]]></category><category><![CDATA[TypeScript]]></category><dc:creator><![CDATA[Bartosz Szłapak]]></dc:creator><pubDate>Sun, 16 Apr 2023 10:01:34 GMT</pubDate><content:encoded><![CDATA[<p>In this article, I'll show how to test Angular directive. To test it I'll create <code>appCloneElement</code> directive. Its purpose is to duplicate HTML elements given <code>n</code> times.</p>
<h1 id="heading-setup">Setup</h1>
<p>If unsure how to set up Angular with Jest please refer to the article: https://barcioch.pro/angular-with-jest-setup</p>
<h1 id="heading-clone-element-directive">Clone element directive</h1>
<p>The directive works as follows (everything happens in <code>ngOnChanges</code> method):</p>
<ul>
<li>clear the view of created elements</li>
</ul>
<pre><code class="lang-typescript"><span class="hljs-built_in">this</span>.viewContainer.clear();
</code></pre>
<ul>
<li>check if the passed value is a valid number</li>
</ul>
<pre><code class="lang-typescript"><span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.appCloneElement == <span class="hljs-literal">null</span>) {
  <span class="hljs-keyword">return</span>;
}

<span class="hljs-keyword">if</span> (!<span class="hljs-built_in">Number</span>.isFinite(<span class="hljs-built_in">this</span>.appCloneElement)) {
  <span class="hljs-keyword">return</span>;
}

<span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.appCloneElement &lt; <span class="hljs-number">1</span>) {
  <span class="hljs-keyword">return</span>;
}
</code></pre>
<ul>
<li>render the elements</li>
</ul>
<pre><code class="lang-typescript"><span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">1</span>; i &lt;= <span class="hljs-built_in">this</span>.appCloneElement; i++) {
  <span class="hljs-built_in">this</span>.viewContainer.createEmbeddedView(<span class="hljs-built_in">this</span>.templateRef)
}
</code></pre>
<p>Full implementation:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Directive, Input, OnChanges, SimpleChanges, TemplateRef, ViewContainerRef } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;

<span class="hljs-meta">@Directive</span>({
  selector: <span class="hljs-string">'[appCloneElement]'</span>
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> CloneElementDirective <span class="hljs-keyword">implements</span> OnChanges {
  <span class="hljs-meta">@Input</span>() appCloneElement: <span class="hljs-built_in">number</span> | <span class="hljs-literal">null</span> | <span class="hljs-literal">undefined</span>;

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params">
    <span class="hljs-keyword">private</span> templateRef: TemplateRef&lt;<span class="hljs-built_in">any</span>&gt;,
    <span class="hljs-keyword">private</span> viewContainer: ViewContainerRef
  </span>) {
  }

  ngOnChanges(changes: SimpleChanges): <span class="hljs-built_in">void</span> {
    <span class="hljs-built_in">this</span>.viewContainer.clear();

    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.appCloneElement == <span class="hljs-literal">null</span>) {
      <span class="hljs-keyword">return</span>;
    }

    <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">Number</span>.isFinite(<span class="hljs-built_in">this</span>.appCloneElement)) {
      <span class="hljs-keyword">return</span>;
    }

    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.appCloneElement &lt; <span class="hljs-number">1</span>) {
      <span class="hljs-keyword">return</span>;
    }

    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">1</span>; i &lt;= <span class="hljs-built_in">this</span>.appCloneElement; i++) {
      <span class="hljs-built_in">this</span>.viewContainer.createEmbeddedView(<span class="hljs-built_in">this</span>.templateRef)
    }
  }
}
</code></pre>
<h1 id="heading-testing">Testing</h1>
<p>To test a directive you need a component that passes properties to the directive. In the following example, it's <code>TestComponent</code>. The directive itself is declared in a separate module <code>CloneElementModule</code> so it has to be imported. The testing scenarios are quite simple:</p>
<ul>
<li><p>pass the value to the component</p>
</li>
<li><p>detect changes</p>
</li>
<li><p>validate the number of displayed, cloned elements</p>
</li>
</ul>
<p>The first scenario to check is the default view - without passing any values.</p>
<pre><code class="lang-typescript">describe(<span class="hljs-string">'when component with directive is initialized'</span>, <span class="hljs-function">() =&gt;</span> {

it(<span class="hljs-string">'should not render any element'</span>, <span class="hljs-function">() =&gt;</span> {
  expect(getClones().length).toBe(<span class="hljs-number">0</span>);
});
...
</code></pre>
<p>The directive takes the number of elements to clone. If the number is less that 1 it shouldn't render any element. Since you can pass some values that are not valid numbers but the compiler won't complain about them, we're also going to test them. These values are: <code>null</code>, <code>undefined</code>, <code>NaN</code>, <code>Infinity</code>.</p>
<pre><code class="lang-typescript">describe.each([
  {input: <span class="hljs-number">-1</span>, inputText: <span class="hljs-string">'-1'</span>},
  {input: <span class="hljs-number">-581</span>, inputText: <span class="hljs-string">'-581'</span>},
  {input: <span class="hljs-number">0</span>, inputText: <span class="hljs-string">'0'</span>},
  {input: <span class="hljs-literal">null</span>, inputText: <span class="hljs-string">'null'</span>},
  {input: <span class="hljs-literal">undefined</span>, inputText: <span class="hljs-string">'undefined'</span>},
  {input: <span class="hljs-literal">Infinity</span>, inputText: <span class="hljs-string">'Infinity'</span>},
  {input: <span class="hljs-literal">NaN</span>, inputText: <span class="hljs-string">'NaN'</span>},
])(<span class="hljs-string">'and $inputText value is passed'</span>, <span class="hljs-function">(<span class="hljs-params">{input}</span>) =&gt;</span> {
  beforeEach(<span class="hljs-function">() =&gt;</span> {
    component.cloneNumber = input;
    fixture.detectChanges();
  });

  it(<span class="hljs-string">'should not render any element'</span>, <span class="hljs-function">() =&gt;</span> {
    expect(getClones().length).toBe(<span class="hljs-number">0</span>);
  });
});
</code></pre>
<p>Also, you have to verify that multiple changes to the directive input will produce a valid number of cloned elements. The scenario will look like this:</p>
<ul>
<li><p>pass <code>5</code> number</p>
</li>
<li><p>detect changes</p>
</li>
<li><p>validate there are 5 elements</p>
</li>
<li><p>pass <code>9</code> number</p>
</li>
<li><p>detect changes</p>
</li>
<li><p>validate there are 9 elements</p>
</li>
<li><p>pass <code>undefined</code> value</p>
</li>
<li><p>detect changes</p>
</li>
<li><p>validate there are no elements</p>
</li>
</ul>
<pre><code class="lang-typescript">describe(<span class="hljs-string">'and 5 value is passed'</span>, <span class="hljs-function">() =&gt;</span> {
  beforeEach(<span class="hljs-function">() =&gt;</span> {
    component.cloneNumber = <span class="hljs-number">5</span>;
    fixture.detectChanges();
  });

  it(<span class="hljs-string">`should render 5 elements`</span>, <span class="hljs-function">() =&gt;</span> {
    expect(getClones().length).toBe(<span class="hljs-number">5</span>);
  });

  describe(<span class="hljs-string">'and 9 value is passed'</span>, <span class="hljs-function">() =&gt;</span> {
    beforeEach(<span class="hljs-function">() =&gt;</span> {
      component.cloneNumber = <span class="hljs-number">9</span>;
      fixture.detectChanges();
    });

    it(<span class="hljs-string">`should render 9 elements`</span>, <span class="hljs-function">() =&gt;</span> {
      expect(getClones().length).toBe(<span class="hljs-number">9</span>);
    });
  });

  describe(<span class="hljs-string">'and undefined value is passed'</span>, <span class="hljs-function">() =&gt;</span> {
    beforeEach(<span class="hljs-function">() =&gt;</span> {
      component.cloneNumber = <span class="hljs-literal">undefined</span>;
      fixture.detectChanges();
    });

    it(<span class="hljs-string">`should not render any element`</span>, <span class="hljs-function">() =&gt;</span> {
      expect(getClones().length).toBe(<span class="hljs-number">0</span>);
    });
  });
});
</code></pre>
<p>Full implementation:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { ComponentFixture, TestBed } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core/testing'</span>;
<span class="hljs-keyword">import</span> { Component, DebugElement } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { CommonModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/common'</span>;
<span class="hljs-keyword">import</span> { CloneElementModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'./clone-element.module'</span>;
<span class="hljs-keyword">import</span> { By } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/platform-browser'</span>;

describe(<span class="hljs-string">'CloneElementDirective'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">let</span> fixture: ComponentFixture&lt;TestComponent&gt;;
  <span class="hljs-keyword">let</span> component: TestComponent;

  beforeEach(<span class="hljs-function">() =&gt;</span> {
    TestBed.configureTestingModule({
      imports: [
        CommonModule,
        CloneElementModule
      ],
      declarations: [TestComponent],
    });

    fixture = TestBed.createComponent(TestComponent);
    component = fixture.componentInstance;
  });

  describe(<span class="hljs-string">'when component with directive is initialized'</span>, <span class="hljs-function">() =&gt;</span> {

    it(<span class="hljs-string">'should not render any element'</span>, <span class="hljs-function">() =&gt;</span> {
      expect(getClones().length).toBe(<span class="hljs-number">0</span>);
    });

    describe.each([
      {input: <span class="hljs-number">-1</span>, inputText: <span class="hljs-string">'-1'</span>},
      {input: <span class="hljs-number">-581</span>, inputText: <span class="hljs-string">'-581'</span>},
      {input: <span class="hljs-number">0</span>, inputText: <span class="hljs-string">'0'</span>},
      {input: <span class="hljs-literal">null</span>, inputText: <span class="hljs-string">'null'</span>},
      {input: <span class="hljs-literal">undefined</span>, inputText: <span class="hljs-string">'undefined'</span>},
      {input: <span class="hljs-literal">Infinity</span>, inputText: <span class="hljs-string">'Infinity'</span>},
      {input: <span class="hljs-literal">NaN</span>, inputText: <span class="hljs-string">'NaN'</span>},
    ])(<span class="hljs-string">'and $inputText value is passed'</span>, <span class="hljs-function">(<span class="hljs-params">{input}</span>) =&gt;</span> {
      beforeEach(<span class="hljs-function">() =&gt;</span> {
        component.cloneNumber = input;
        fixture.detectChanges();
      });

      it(<span class="hljs-string">'should not render any element'</span>, <span class="hljs-function">() =&gt;</span> {
        expect(getClones().length).toBe(<span class="hljs-number">0</span>);
      });
    });

    describe.each([
      {input: <span class="hljs-number">1</span>},
      {input: <span class="hljs-number">99</span>},
      {input: <span class="hljs-number">19</span>},
    ])(<span class="hljs-string">'and $input value is passed'</span>, <span class="hljs-function">(<span class="hljs-params">{input}</span>) =&gt;</span> {
      beforeEach(<span class="hljs-function">() =&gt;</span> {
        component.cloneNumber = input;
        fixture.detectChanges();
      });

      it(<span class="hljs-string">`should render <span class="hljs-subst">${ input }</span> element(s)`</span>, <span class="hljs-function">() =&gt;</span> {
        expect(getClones().length).toBe(input);
      });
    });

    describe(<span class="hljs-string">'and 5 value is passed'</span>, <span class="hljs-function">() =&gt;</span> {
      beforeEach(<span class="hljs-function">() =&gt;</span> {
        component.cloneNumber = <span class="hljs-number">5</span>;
        fixture.detectChanges();
      });

      it(<span class="hljs-string">`should render 5 elements`</span>, <span class="hljs-function">() =&gt;</span> {
        expect(getClones().length).toBe(<span class="hljs-number">5</span>);
      });

      describe(<span class="hljs-string">'and 9 value is passed'</span>, <span class="hljs-function">() =&gt;</span> {
        beforeEach(<span class="hljs-function">() =&gt;</span> {
          component.cloneNumber = <span class="hljs-number">9</span>;
          fixture.detectChanges();
        });

        it(<span class="hljs-string">`should render 9 elements`</span>, <span class="hljs-function">() =&gt;</span> {
          expect(getClones().length).toBe(<span class="hljs-number">9</span>);
        });
      });

      describe(<span class="hljs-string">'and undefined value is passed'</span>, <span class="hljs-function">() =&gt;</span> {
        beforeEach(<span class="hljs-function">() =&gt;</span> {
          component.cloneNumber = <span class="hljs-literal">undefined</span>;
          fixture.detectChanges();
        });

        it(<span class="hljs-string">`should not render any element`</span>, <span class="hljs-function">() =&gt;</span> {
          expect(getClones().length).toBe(<span class="hljs-number">0</span>);
        });
      });
    });
  });

  <span class="hljs-keyword">const</span> getClones = (): DebugElement[] =&gt; {
    <span class="hljs-keyword">return</span> fixture.debugElement.queryAll(By.css(<span class="hljs-string">'.clone'</span>));
  }
});


<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-test'</span>,
  template: <span class="hljs-string">`
    &lt;div class="clone" *appCloneElement="cloneNumber"&gt;I'm a clone&lt;/div&gt;
  `</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> TestComponent {
  cloneNumber: <span class="hljs-built_in">number</span> | <span class="hljs-literal">null</span> | <span class="hljs-literal">undefined</span>;
}
</code></pre>
<p>Run the tests and you should see the results:</p>
<pre><code class="lang-text"> PASS  src/app/directives/clone-element.directive.spec.ts
  CloneElementDirective
    when component with directive is initialized
      ✓ should not render any element
      and -1 value is passed
        ✓ should not render any element
      and -581 value is passed
        ✓ should not render any element
      and 0 value is passed
        ✓ should not render any element 
      and null value is passed
        ✓ should not render any element 
      and undefined value is passed
        ✓ should not render any element 
      and Infinity value is passed
        ✓ should not render any element 
      and NaN value is passed
        ✓ should not render any element
      and 1 value is passed
        ✓ should render 1 element(s) 
      and 99 value is passed
        ✓ should render 99 element(s) 
      and 19 value is passed
        ✓ should render 19 element(s) 
      and 5 value is passed
        ✓ should render 5 elements 
        and 9 value is passed
          ✓ should render 9 elements 
        and undefined value is passed
          ✓ should not render any element 

Test Suites: 1 passed, 1 total
Tests:       14 passed, 14 total
</code></pre>
<h1 id="heading-source-code">Source code</h1>
<p>https://gitlab.com/barcioch-blog-examples/series-angular-testing-06-angular-directive-testing</p>
]]></content:encoded></item><item><title><![CDATA[Angular - include basic git details]]></title><description><![CDATA[In this article, I'll show you how to include basic GIT data in the Angular application. The data will be dumped to src/assets/version.json file and will consist of:

short hash

long hash

branch name

commit date


Set up the app
Init the test app ...]]></description><link>https://blog.procode.pl/angular-include-basic-git-details</link><guid isPermaLink="true">https://blog.procode.pl/angular-include-basic-git-details</guid><category><![CDATA[Angular]]></category><category><![CDATA[Git]]></category><dc:creator><![CDATA[Bartosz Szłapak]]></dc:creator><pubDate>Sat, 15 Apr 2023 14:34:15 GMT</pubDate><content:encoded><![CDATA[<p>In this article, I'll show you how to include basic GIT data in the Angular application. The data will be dumped to <code>src/assets/version.json</code> file and will consist of:</p>
<ul>
<li><p>short hash</p>
</li>
<li><p>long hash</p>
</li>
<li><p>branch name</p>
</li>
<li><p>commit date</p>
</li>
</ul>
<h1 id="heading-set-up-the-app">Set up the app</h1>
<p>Init the test app with default settings:</p>
<pre><code class="lang-shell">ng new app --defaults=true
</code></pre>
<p>Navigate to the app directory:</p>
<pre><code class="lang-shell">cd app
</code></pre>
<p>Install the dependencies. I'll be using the <code>git-rev-sync</code> library.</p>
<pre><code class="lang-shell">npm i git-rev-sync
</code></pre>
<h1 id="heading-implement-the-solution">Implement the solution</h1>
<p>Create a <code>.gitignore</code> file the <code>src/assets</code> directory and add the following content. This will prevent the file from being included in the repository.</p>
<pre><code class="lang-bash">version.json
</code></pre>
<p>Create the script file <code>dump-git-info.js</code> in the app's main directory. The flow is quite simple:</p>
<ul>
<li><p>import the libraries</p>
</li>
<li><p>read git data</p>
</li>
<li><p>write file</p>
</li>
</ul>
<pre><code class="lang-javascript"><span class="hljs-comment">// dump-git-info.js</span>

<span class="hljs-keyword">const</span> git = <span class="hljs-built_in">require</span>(<span class="hljs-string">'git-rev-sync'</span>);
<span class="hljs-keyword">const</span> { writeFileSync } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'fs'</span>);

<span class="hljs-keyword">const</span> version = {
  <span class="hljs-attr">short</span>: git.short(),
  <span class="hljs-attr">long</span>: git.long(),
  <span class="hljs-attr">branch</span>: git.branch(),
  <span class="hljs-attr">date</span>: git.date(),
};

writeFileSync(<span class="hljs-string">'./src/assets/version.json'</span>, <span class="hljs-built_in">JSON</span>.stringify(version));
</code></pre>
<p>You can run the script with the following command:</p>
<pre><code class="lang-shell">npm i git-rev-sync
</code></pre>
<p>Check the file <code>./src/assets/version.json</code> and you should see output similar to:</p>
<pre><code class="lang-json">{<span class="hljs-attr">"short"</span>:<span class="hljs-string">"386e4a5"</span>,<span class="hljs-attr">"long"</span>:<span class="hljs-string">"386e4a55e9e96018465754de172c351c68f7ff1f"</span>,<span class="hljs-attr">"branch"</span>:<span class="hljs-string">"master"</span>,<span class="hljs-attr">"date"</span>:<span class="hljs-string">"2023-04-15T13:45:30.000Z"</span>}
</code></pre>
<p>Add the script to <code>package.json</code>:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"scripts"</span>: {
    <span class="hljs-attr">"dump-git-info"</span>: <span class="hljs-string">"node dump-git-info.js"</span>,
...
</code></pre>
<p>Also, add <code>prebuild</code> hook to run it automatically:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"scripts"</span>: {
    <span class="hljs-attr">"prebuild"</span>: <span class="hljs-string">"npm run dump-git-info"</span>,
...
</code></pre>
<p>From now on, whenever you run <code>npm run build</code> the <code>version.json</code> will be also created and accessible through <code>/assets/version.json</code> url in the browser.</p>
<pre><code class="lang-shell">$ npm run build

&gt; app@0.0.0 prebuild
&gt; npm run dump-git-info


&gt; app@0.0.0 dump-git-info
&gt; node dump-git-info.js


&gt; app@0.0.0 build
&gt; ng build

&gt; Generating browser application bundles (phase: setup)...
</code></pre>
<h1 id="heading-source-code">Source code</h1>
<p>https://gitlab.com/barcioch-blog-examples/007-angular-include-git-info</p>
]]></content:encoded></item><item><title><![CDATA[Youtube - mark all as "not interesing"]]></title><description><![CDATA[Whenever you open a video on YouTube, its algorithm will start to recommend you similar videos. Sometimes it's quite irritating. Last time I was checking the video card comparisons. Soon after YouTube decided I'm a big fan of graphics card comparison...]]></description><link>https://blog.procode.pl/youtube-mark-all-as-not-interesing</link><guid isPermaLink="true">https://blog.procode.pl/youtube-mark-all-as-not-interesing</guid><category><![CDATA[youtube]]></category><category><![CDATA[hack]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Bartosz Szłapak]]></dc:creator><pubDate>Thu, 13 Apr 2023 11:48:07 GMT</pubDate><content:encoded><![CDATA[<p>Whenever you open a video on YouTube, its algorithm will start to recommend you similar videos. Sometimes it's quite irritating. Last time I was checking the video card comparisons. Soon after YouTube decided I'm a big fan of graphics card comparisons and every 5th recommendation was the same card comparison. You get the point.</p>
<p>To mark a video as <code>not interesting</code> you need to click the menu button and click <code>I'm not interested</code> every single time. This becomes tedious very quickly, so I created a <code>quick and dirty</code> solution. Assuming you are currently viewing YouTube's main page, it iterates over all loaded videos and marks them as not interesting.</p>
<p>By default, the script assumes that 6th option in the menu is the <code>I'm not interested</code> option. Before copying/pasting make sure it's the same since in your region/language it can be located in a different position. If needed alter the <code>notInterstedMenuItemIndex</code> value.</p>
<p>If the script encounters only one option in the menu it assumes it's the YouTubeMix and clicks it.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> sleep = <span class="hljs-function">(<span class="hljs-params">ms</span>) =&gt;</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function"><span class="hljs-params">resolve</span> =&gt;</span> <span class="hljs-built_in">setTimeout</span>(resolve, ms));
}

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">main</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> notInterstedMenuItemIndex = <span class="hljs-number">4</span>;
    <span class="hljs-keyword">const</span> menuButtons = <span class="hljs-built_in">Array</span>.from(<span class="hljs-built_in">document</span>.querySelectorAll(<span class="hljs-string">'#details button#button'</span>));

    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; menuButtons.length; i++) {
        menuButtons[i].click();
        <span class="hljs-keyword">await</span> sleep(<span class="hljs-number">50</span>);

        <span class="hljs-keyword">const</span> menuItems = <span class="hljs-built_in">document</span>.querySelectorAll(<span class="hljs-string">'#items ytd-menu-service-item-renderer'</span>);
        <span class="hljs-keyword">const</span> menuItemIndex = menuItems.length === <span class="hljs-number">1</span> ? <span class="hljs-number">0</span> : notInterstedMenuItemIndex;
        menuItems[menuItemIndex].click();
        <span class="hljs-keyword">await</span> sleep(<span class="hljs-number">50</span>);
    }
}

main();
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Angular pipe testing]]></title><description><![CDATA[In this article, I'll show how to test Angular pipes. It can be done in two different ways:

directly create pipe and test output

use the pipe in the component and test HTML


In the example there will be created two simple pipes:

trim - for trimmi...]]></description><link>https://blog.procode.pl/angular-pipe-testing</link><guid isPermaLink="true">https://blog.procode.pl/angular-pipe-testing</guid><category><![CDATA[Angular]]></category><category><![CDATA[Testing]]></category><category><![CDATA[pipe]]></category><category><![CDATA[Jest]]></category><dc:creator><![CDATA[Bartosz Szłapak]]></dc:creator><pubDate>Sun, 09 Apr 2023 18:23:37 GMT</pubDate><content:encoded><![CDATA[<p>In this article, I'll show how to test Angular pipes. It can be done in two different ways:</p>
<ul>
<li><p>directly create pipe and test output</p>
</li>
<li><p>use the pipe in the component and test HTML</p>
</li>
</ul>
<p>In the example there will be created two simple pipes:</p>
<ul>
<li><p>trim - for trimming strings</p>
</li>
<li><p>ucFirst - for changing the first character to uppercase</p>
</li>
</ul>
<p>The environment is initialized with the following libraries:</p>
<ul>
<li><p>Angular 15</p>
</li>
<li><p>Jest 29</p>
</li>
</ul>
<h1 id="heading-the-pipes">The pipes</h1>
<h2 id="heading-trimpipe">TrimPipe</h2>
<p>The pipe works as follows:</p>
<ul>
<li><p>take the input string</p>
</li>
<li><p>if it's null or undefined or an empty string - return an empty string</p>
</li>
<li><p>otherwise - return trimmed value</p>
</li>
</ul>
<pre><code class="lang-typescript"><span class="hljs-comment">// trim.pipe.ts</span>

<span class="hljs-keyword">import</span> { Pipe, PipeTransform } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;

<span class="hljs-meta">@Pipe</span>({
  name: <span class="hljs-string">'appTrim'</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> TrimPipe <span class="hljs-keyword">implements</span> PipeTransform {

  transform(input: <span class="hljs-built_in">string</span> | <span class="hljs-literal">null</span> | <span class="hljs-literal">undefined</span>): <span class="hljs-built_in">string</span> {
    <span class="hljs-keyword">if</span> (input == <span class="hljs-literal">null</span> || input.length === <span class="hljs-number">0</span>) {
      <span class="hljs-keyword">return</span> <span class="hljs-string">''</span>;
    }

    <span class="hljs-keyword">return</span> input.trim();
  }
}
</code></pre>
<p>Test it:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// trim.pipe.spec.ts</span>

<span class="hljs-keyword">import</span> { TrimPipe } <span class="hljs-keyword">from</span> <span class="hljs-string">'./trim.pipe'</span>;

describe(<span class="hljs-string">'TrimPipe'</span>, <span class="hljs-function">() =&gt;</span> {
  describe(<span class="hljs-string">'when pipe is created'</span>, <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">let</span> pipe: TrimPipe;

    beforeAll(<span class="hljs-function">() =&gt;</span> {
      pipe = <span class="hljs-keyword">new</span> TrimPipe();
    });

    it.each([
      {input: <span class="hljs-literal">undefined</span>, text: <span class="hljs-string">'undefined'</span>},
      {input: <span class="hljs-literal">null</span>, text: <span class="hljs-string">'null'</span>},
      {input: <span class="hljs-string">''</span>, text: <span class="hljs-string">'empty string'</span>},
      {input: <span class="hljs-string">'\n   \n'</span>, text: <span class="hljs-string">'"\\n   \\n"'</span>},
    ])(<span class="hljs-string">'should transform $text to empty string'</span>, <span class="hljs-function">(<span class="hljs-params">{input, text}</span>) =&gt;</span> {
      expect(pipe.transform(input)).toBe(<span class="hljs-string">''</span>);
    });

    it.each([
      {input: <span class="hljs-string">'  text'</span>, text: <span class="hljs-string">'text'</span>, expectedText: <span class="hljs-string">'text'</span>, expected: <span class="hljs-string">'text'</span>},
      {input: <span class="hljs-string">'text  '</span>, text: <span class="hljs-string">'text'</span>, expectedText: <span class="hljs-string">'text'</span>, expected: <span class="hljs-string">'text'</span>},
      {input: <span class="hljs-string">'text text'</span>, text: <span class="hljs-string">'text text'</span>, expectedText: <span class="hljs-string">'text text'</span>, expected: <span class="hljs-string">'text text'</span>},
      {input: <span class="hljs-string">'   text text  '</span>, text: <span class="hljs-string">'text text'</span>, expectedText: <span class="hljs-string">'text text'</span>, expected: <span class="hljs-string">'text text'</span>},
      {input: <span class="hljs-string">'   text \n text  '</span>, text: <span class="hljs-string">'text \\n text'</span>, expectedText: <span class="hljs-string">'text \\n text'</span>, expected: <span class="hljs-string">'text \n text'</span>},
      {
        input: <span class="hljs-string">'\n   text \n text  \n'</span>,
        text: <span class="hljs-string">'\\n   text \\n text  \\n'</span>,
        expectedText: <span class="hljs-string">'text \\n text'</span>,
        expected: <span class="hljs-string">'text \n text'</span>
      },
    ])(<span class="hljs-string">'should transform "$text" to "$expectedText"'</span>, <span class="hljs-function">(<span class="hljs-params">{input, expected, expectedText}</span>) =&gt;</span> {
      expect(pipe.transform(input)).toBe(expected);
    });
  });
});
</code></pre>
<h2 id="heading-ucfirstpipe">UcFirstPipe</h2>
<p>The pipe works as follows:</p>
<ul>
<li><p>take the input string</p>
</li>
<li><p>if it's null or undefined or an empty string - return an empty string</p>
</li>
<li><p>otherwise - make the first letter upper case and return the string</p>
</li>
</ul>
<pre><code class="lang-typescript"><span class="hljs-comment">// uc-first.pipe.ts</span>

<span class="hljs-keyword">import</span> { Pipe, PipeTransform } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;

<span class="hljs-meta">@Pipe</span>({
  name: <span class="hljs-string">'appUcFirst'</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> UcFirstPipe <span class="hljs-keyword">implements</span> PipeTransform {

  transform(input: <span class="hljs-built_in">string</span> | <span class="hljs-literal">null</span> | <span class="hljs-literal">undefined</span>): <span class="hljs-built_in">string</span> {
    <span class="hljs-keyword">if</span> (input == <span class="hljs-literal">null</span> || input.length === <span class="hljs-number">0</span>) {
      <span class="hljs-keyword">return</span> <span class="hljs-string">''</span>;
    }

    <span class="hljs-keyword">return</span> input[<span class="hljs-number">0</span>].toUpperCase() + input.substring(<span class="hljs-number">1</span>, input.length);
  }
}
</code></pre>
<p>Test it:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// uc-first.pipe.spec.ts</span>

<span class="hljs-keyword">import</span> { UcFirstPipe } <span class="hljs-keyword">from</span> <span class="hljs-string">'./uc-first.pipe'</span>;

describe(<span class="hljs-string">'UcFirstPipe'</span>, <span class="hljs-function">() =&gt;</span> {
  describe(<span class="hljs-string">'when pipe is created'</span>, <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">let</span> pipe: UcFirstPipe;

    beforeAll(<span class="hljs-function">() =&gt;</span> {
      pipe = <span class="hljs-keyword">new</span> UcFirstPipe();
    });

    it.each([
      {input: <span class="hljs-literal">undefined</span>, text: <span class="hljs-string">'undefined'</span>},
      {input: <span class="hljs-literal">null</span>, text: <span class="hljs-string">'null'</span>},
      {input: <span class="hljs-string">''</span>, text: <span class="hljs-string">'empty string'</span>},
    ])(<span class="hljs-string">'should transform $text to empty string'</span>, <span class="hljs-function">(<span class="hljs-params">{input, text}</span>) =&gt;</span> {
      expect(pipe.transform(input)).toBe(<span class="hljs-string">''</span>);
    });

    it.each([
      {input: <span class="hljs-string">' '</span>, expected: <span class="hljs-string">' '</span>},
      {input: <span class="hljs-string">' text'</span>, expected: <span class="hljs-string">' text'</span>},
      {input: <span class="hljs-string">'text'</span>, expected: <span class="hljs-string">'Text'</span>},
      {input: <span class="hljs-string">'Text'</span>, expected: <span class="hljs-string">'Text'</span>},
      {input: <span class="hljs-string">'a'</span>, expected: <span class="hljs-string">'A'</span>},
      {input: <span class="hljs-string">'1a'</span>, expected: <span class="hljs-string">'1a'</span>},
      {input: <span class="hljs-string">'TEXT '</span>, expected: <span class="hljs-string">'TEXT '</span>},
    ])(<span class="hljs-string">'should transform "$input" to "$expected"'</span>, <span class="hljs-function">(<span class="hljs-params">{input, expected}</span>) =&gt;</span> {
      expect(pipe.transform(input)).toBe(expected);
    });
  });
});
</code></pre>
<h1 id="heading-testing-in-component">Testing in component</h1>
<p>To properly test components without worrying about change detection it's a good practice to wrap them in the <code>wrapper component</code> and update only the <code>wrapper's</code> values. An example, of how the final HTML structure will look like:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">app-wrapper</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">app-test</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"result"</span>&gt;</span>{{ title | appTrim | appUcFirst}}<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">app-test</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">app-wrapper</span>&gt;</span>
</code></pre>
<p>The test suite contains two extra definitions:</p>
<ul>
<li><p><code>AppWrapperComponent</code> - wrapper component for setting values</p>
</li>
<li><p><code>AppTestComponent</code> - the right component that uses the pipe and takes data by input. Usually, these kinds of components don't need to be defined inside tests since we want to use already existing components or modules.</p>
</li>
</ul>
<p>The test procedure is quite straightforward:</p>
<ul>
<li>create testing module</li>
</ul>
<p><code>TestBed.configureTestingModule()</code></p>
<ul>
<li>prepare test data using</li>
</ul>
<p><code>describe.each()</code></p>
<ul>
<li>pass the data to the component and run change detection</li>
</ul>
<pre><code class="lang-typescript">beforeEach(<span class="hljs-function">() =&gt;</span> {
  component.title = input;
  fixture.detectChanges();
});
</code></pre>
<ul>
<li>test the <code>title</code> element</li>
</ul>
<pre><code class="lang-typescript">it(<span class="hljs-string">`should display "<span class="hljs-subst">${ expectedText }</span>"`</span>, <span class="hljs-function">() =&gt;</span> {
  expect(title().nativeElement.textContent).toBe(expected);
});
</code></pre>
<pre><code class="lang-typescript"><span class="hljs-comment">// pipes.spec.ts</span>

<span class="hljs-keyword">import</span> { ComponentFixture, TestBed } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core/testing'</span>;
<span class="hljs-keyword">import</span> { TrimPipe } <span class="hljs-keyword">from</span> <span class="hljs-string">'./pipes/trim.pipe'</span>;
<span class="hljs-keyword">import</span> { UcFirstPipe } <span class="hljs-keyword">from</span> <span class="hljs-string">'./pipes/uc-first.pipe'</span>;
<span class="hljs-keyword">import</span> { Component, DebugElement, Input } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { By } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/platform-browser'</span>;

describe(<span class="hljs-string">'Test Pipes'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">let</span> fixture: ComponentFixture&lt;AppWrapperComponent&gt;;
  <span class="hljs-keyword">let</span> component: AppWrapperComponent;

  beforeEach(<span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">await</span> TestBed.configureTestingModule({
      declarations: [
        TrimPipe,
        UcFirstPipe,
        AppTestComponent,
        AppWrapperComponent
      ],
    }).compileComponents();

    fixture = TestBed.createComponent(AppWrapperComponent);
    component = fixture.componentInstance;
  });

  describe.each([
    {input: <span class="hljs-string">''</span>, inputText: <span class="hljs-string">''</span>, expected: <span class="hljs-string">''</span>, expectedText: <span class="hljs-string">''</span>},
    {input: <span class="hljs-string">'article'</span>, inputText: <span class="hljs-string">'article'</span>, expected: <span class="hljs-string">'Article'</span>, expectedText: <span class="hljs-string">'Article'</span>},
    {input: <span class="hljs-string">'  article  '</span>, inputText: <span class="hljs-string">'  article  '</span>, expected: <span class="hljs-string">'Article'</span>, expectedText: <span class="hljs-string">'Article'</span>},
    {input: <span class="hljs-string">'  new article  '</span>, inputText: <span class="hljs-string">'  new article  '</span>, expected: <span class="hljs-string">'New article'</span>, expectedText: <span class="hljs-string">'New article'</span>},
    {
      input: <span class="hljs-string">'  \n new article \n  '</span>,
      inputText: <span class="hljs-string">'  \\n new article \\n  '</span>,
      expected: <span class="hljs-string">'New article'</span>,
      expectedText: <span class="hljs-string">'New article'</span>
    },
    {
      input: <span class="hljs-string">'  \n new \n article \n  '</span>,
      inputText: <span class="hljs-string">'  \\n new \\n article \\n  '</span>,
      expected: <span class="hljs-string">'New \n article'</span>,
      expectedText: <span class="hljs-string">'New \\n article'</span>
    },
  ])(<span class="hljs-string">'when "$inputText" is passed'</span>, <span class="hljs-function">(<span class="hljs-params">{input, inputText, expected, expectedText}</span>) =&gt;</span> {
    beforeEach(<span class="hljs-function">() =&gt;</span> {
      component.title = input;
      fixture.detectChanges();
    });

    it(<span class="hljs-string">`should display "<span class="hljs-subst">${ expectedText }</span>"`</span>, <span class="hljs-function">() =&gt;</span> {
      expect(title().nativeElement.textContent).toBe(expected);
    });
  });

  <span class="hljs-keyword">const</span> title = (): <span class="hljs-function"><span class="hljs-params">DebugElement</span> =&gt;</span> {
    <span class="hljs-keyword">return</span> fixture.debugElement.query(By.css(<span class="hljs-string">'.title'</span>));
  }
});


<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-test'</span>,
  template: <span class="hljs-string">'&lt;span class="title"&gt;{{ title | appTrim | appUcFirst }}&lt;/span&gt;'</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AppTestComponent {
  <span class="hljs-meta">@Input</span>() title: <span class="hljs-built_in">string</span> | <span class="hljs-literal">null</span> | <span class="hljs-literal">undefined</span>;
}

<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-wrapper'</span>,
  template: <span class="hljs-string">'&lt;app-test [title]="title"&gt;&lt;/app-test&gt;'</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AppWrapperComponent {
  title: <span class="hljs-built_in">string</span> | <span class="hljs-literal">null</span> | <span class="hljs-literal">undefined</span>;
}
</code></pre>
<p>Run <code>npx jest --coverage</code> and make sure that all the code paths you're interested in were tested.</p>
<pre><code class="lang-text"> PASS  src/app/pipes/trim.pipe.spec.ts
 PASS  src/app/pipes/uc-first.pipe.spec.ts
 PASS  src/app/pipes.spec.ts
------------------|---------|----------|---------|---------|-------------------
File              | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
------------------|---------|----------|---------|---------|-------------------
All files         |     100 |      100 |     100 |     100 |                   
 trim.pipe.ts     |     100 |      100 |     100 |     100 |                   
 uc-first.pipe.ts |     100 |      100 |     100 |     100 |                   
------------------|---------|----------|---------|---------|-------------------

Test Suites: 3 passed, 3 total
Tests:       26 passed, 26 total
Snapshots:   0 total
Time:        1.945 s, estimated 3 s
Ran all test suites.
</code></pre>
<h1 id="heading-summary">Summary</h1>
<p>The pipes are implemented and thoroughly tested. In this example, the pipes were defined directly in the testing module. In the real application, the pipe will be probably defined in a separate module that you're going to import. As always, try to cover as many cases as you can imagine.</p>
<h1 id="heading-source-code">Source code</h1>
<p>https://gitlab.com/barcioch-blog-examples/series-angular-testing-05-angular-pipe-testing</p>
]]></content:encoded></item><item><title><![CDATA[Angular with Jest setup]]></title><description><![CDATA[In this article, I'll show how to set up a basic Angular app with Jest. In case of any problems please refer to the official documentation:

Jest - https://jestjs.io

jest-preset-angular - https://thymikee.github.io/jest-preset-angular


The setup is...]]></description><link>https://blog.procode.pl/angular-with-jest-setup</link><guid isPermaLink="true">https://blog.procode.pl/angular-with-jest-setup</guid><category><![CDATA[Testing]]></category><category><![CDATA[Angular]]></category><category><![CDATA[Jest]]></category><dc:creator><![CDATA[Bartosz Szłapak]]></dc:creator><pubDate>Sun, 09 Apr 2023 18:00:42 GMT</pubDate><content:encoded><![CDATA[<p>In this article, I'll show how to set up a basic Angular app with Jest. In case of any problems please refer to the official documentation:</p>
<ul>
<li><p>Jest - https://jestjs.io</p>
</li>
<li><p>jest-preset-angular - https://thymikee.github.io/jest-preset-angular</p>
</li>
</ul>
<p>The setup is based on Angular 15 and Jest 29. Make sure you have globally installed <code>Angular CLI 15</code> and <code>npx</code>.</p>
<h1 id="heading-create-the-app-optional">Create the app (optional)</h1>
<p>Create the new application with the defaults using Angular CLI.</p>
<pre><code class="lang-shell">ng new app --defaults=true
</code></pre>
<p>Navigate to the app directory:</p>
<pre><code class="lang-shell">cd app
</code></pre>
<h1 id="heading-set-up-the-jest">Set up the Jest</h1>
<p>Install required dependencies</p>
<pre><code class="lang-shell">npm i jest@29 @types/jest@29 jest-preset-angular ts-jest
</code></pre>
<p>Create <code>setup-jest.ts</code> file the application root and insert contents:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> <span class="hljs-string">'jest-preset-angular/setup-jest'</span>;
</code></pre>
<p>Create <code>jest.config.js</code> file in the application root and insert contents:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">module</span>.exports = {
  <span class="hljs-attr">preset</span>: <span class="hljs-string">'jest-preset-angular'</span>,
  <span class="hljs-attr">setupFilesAfterEnv</span>: [<span class="hljs-string">'&lt;rootDir&gt;/setup-jest.ts'</span>],
  <span class="hljs-attr">globalSetup</span>: <span class="hljs-string">'jest-preset-angular/global-setup'</span>,
};
</code></pre>
<p>Update <code>tsconfig.spec.json</code> file to match the following contents. There are two differences between the original and the changed file:</p>
<ul>
<li><p>added <code>"module": "CommonJs"</code></p>
</li>
<li><p>changed <code>"types": ["jasmine"]</code> to <code>"types": ["jest"]</code></p>
</li>
</ul>
<pre><code class="lang-json">{
  <span class="hljs-attr">"extends"</span>: <span class="hljs-string">"./tsconfig.json"</span>,
  <span class="hljs-attr">"compilerOptions"</span>: {
    <span class="hljs-attr">"outDir"</span>: <span class="hljs-string">"./out-tsc/spec"</span>,
    <span class="hljs-attr">"module"</span>: <span class="hljs-string">"CommonJs"</span>,
    <span class="hljs-attr">"types"</span>: [<span class="hljs-string">"jest"</span>]
  },
  <span class="hljs-attr">"include"</span>: [<span class="hljs-string">"src/**/*.spec.ts"</span>, <span class="hljs-string">"src/**/*.d.ts"</span>]
}
</code></pre>
<p>Remove <code>karma</code> and <code>jasmine</code> dependencies.</p>
<pre><code class="lang-shell">npm remove @types/jasmine jasmine-core karma karma-chrome-launcher karma-coverage karma-jasmine karma-jasmine-html-reporter
</code></pre>
<h1 id="heading-verify">Verify</h1>
<p>Use the following command to run the tests. In the default app, there is one test suite for the <code>AppComponent</code> with 3 tests.</p>
<pre><code class="lang-shell">npx jest
</code></pre>
<p>The tests were run successfully.</p>
<pre><code class="lang-text"> PASS  src/app/app.component.spec.ts
  AppComponent
    ✓ should create the app (183 ms)
    ✓ should have as title 'app' (36 ms)
    ✓ should render title (34 ms)

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        3.577 s, estimated 5 s
Ran all test suites.
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Migrate legacy NGRX]]></title><description><![CDATA[Before version 15 of NGRX, some old syntax and decorators were allowed but deprecated. I'll show you how to migrate a simple, but deprecated code to NGRX@15. The following example is mostly taken from a real-world application. There will be minor cod...]]></description><link>https://blog.procode.pl/migrate-legacy-ngrx</link><guid isPermaLink="true">https://blog.procode.pl/migrate-legacy-ngrx</guid><category><![CDATA[TypeScript]]></category><category><![CDATA[NgRx]]></category><category><![CDATA[migration]]></category><category><![CDATA[legacy]]></category><category><![CDATA[store]]></category><dc:creator><![CDATA[Bartosz Szłapak]]></dc:creator><pubDate>Wed, 05 Apr 2023 10:53:54 GMT</pubDate><content:encoded><![CDATA[<p>Before version 15 of NGRX, some old syntax and decorators were allowed but deprecated. I'll show you how to migrate a simple, but deprecated code to NGRX@15. The following example is mostly taken from a real-world application. There will be minor code refactoring included.</p>
<h1 id="heading-actions">Actions</h1>
<p>The old way of creating actions.</p>
<ul>
<li><p>an enum <code>CountriesActionTypes</code> with action types</p>
</li>
<li><p>each action as a class implementing <code>Action</code> interface</p>
</li>
<li><p>union type <code>CountriesActions</code> with action class' references</p>
</li>
</ul>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Action } <span class="hljs-keyword">from</span> <span class="hljs-string">'@ngrx/store'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-built_in">enum</span> CountriesActionTypes {
  LoadCountries = <span class="hljs-string">'[Countries] Load Countries'</span>,
  LoadCountriesSuccess = <span class="hljs-string">'[Countries] Load Countries Success'</span>,
  LoadCountriesFailure = <span class="hljs-string">'[Countries] Load Countries Failure'</span>,
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> LoadCountries <span class="hljs-keyword">implements</span> Action {
  <span class="hljs-keyword">readonly</span> <span class="hljs-keyword">type</span> = CountriesActionTypes.LoadCountries;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> LoadCountriesSuccess <span class="hljs-keyword">implements</span> Action {
  <span class="hljs-keyword">readonly</span> <span class="hljs-keyword">type</span> = CountriesActionTypes.LoadCountriesSuccess;

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">public</span> payload: <span class="hljs-built_in">string</span>[]</span>) {
  }
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> LoadCountriesFailure <span class="hljs-keyword">implements</span> Action {
  <span class="hljs-keyword">readonly</span> <span class="hljs-keyword">type</span> = CountriesActionTypes.LoadCountriesFailure;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> CountriesActions = LoadCountries | LoadCountriesSuccess | LoadCountriesFailure;
</code></pre>
<p>To migrate:</p>
<ul>
<li><p>remove the enum with action types</p>
</li>
<li><p>replace action classes with <code>createAction()</code> functions</p>
<ul>
<li>use <code>props()</code> function to define action payload</li>
</ul>
</li>
</ul>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { createAction, props } <span class="hljs-keyword">from</span> <span class="hljs-string">'@ngrx/store'</span>;


<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> requestCountries = createAction(<span class="hljs-string">'[Countries] Request countries'</span>);
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> requestCountriesSuccess = createAction(<span class="hljs-string">'[Countries] Request countries success'</span>, props&lt;{ response: <span class="hljs-built_in">string</span>[] }&gt;());
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> requestCountriesError = createAction(<span class="hljs-string">'[Countries] Request countries error'</span>, props&lt;{ error: unknown }&gt;());
</code></pre>
<h1 id="heading-reducer">Reducer</h1>
<p>The old reducer uses <code>switch</code> instruction to update state according to supported action type (taken from action's class <code>type</code> property).</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { CountriesActions, CountriesActionTypes } <span class="hljs-keyword">from</span> <span class="hljs-string">'./countries.actions'</span>;


<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> STATE_NAME = <span class="hljs-string">'countries'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> CountriesState {
  loading?: <span class="hljs-built_in">boolean</span>;
  error?: <span class="hljs-built_in">boolean</span>;
  list?: <span class="hljs-built_in">string</span>[];
}

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">countriesReducer</span>(<span class="hljs-params">state: CountriesState = {}, action: CountriesActions</span>): <span class="hljs-title">CountriesState</span> </span>{
  <span class="hljs-keyword">switch</span> (action.type) {
    <span class="hljs-keyword">case</span> CountriesActionTypes.LoadCountries:
      <span class="hljs-keyword">return</span> {
        ...state,
        loading: <span class="hljs-literal">true</span>,
        error: <span class="hljs-literal">false</span>,
      };
    <span class="hljs-keyword">case</span> CountriesActionTypes.LoadCountriesSuccess:
      <span class="hljs-keyword">return</span> {
        ...state,
        list: action.payload,
        loading: <span class="hljs-literal">false</span>,
        error: <span class="hljs-literal">false</span>,
      };
    <span class="hljs-keyword">case</span> CountriesActionTypes.LoadCountriesFailure:
      <span class="hljs-keyword">return</span> {
        ...state,
        loading: <span class="hljs-literal">false</span>,
        error: <span class="hljs-literal">true</span>,
      };
    <span class="hljs-keyword">default</span>:
      <span class="hljs-keyword">return</span> state;
  }
}
</code></pre>
<p>To migrate:</p>
<ul>
<li><p>(minor) update state key names to be more verbose</p>
</li>
<li><p>introduce <code>initialState</code></p>
</li>
<li><p>update <code>createReducer</code> function:</p>
<ul>
<li><p>pass <code>initialState</code></p>
</li>
<li><p>replace <code>switch</code> with <code>on()</code> functions (state change function)</p>
<ul>
<li><p>the <code>on(actionCreator, reducer)</code> function takes 2 arguments</p>
<ul>
<li><p><code>actionCreator</code> - the reference to the created action</p>
</li>
<li><p><code>reducer</code> - a function that takes the current state and current action and returns the updated state <code>(state, action) =&gt; ({...})</code></p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Action, createReducer, on } <span class="hljs-keyword">from</span> <span class="hljs-string">'@ngrx/store'</span>;
<span class="hljs-keyword">import</span> {
  requestCountries,
  requestCountriesError,
  requestCountriesSuccess,
} <span class="hljs-keyword">from</span> <span class="hljs-string">'./countries.actions'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> STATE_NAME = <span class="hljs-string">'countries'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> State {
  countriesResponse?: <span class="hljs-built_in">string</span>[];
  countriesLoading: <span class="hljs-built_in">boolean</span>;
  countriesError: unknown;
}

<span class="hljs-keyword">const</span> initialState: State = {
  countriesResponse: <span class="hljs-literal">undefined</span>,
  countriesLoading: <span class="hljs-literal">false</span>,
  countriesError: <span class="hljs-literal">undefined</span>,
};

<span class="hljs-keyword">const</span> reducer = createReducer(
  initialState,

  on(requestCountries, <span class="hljs-function">(<span class="hljs-params">state</span>) =&gt;</span> ({
    ...state,
    countriesResponse: <span class="hljs-literal">undefined</span>,
    countriesLoading: <span class="hljs-literal">true</span>,
    countriesError: <span class="hljs-literal">undefined</span>,
  })),
  on(requestCountriesError, <span class="hljs-function">(<span class="hljs-params">state, {error}</span>) =&gt;</span> ({
    ...state,
    countriesLoading: <span class="hljs-literal">false</span>,
    countriesError: error,
  })),
  on(requestCountriesSuccess, <span class="hljs-function">(<span class="hljs-params">state, action</span>) =&gt;</span> ({
    ...state,
    countriesResponse: action.response,
    countriesLoading: <span class="hljs-literal">false</span>,
  })),
);

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">countriesReducer</span>(<span class="hljs-params">state: State | <span class="hljs-literal">undefined</span>, action: Action</span>) </span>{
  <span class="hljs-keyword">return</span> reducer(state, action);
}
</code></pre>
<h1 id="heading-effects">Effects</h1>
<p>Before version 7, you can find the usage of <code>ofType</code> function chained directly on <code>this.actions$</code> (injected <code>Actions</code>). This syntax was dropped in version 7 in favor of <code>ofType</code> operator.</p>
<pre><code class="lang-typescript"><span class="hljs-meta">@Injectable</span>()
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> CountriesEffects {
  <span class="hljs-meta">@Effect</span>()
  someEffect$: Observable&lt;Action&gt; = <span class="hljs-built_in">this</span>.actions$
    .ofType(CountriesActionTypes.LoadCountries)
    .pipe(
      map(<span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> <span class="hljs-keyword">new</span> LoadCountriesSuccess(response)),
      catchError(<span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">of</span>(<span class="hljs-keyword">new</span> LoadCountriesFailure()))
    );

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> actions$: Actions</span>) {}
}
</code></pre>
<p>The old effects use <code>@Effect</code> decorator which was replaced by <code>createEffect()</code> function.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Injectable } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { Actions, Effect, ofType } <span class="hljs-keyword">from</span> <span class="hljs-string">'@ngrx/effects'</span>;
<span class="hljs-keyword">import</span> { Observable, <span class="hljs-keyword">of</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'rxjs'</span>;
<span class="hljs-keyword">import</span> { catchError, map, switchMap } <span class="hljs-keyword">from</span> <span class="hljs-string">'rxjs/operators'</span>;
<span class="hljs-keyword">import</span> { CountriesActionTypes, LoadCountriesFailure, LoadCountriesSuccess } <span class="hljs-keyword">from</span> <span class="hljs-string">'./countries.actions'</span>;
<span class="hljs-keyword">import</span> { CountriesService } <span class="hljs-keyword">from</span> <span class="hljs-string">'./countries.service'</span>;

<span class="hljs-meta">@Injectable</span>()
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> CountriesEffects {
  <span class="hljs-meta">@Effect</span>()
  load: Observable&lt;LoadCountriesSuccess | LoadCountriesFailure&gt; = <span class="hljs-built_in">this</span>.actions.pipe(
    ofType(CountriesActionTypes.LoadCountries),
    switchMap(<span class="hljs-function">() =&gt;</span>
      <span class="hljs-built_in">this</span>.countriesService.list().pipe(
        map(<span class="hljs-function">(<span class="hljs-params">response: <span class="hljs-built_in">string</span>[]</span>) =&gt;</span> <span class="hljs-keyword">new</span> LoadCountriesSuccess(response)),
        catchError(<span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">of</span>(<span class="hljs-keyword">new</span> LoadCountriesFailure()))
      )
    )
  );



  <span class="hljs-keyword">constructor</span>(<span class="hljs-params">
    <span class="hljs-keyword">private</span> actions: Actions,
    <span class="hljs-keyword">private</span> countriesService: CountriesService
  </span>) {
  }
}
</code></pre>
<p>To migrate:</p>
<ul>
<li><p>use <code>createEffect()</code> function instead of <code>@Effect()</code> decorator</p>
</li>
<li><p>use <code>ofType()</code> operator to filter the desired action</p>
</li>
<li><p>(minor) rename the effect property to be more verbose and end it with <code>$</code> which indicates that it's an observable</p>
</li>
</ul>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Injectable } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { Actions, createEffect, ofType } <span class="hljs-keyword">from</span> <span class="hljs-string">'@ngrx/effects'</span>;
<span class="hljs-keyword">import</span> { catchError, mergeMap, switchMap } <span class="hljs-keyword">from</span> <span class="hljs-string">'rxjs/operators'</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-keyword">of</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'rxjs'</span>;
<span class="hljs-keyword">import</span> {
  requestCountries,
  requestCountriesError,
  requestCountriesSuccess,
} <span class="hljs-keyword">from</span> <span class="hljs-string">'./countries.actions'</span>;
<span class="hljs-keyword">import</span> { CountriesService } <span class="hljs-keyword">from</span> <span class="hljs-string">'./countries.service'</span>;

<span class="hljs-meta">@Injectable</span>()
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> CountriesEffects {
  requestCountries$ = createEffect(<span class="hljs-function">() =&gt;</span>
    <span class="hljs-built_in">this</span>.actions$.pipe(
      ofType(requestCountries),
      mergeMap(<span class="hljs-function">(<span class="hljs-params">action</span>) =&gt;</span>
        <span class="hljs-built_in">this</span>.countriesService.getCountries().pipe(
          switchMap(<span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> <span class="hljs-keyword">of</span>(requestCountriesSuccess({response}))),
          catchError(<span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> <span class="hljs-keyword">of</span>(requestCountriesError({error})))
        )
      )
    )
  );

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params">
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> actions$: Actions,
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> countriesService: CountriesService,
  </span>) {
  }
}
</code></pre>
<h1 id="heading-selectors">Selectors</h1>
<p>If you were using <strong>selectors with props</strong> they might also need migration. You have to replace them with <strong>factory selectors</strong>.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> selectFirstNCountries = createSelector(
    selectCountries,
    <span class="hljs-function">(<span class="hljs-params">countries, props: { count: <span class="hljs-built_in">number</span> }</span>) =&gt;</span> {
      <span class="hljs-keyword">return</span> countries?.slice(count);
    }
);
</code></pre>
<p>To migrate:</p>
<ul>
<li>create a factory with a parameter instead of a selector with props</li>
</ul>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> selectFirstNCountries = <span class="hljs-function">(<span class="hljs-params">count: <span class="hljs-built_in">number</span></span>) =&gt;</span>
  createSelector(
    selectCountries,
    <span class="hljs-function">(<span class="hljs-params">countries</span>) =&gt;</span> {
      <span class="hljs-keyword">return</span> countries?.slice(count);
    }
  );
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Ubuntu screen flickering]]></title><description><![CDATA[For the last few weeks, I was occasionally experiencing screen flickering. It was randomly blinking black. Initially, I thought it was my laptop. This kind of problem looks like hardware malfunction at first glance. I have Dell XPS 7590 (Ubuntu 22.04...]]></description><link>https://blog.procode.pl/ubuntu-screen-flickering</link><guid isPermaLink="true">https://blog.procode.pl/ubuntu-screen-flickering</guid><category><![CDATA[Ubuntu]]></category><category><![CDATA[flickering]]></category><dc:creator><![CDATA[Bartosz Szłapak]]></dc:creator><pubDate>Mon, 03 Apr 2023 08:56:25 GMT</pubDate><content:encoded><![CDATA[<p>For the last few weeks, I was occasionally experiencing screen flickering. It was randomly blinking black. Initially, I thought it was my laptop. This kind of problem looks like hardware malfunction at first glance. I have Dell XPS 7590 (Ubuntu 22.04), which had undergone repair last year. I had some problems with the display, so I thought it might be the case.</p>
<p>Last week, the flickering became a burden. It began to happen very often and was irritating. I googled it, and it turned out, that it's an Ubuntu problem. Here's what I've done.</p>
<ol>
<li><p>Open file <code>/etc/default/grub</code>.</p>
</li>
<li><p>Find <code>GRUB_CMDLINE_LINUX_DEFAULT</code> key and add <code>i915.enable_dc=0 intel_idle.max_cstate=2 i915.enable_psr=0</code>.</p>
<p> Full line after the changes:</p>
<pre><code class="lang-text"> GRUB_CMDLINE_LINUX_DEFAULT="quiet splash i915.enable_dc=0 intel_idle.max_cstate=2 i915.enable_psr=0"
</code></pre>
</li>
<li><p>Run <code>update-grub</code> (I'm not sure if this step is necessary).</p>
</li>
<li><p>Reboot and the flickering is gone.</p>
</li>
</ol>
<p>To be honest, I've no idea what these entries do, but I'm happy that problem is solved :).</p>
]]></content:encoded></item><item><title><![CDATA[Code coverage]]></title><description><![CDATA[Code coverage is a metric that measures what part of the tested code was executed. It's relevant because it helps to find edge cases, untested or dead code. Especially, when the application grows bigger and so does the code, these metrics become very...]]></description><link>https://blog.procode.pl/code-coverage</link><guid isPermaLink="true">https://blog.procode.pl/code-coverage</guid><category><![CDATA[Testing]]></category><category><![CDATA[code coverage]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Jest]]></category><dc:creator><![CDATA[Bartosz Szłapak]]></dc:creator><pubDate>Mon, 03 Apr 2023 08:22:20 GMT</pubDate><content:encoded><![CDATA[<p>Code coverage is a metric that measures what part of the tested code was executed. It's relevant because it helps to find edge cases, untested or dead code. Especially, when the application grows bigger and so does the code, these metrics become very useful in finding untested parts.</p>
<p>To execute tests with code coverage simply run:</p>
<pre><code class="lang-shell">npx jest --coverage
</code></pre>
<p>The Jest will display a summary table in the shell. By default, the HTML report will be generated in the <code>./coverage</code> directory. Navigate to <code>./coverage/lcov-report</code> and open <code>index.html</code> in the browser to view it.</p>
<p>For all available options and configurations refer to the official Jest documentation https://jestjs.io.</p>
<h1 id="heading-requirements">Requirements</h1>
<ul>
<li><p>NodeJS v16</p>
</li>
<li><p>NPM v8</p>
</li>
<li><p>npx</p>
</li>
</ul>
<h1 id="heading-setup">Setup</h1>
<p>Install dependencies:</p>
<pre><code class="lang-bash">npm i jest@29 @types/jest@29 ts-jest@29 typescript@4.7
</code></pre>
<p>Initialize the default <strong>ts-jest</strong> configuration.</p>
<pre><code class="lang-bash">npx ts-jest config:init
</code></pre>
<h1 id="heading-example">Example</h1>
<p>Assume, that you have two files:</p>
<ul>
<li><p><code>functions.ts</code></p>
</li>
<li><p><code>functions.spec.ts</code></p>
</li>
</ul>
<p>Take a look at the following code. It's supposed to sum all the passed numbers and return a result.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// functions.ts</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> sum = (values: <span class="hljs-built_in">number</span>[]): <span class="hljs-function"><span class="hljs-params">number</span>  =&gt;</span> {
  <span class="hljs-keyword">return</span> values.reduce(<span class="hljs-function">(<span class="hljs-params">previous, current</span>) =&gt;</span> previous + current, <span class="hljs-number">0</span>);
}
</code></pre>
<p>The test file:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// functions.spec.ts</span>

<span class="hljs-keyword">import</span> { sum } <span class="hljs-keyword">from</span> <span class="hljs-string">'./functions'</span>;


<span class="hljs-keyword">interface</span> Dataset {
  input: <span class="hljs-built_in">number</span>[];
  result: <span class="hljs-built_in">number</span>;
}

<span class="hljs-keyword">const</span> datasets: Dataset[] = [
  {input: [<span class="hljs-number">0</span>], result: <span class="hljs-number">0</span>},
  {input: [<span class="hljs-number">0</span>, <span class="hljs-number">1</span>], result: <span class="hljs-number">1</span>},
  {input: [<span class="hljs-number">2</span>, <span class="hljs-number">6</span>], result: <span class="hljs-number">8</span>},
  {input: [<span class="hljs-number">-9</span>, <span class="hljs-number">0</span>], result: <span class="hljs-number">-9</span>},
  {input: [<span class="hljs-number">-9</span>, <span class="hljs-number">-2</span>], result: <span class="hljs-number">-11</span>},
  {input: [<span class="hljs-number">-9</span>, <span class="hljs-number">100</span>], result: <span class="hljs-number">91</span>},
]

describe(<span class="hljs-string">'Test sum function'</span>, <span class="hljs-function">() =&gt;</span> {
  it.each(datasets)(<span class="hljs-string">'dataset: %j'</span>, <span class="hljs-function">(<span class="hljs-params">dataset: Dataset</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> result = sum(...dataset.input);
    expect(result).toBe(dataset.result);
  });
});
</code></pre>
<p>Run <code>npx jest --coverage</code>. All tests passed and the coverage shows 100%.</p>
<pre><code class="lang-text">--------------|---------|----------|---------|---------|-------------------
File          | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
--------------|---------|----------|---------|---------|-------------------
All files     |     100 |      100 |     100 |     100 |                   
 functions.ts |     100 |      100 |     100 |     100 |                   
--------------|---------|----------|---------|---------|-------------------
</code></pre>
<p>Next, update the definition of the <code>sum</code> function by introducing argument validation.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// functions.ts</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> sum = (values: <span class="hljs-built_in">number</span>[]): <span class="hljs-built_in">number</span> | <span class="hljs-function"><span class="hljs-params">never</span> =&gt;</span> {
  values.forEach(<span class="hljs-function">(<span class="hljs-params">index, value</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">Number</span>.isFinite(value)) {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">`Provided argument at index:<span class="hljs-subst">${index}</span>, value:<span class="hljs-subst">${<span class="hljs-built_in">String</span>(value)}</span> is invalid`</span>);
    }
  });

  <span class="hljs-keyword">const</span> result = values.reduce(<span class="hljs-function">(<span class="hljs-params">previous, current</span>) =&gt;</span> previous + current, <span class="hljs-number">0</span>);

  <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">Number</span>.isFinite(result)) {
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'The result exceeded supported numeric values'</span>);
  }

  <span class="hljs-keyword">return</span> result;
}
</code></pre>
<p>Run <code>npx jest --coverage</code>. All tests passed, but not all code paths were tested. A quick glimpse at the report shows, that lines 4 and 11 were not executed.</p>
<pre><code class="lang-text">--------------|---------|----------|---------|---------|-------------------
File          | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
--------------|---------|----------|---------|---------|-------------------
All files     |      80 |        0 |     100 |      75 |                   
 functions.ts |      80 |        0 |     100 |      75 | 4,11              
--------------|---------|----------|---------|---------|-------------------
</code></pre>
<p>To test the new code paths create separate tests that cause the exceptions. There are a couple of new cases to cover:</p>
<ul>
<li><p>pass <code>null</code>, <code>undefined</code>, <code>NaN</code>, <code>Infinity</code> or <code>-Infinity</code></p>
</li>
<li><p>make the sum of arguments resulting in <code>Infinity</code> or <code>-Infinity</code></p>
</li>
</ul>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { sum } <span class="hljs-keyword">from</span> <span class="hljs-string">'./functions'</span>;


<span class="hljs-keyword">interface</span> Dataset {
  input: <span class="hljs-built_in">number</span>[];
  result: <span class="hljs-built_in">number</span>;
}

<span class="hljs-keyword">const</span> datasets: Dataset[] = [
  {input: [<span class="hljs-number">0</span>], result: <span class="hljs-number">0</span>},
  {input: [<span class="hljs-number">0</span>, <span class="hljs-number">1</span>], result: <span class="hljs-number">1</span>},
  {input: [<span class="hljs-number">2</span>, <span class="hljs-number">6</span>], result: <span class="hljs-number">8</span>},
  {input: [<span class="hljs-number">-9</span>, <span class="hljs-number">0</span>], result: <span class="hljs-number">-9</span>},
  {input: [<span class="hljs-number">-9</span>, <span class="hljs-number">-2</span>], result: <span class="hljs-number">-11</span>},
  {input: [<span class="hljs-number">-9</span>, <span class="hljs-number">100</span>], result: <span class="hljs-number">91</span>},
];

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">formatMessage</span>(<span class="hljs-params">index: <span class="hljs-built_in">number</span>, value: <span class="hljs-built_in">number</span></span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-string">`Invalid argument: index:<span class="hljs-subst">${index}</span>, value:<span class="hljs-subst">${<span class="hljs-built_in">String</span>(value)}</span>`</span>;
}

<span class="hljs-keyword">interface</span> ExceptionDataset {
  input: <span class="hljs-built_in">number</span>[],
  errorMessage: <span class="hljs-built_in">string</span>;
}

<span class="hljs-keyword">const</span> exceptionDatasets: ExceptionDataset[] = [
  {
    input: [<span class="hljs-literal">null</span>],
    errorMessage: formatMessage(<span class="hljs-number">0</span>, <span class="hljs-literal">null</span>)
  },
  {
    input: [<span class="hljs-literal">undefined</span>],
    errorMessage: formatMessage(<span class="hljs-number">0</span>, <span class="hljs-literal">undefined</span>)
  },
  {
    input: [<span class="hljs-literal">NaN</span>],
    errorMessage: formatMessage(<span class="hljs-number">0</span>, <span class="hljs-literal">NaN</span>)
  },
  {
    input: [<span class="hljs-literal">Infinity</span>],
    errorMessage: formatMessage(<span class="hljs-number">0</span>, <span class="hljs-literal">Infinity</span>)
  },
  {
    input: [-<span class="hljs-literal">Infinity</span>],
    errorMessage: formatMessage(<span class="hljs-number">0</span>, -<span class="hljs-literal">Infinity</span>)
  },
  {
    input: [<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-literal">NaN</span>],
    errorMessage: formatMessage(<span class="hljs-number">3</span>, <span class="hljs-literal">NaN</span>)
  },
  {
    input: [<span class="hljs-number">1</span>, <span class="hljs-literal">null</span>, <span class="hljs-number">2</span>],
    errorMessage: formatMessage(<span class="hljs-number">1</span>, <span class="hljs-literal">null</span>)
  },
  {
    input: [<span class="hljs-number">1</span>, <span class="hljs-literal">null</span>, <span class="hljs-number">2</span>, <span class="hljs-literal">undefined</span>, <span class="hljs-number">3</span>, <span class="hljs-literal">NaN</span>],
    errorMessage: formatMessage(<span class="hljs-number">1</span>, <span class="hljs-literal">null</span>)
  },
  {
    input: [<span class="hljs-built_in">Math</span>.pow(<span class="hljs-number">2</span>, <span class="hljs-number">1023</span>), <span class="hljs-built_in">Math</span>.pow(<span class="hljs-number">2</span>, <span class="hljs-number">1023</span>)],
    errorMessage: <span class="hljs-string">'The result exceeded supported numeric values'</span>
  },
  {
    input: [-<span class="hljs-built_in">Math</span>.pow(<span class="hljs-number">2</span>, <span class="hljs-number">1023</span>), -<span class="hljs-built_in">Math</span>.pow(<span class="hljs-number">2</span>, <span class="hljs-number">1023</span>)],
    errorMessage: <span class="hljs-string">'The result exceeded supported numeric values'</span>
  },
];

describe(<span class="hljs-string">'Test sum function'</span>, <span class="hljs-function">() =&gt;</span> {
  it.each(datasets)(<span class="hljs-string">'dataset: %j'</span>, <span class="hljs-function">(<span class="hljs-params">dataset: Dataset</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> result = sum(dataset.input);
    expect(result).toBe(dataset.result);
  });

  it.each(exceptionDatasets)(<span class="hljs-string">'dataset: %j'</span>, <span class="hljs-function">(<span class="hljs-params">dataset: ExceptionDataset</span>) =&gt;</span> {
    expect(<span class="hljs-function">() =&gt;</span> sum(dataset.input)).toThrow(dataset.errorMessage);
  });
});
</code></pre>
<p>Run the tests with coverage and now all the paths are tested.</p>
<pre><code class="lang-text">  Test sum function
    ✓ dataset: {"input":[0],"result":0}
    ✓ dataset: {"input":[0,1],"result":1}
    ✓ dataset: {"input":[2,6],"result":8}
    ✓ dataset: {"input":[-9,0],"result":-9}
    ✓ dataset: {"input":[-9,-2],"result":-11}
    ✓ dataset: {"input":[-9,100],"result":91}
    ✓ dataset: {"input":[null],"errorMessage":"Invalid argument: index:0, value:null"} 
    ✓ dataset: {"input":[null],"errorMessage":"Invalid argument: index:0, value:undefined"}
    ✓ dataset: {"input":[null],"errorMessage":"Invalid argument: index:0, value:NaN"}
    ✓ dataset: {"input":[null],"errorMessage":"Invalid argument: index:0, value:Infinity"} 
    ✓ dataset: {"input":[null],"errorMessage":"Invalid argument: index:0, value:-Infinity"} 
    ✓ dataset: {"input":[0,0,0,null],"errorMessage":"Invalid argument: index:3, value:NaN"}
    ✓ dataset: {"input":[1,null,2],"errorMessage":"Invalid argument: index:1, value:null"}
    ✓ dataset: {"input":[1,null,2,null,3,null],"errorMessage":"Invalid argument: index:1, value:null"} (
    ✓ dataset: {"input":[8.98846567431158e+307,8.98846567431158e+307],"errorMessage":"The result exceeded supported numeric values"} 
    ✓ dataset: {"input":[-8.98846567431158e+307,-8.98846567431158e+307],"errorMessage":"The result exceeded supported numeric values"} 

--------------|---------|----------|---------|---------|-------------------
File          | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
--------------|---------|----------|---------|---------|-------------------
All files     |     100 |      100 |     100 |     100 |                   
 functions.ts |     100 |      100 |     100 |     100 |                   
--------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       16 passed, 16 total
</code></pre>
<p>A quick note about <code>%j</code> formatting. Due to the fact, that the value is converted to JSON we lose some details. Since the JSON format doesn't support values like <code>NaN</code>, <code>undefined</code> or <code>Infinity</code> they are all converted to <code>null</code>. Take a look at the message:</p>
<pre><code class="lang-text">dataset: {"input":[null],"errorMessage":"Invalid argument: index:0, value:undefined"}
</code></pre>
<p>The input is <code>[null]</code> but the value in the error message is <code>undefined</code>. So the input's <code>[null]</code> was originally <code>[undefined]</code> but the information was lost during the conversion. In the message, you can see <code>index:0, value:undefined"</code> because that message was generated manually while the dataset was built. Since it's sufficient for this article, you should consider using a different approach to formatting when writing your tests. This might save you some headaches in the future.</p>
<h1 id="heading-summary">Summary</h1>
<p>In this article, I've shown you how to use code coverage and how can it be useful. When your application and the team grow bigger, and the code becomes more complex, the usage of code coverage becomes the natural part of the testing process.</p>
]]></content:encoded></item><item><title><![CDATA[Getting started testing with Jest]]></title><description><![CDATA[The methodology
This series will be based on the black box testing methodology. What does that even mean? It means that there is input data provided, then internal processing occurs (a.k.a. black box) and results are produced. The tester is not aware...]]></description><link>https://blog.procode.pl/getting-started-testing-with-jest</link><guid isPermaLink="true">https://blog.procode.pl/getting-started-testing-with-jest</guid><category><![CDATA[TypeScript]]></category><category><![CDATA[Jest]]></category><category><![CDATA[Testing]]></category><dc:creator><![CDATA[Bartosz Szłapak]]></dc:creator><pubDate>Thu, 30 Mar 2023 18:08:29 GMT</pubDate><content:encoded><![CDATA[<h1 id="heading-the-methodology">The methodology</h1>
<p>This series will be based on the <strong>black box testing</strong> methodology. What does that even mean? It means that there is input <strong>data</strong> provided, then internal processing occurs (a.k.a. black box) and results are produced. The tester is not aware of how the internals work but only knows what the app should do.</p>
<pre><code class="lang-bash">input -&gt; [ black box ] -&gt; output
</code></pre>
<p>Take a look at a simple web page.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">button</span>&gt;</span>Add<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">table</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">thead</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">th</span>&gt;</span>value<span class="hljs-tag">&lt;/<span class="hljs-name">th</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">thead</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">tbody</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">tbody</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">table</span>&gt;</span>
</code></pre>
<p>As a tester I know that:</p>
<ul>
<li><p>when I fill the input field (input)</p>
</li>
<li><p>and click Add button (input)</p>
</li>
<li><p>it should block the input field and the button (output)</p>
</li>
<li><p>send HTTP request (output)</p>
</li>
<li><p>add new a new record to the table (output)</p>
</li>
<li><p>clear input field data (output)</p>
</li>
<li><p>unblock the input field and the button (output)</p>
</li>
</ul>
<p>I don't care how the app is going to do all that stuff, but I do care about the user interface and sent requests. And that's what can and should be tested.</p>
<h1 id="heading-the-tools">The tools</h1>
<h2 id="heading-requirements">Requirements</h2>
<ul>
<li><p>node 16</p>
</li>
<li><p>npm 8</p>
</li>
<li><p>npx</p>
</li>
</ul>
<h2 id="heading-setup">Setup</h2>
<p>The series will be using Jest as the default testing framework. Jest is a fast, widely used and well-documented testing framework. The documentation can be found here: https://jestjs.io. For testing typescript files <code>ts-jest</code> library is required.</p>
<p>Initialize <strong>npm</strong> with default settings.</p>
<pre><code class="lang-bash">npm init -y
</code></pre>
<p>Install required dependencies. For testing: <strong>jest</strong> and <strong>ts-jest</strong>. For compilation: <strong>typescript</strong>. For observables: RxJS</p>
<pre><code class="lang-bash">npm i jest@29 @types/jest@29 ts-jest@29 typescript@4.7 rxjs@7
</code></pre>
<p>Initialize the default <strong>ts-jest</strong> configuration.</p>
<pre><code class="lang-bash">npx ts-jest config:init
</code></pre>
<h1 id="heading-a-simple-test">A simple test</h1>
<p>Assume you want to test the following class:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">class</span> Formatter {
  whitespaceToUnderscore(input: <span class="hljs-built_in">string</span>): <span class="hljs-built_in">string</span> {
    <span class="hljs-keyword">return</span> input.split(<span class="hljs-string">''</span>).join(<span class="hljs-string">'_'</span>);
  }
}
</code></pre>
<p>So the test might look like this:</p>
<pre><code class="lang-typescript">describe(<span class="hljs-string">'Test Formatter'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">let</span> formatter: Formatter;

  beforeAll(<span class="hljs-function">() =&gt;</span> {
    formatter = <span class="hljs-keyword">new</span> Formatter();
  });

  it(<span class="hljs-string">'should convert "This is and example input" to "This_is_and_example_input"'</span>, <span class="hljs-function">() =&gt;</span> {
    expect(formatter.whitespaceToUnderscore(<span class="hljs-string">'This is and example input'</span>)).toBe(<span class="hljs-string">'This_is_and_example_input'</span>);
  });
});
</code></pre>
<p>Let's break it down.</p>
<ul>
<li><p>The <code>describe</code> is a block that groups the related tests or other <code>describe</code> blocks,</p>
</li>
<li><p>The <code>beforeAll</code> is a block that is executed once in the <code>describe</code> block. Since you don't want to create Formatter instance each time it's more performant just to create it once.</p>
</li>
<li><p>The <code>it</code> (a.k.a. test) is a block that contains expectations (preferably one expectation per <code>it</code> block),</p>
</li>
<li><p>The <code>expect</code> is a function that takes input (i.e. the actual result of a method call). Next, it is chained with the <code>matcher</code>. The <code>matcher</code> is a function that takes a value, that you expect to receive and compares it with <code>expect's</code> argument. In this particular case, it's <code>toBe</code> matcher, which is used for comparing primitive types.</p>
</li>
</ul>
<h1 id="heading-jest-basics">Jest basics</h1>
<h2 id="heading-primitives">Primitives</h2>
<p>To test primitives it's enough to use <code>toBe</code> matcher.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> inputString = <span class="hljs-string">'my example'</span>;
<span class="hljs-keyword">const</span> inputFloat = <span class="hljs-number">512.1241</span>;
<span class="hljs-keyword">const</span> inputInteger = <span class="hljs-number">999</span>;
<span class="hljs-keyword">const</span> inputBoolean = <span class="hljs-literal">false</span>;
<span class="hljs-keyword">const</span> inputNull = <span class="hljs-literal">null</span>;
<span class="hljs-keyword">const</span> inputUndefined = <span class="hljs-literal">undefined</span>;
<span class="hljs-keyword">const</span> inputNaN = <span class="hljs-literal">NaN</span>;


describe(<span class="hljs-string">'Test primitives'</span>, <span class="hljs-function">() =&gt;</span> {

  it(<span class="hljs-string">'should test string'</span>, <span class="hljs-function">() =&gt;</span> {
    expect(inputString).toBe(<span class="hljs-string">'my example'</span>);
  });

  it(<span class="hljs-string">'should test float'</span>, <span class="hljs-function">() =&gt;</span> {
    expect(inputFloat).toBe(<span class="hljs-number">512.1241</span>);
  });

  it(<span class="hljs-string">'should test integer'</span>, <span class="hljs-function">() =&gt;</span> {
    expect(inputInteger).toBe(<span class="hljs-number">999</span>);
  });

  it(<span class="hljs-string">'should test boolean'</span>, <span class="hljs-function">() =&gt;</span> {
    expect(inputBoolean).toBe(<span class="hljs-literal">false</span>);
  });

  it(<span class="hljs-string">'should test null'</span>, <span class="hljs-function">() =&gt;</span> {
    expect(inputNull).toBe(<span class="hljs-literal">null</span>);
  });

  it(<span class="hljs-string">'should test undefined'</span>, <span class="hljs-function">() =&gt;</span> {
    expect(inputUndefined).toBe(<span class="hljs-literal">undefined</span>);
  });

  it(<span class="hljs-string">'should test NaN'</span>, <span class="hljs-function">() =&gt;</span> {
    expect(inputNaN).toBe(<span class="hljs-literal">NaN</span>);
  });
});
</code></pre>
<p>When the value doesn't matter, but you need to check whether it's a false or true when compared to a boolean, use <code>toBeFalsy()</code> or <code>toBeTruthy()</code>;</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> inputString = <span class="hljs-string">'my example'</span>;
<span class="hljs-keyword">const</span> inputEmptyString = <span class="hljs-string">''</span>;
<span class="hljs-keyword">const</span> inputZeroInteger = <span class="hljs-number">0</span>;
<span class="hljs-keyword">const</span> inputZeroFloat = <span class="hljs-number">0.0</span>;
<span class="hljs-keyword">const</span> inputFloat = <span class="hljs-number">512.1241</span>;
<span class="hljs-keyword">const</span> inputInteger = <span class="hljs-number">999</span>;
<span class="hljs-keyword">const</span> inputBooleanFalse = <span class="hljs-literal">false</span>;
<span class="hljs-keyword">const</span> inputBooleanTrue = <span class="hljs-literal">true</span>;
<span class="hljs-keyword">const</span> inputNull = <span class="hljs-literal">null</span>;
<span class="hljs-keyword">const</span> inputUndefined = <span class="hljs-literal">undefined</span>;
<span class="hljs-keyword">const</span> inputNaN = <span class="hljs-literal">NaN</span>;


describe(<span class="hljs-string">'Test primitives'</span>, <span class="hljs-function">() =&gt;</span> {

  it(<span class="hljs-string">'should test string'</span>, <span class="hljs-function">() =&gt;</span> {
    expect(inputString).toBeTruthy();
    expect(inputEmptyString).toBeFalsy();
  });

  it(<span class="hljs-string">'should test float'</span>, <span class="hljs-function">() =&gt;</span> {
    expect(inputFloat).toBeTruthy();
    expect(inputZeroFloat).toBeFalsy();
  });

  it(<span class="hljs-string">'should test integer'</span>, <span class="hljs-function">() =&gt;</span> {
    expect(inputInteger).toBeTruthy();
    expect(inputZeroInteger).toBeFalsy();
  });

  it(<span class="hljs-string">'should test boolean'</span>, <span class="hljs-function">() =&gt;</span> {
    expect(inputBooleanFalse).toBeFalsy();
    expect(inputBooleanTrue).toBeTruthy();
  });

  it(<span class="hljs-string">'should test null'</span>, <span class="hljs-function">() =&gt;</span> {
    expect(inputNull).toBeFalsy();
  });

  it(<span class="hljs-string">'should test undefined'</span>, <span class="hljs-function">() =&gt;</span> {
    expect(inputUndefined).toBeFalsy();
  });

  it(<span class="hljs-string">'should test NaN'</span>, <span class="hljs-function">() =&gt;</span> {
    expect(inputNaN).toBeFalsy();
  });
});
</code></pre>
<p>For <code>NaN</code>, <code>null</code> and <code>undefined</code> there are corresponding matchers <code>toBeNan()</code>, <code>toBeNull()</code> and <code>toBeUndefined()</code>. It's the same as using <code>toBe()</code> matcher, but more verbose.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> inputNull = <span class="hljs-literal">null</span>;
<span class="hljs-keyword">const</span> inputUndefined = <span class="hljs-literal">undefined</span>;
<span class="hljs-keyword">const</span> inputNaN = <span class="hljs-literal">NaN</span>;


describe(<span class="hljs-string">'Test primitives'</span>, <span class="hljs-function">() =&gt;</span> {

  it(<span class="hljs-string">'should test null'</span>, <span class="hljs-function">() =&gt;</span> {
    expect(inputNull).toBeNull();
  });

  it(<span class="hljs-string">'should test undefined'</span>, <span class="hljs-function">() =&gt;</span> {
    expect(inputUndefined).toBeUndefined();
  });

  it(<span class="hljs-string">'should test NaN'</span>, <span class="hljs-function">() =&gt;</span> {
    expect(inputNaN).toBeNaN();
  });
});
</code></pre>
<h2 id="heading-arrays">Arrays</h2>
<p>To deeply compare arrays use <code>toEqual()</code> matcher.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> inputEmptyArray = [];
<span class="hljs-keyword">const</span> inputSimpleArray = [<span class="hljs-number">1</span>, <span class="hljs-string">'2'</span>];
<span class="hljs-keyword">const</span> inputSparseArray = [<span class="hljs-literal">undefined</span>, <span class="hljs-number">1</span>, <span class="hljs-literal">undefined</span>, <span class="hljs-string">'2'</span>];
<span class="hljs-keyword">const</span> inputNestedArray = [<span class="hljs-number">1</span>, <span class="hljs-string">'2'</span>, [<span class="hljs-string">'33'</span>, {color: <span class="hljs-string">'#fff'</span>}]];


describe(<span class="hljs-string">'Test array equality'</span>, <span class="hljs-function">() =&gt;</span> {

  it(<span class="hljs-string">'should test empty array'</span>, <span class="hljs-function">() =&gt;</span> {
    expect(inputEmptyArray).toEqual([]);
  });

  it(<span class="hljs-string">'should test simple array'</span>, <span class="hljs-function">() =&gt;</span> {
    expect(inputSimpleArray).toEqual([<span class="hljs-number">1</span>, <span class="hljs-string">'2'</span>]);
  });

  it(<span class="hljs-string">'should test sparse array'</span>, <span class="hljs-function">() =&gt;</span> {
    expect(inputSparseArray).toEqual([<span class="hljs-literal">undefined</span>, <span class="hljs-number">1</span>, <span class="hljs-literal">undefined</span>, <span class="hljs-string">'2'</span>]);
  });

  it(<span class="hljs-string">'should test nested array'</span>, <span class="hljs-function">() =&gt;</span> {
    expect(inputNestedArray).toEqual([<span class="hljs-number">1</span>, <span class="hljs-string">'2'</span>, [<span class="hljs-string">'33'</span>, {color: <span class="hljs-string">'#fff'</span>}]]);
  });
});
</code></pre>
<h2 id="heading-objects-and-classes">Objects and classes</h2>
<p>To deeply compare objects use <code>toEqual()</code> matcher.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> inputEmptyObject = {};
<span class="hljs-keyword">const</span> inputSimpleObject = {color: <span class="hljs-string">'#fff'</span>};
<span class="hljs-keyword">const</span> inputSparseObject = {color: <span class="hljs-string">'#fff'</span>, border: <span class="hljs-literal">undefined</span>};
<span class="hljs-keyword">const</span> inputNestedObject = {color: <span class="hljs-string">'#fff'</span>, margin: {top: <span class="hljs-string">'25px'</span>, left: <span class="hljs-string">'5px'</span>}};


describe(<span class="hljs-string">'Test object equality'</span>, <span class="hljs-function">() =&gt;</span> {

  it(<span class="hljs-string">'should test empty object'</span>, <span class="hljs-function">() =&gt;</span> {
    expect(inputEmptyObject).toEqual({});
  });

  it(<span class="hljs-string">'should test simple object'</span>, <span class="hljs-function">() =&gt;</span> {
    expect(inputSimpleObject).toEqual({color: <span class="hljs-string">'#fff'</span>});
  });

  it(<span class="hljs-string">'should test sparse object'</span>, <span class="hljs-function">() =&gt;</span> {
    expect(inputSparseObject).toEqual({color: <span class="hljs-string">'#fff'</span>, border: <span class="hljs-literal">undefined</span>});
  });

  it(<span class="hljs-string">'should test nested object'</span>, <span class="hljs-function">() =&gt;</span> {
    expect(inputNestedObject).toEqual({color: <span class="hljs-string">'#fff'</span>, margin: {top: <span class="hljs-string">'25px'</span>, left: <span class="hljs-string">'5px'</span>}});
  });
});
</code></pre>
<p>To check whether an object is an instance of a certain class use <code>toBeInstanceOf()</code> matcher.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">class</span> ExampleClass {}


describe(<span class="hljs-string">'Test class instance'</span>, <span class="hljs-function">() =&gt;</span> {
  it(<span class="hljs-string">'should test empty object'</span>, <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> myClass = <span class="hljs-keyword">new</span> ExampleClass();
    expect(myClass).toBeInstanceOf(ExampleClass);
  });
});
</code></pre>
<h2 id="heading-method-calls">Method calls</h2>
<p>To track the method calls you need to use a <code>spy</code>. By default, the spied method will be executed normally as it's defined in the code. The first parameter for <code>jest.spyOn()</code> is a class instance. The second is the name of the tracked method.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">class</span> Logger {
  log(data: unknown): <span class="hljs-built_in">void</span> {
    ...
  }
}

logger = <span class="hljs-keyword">new</span> Logger();
jest.spyOn(logger, <span class="hljs-string">'log'</span>);
</code></pre>
<p>When spies are set, you can use specific matchers to check how or if at all, all the methods were called.</p>
<p>To check only if a method was called, use <code>toHaveBeenCalled()</code> matcher.</p>
<pre><code class="lang-typescript">expect(logger.log).toHaveBeenCalled();
</code></pre>
<p>To check if a method was called and returned a specific value, use <code>toHaveReturnedWith()</code> matcher.</p>
<pre><code class="lang-typescript">expect(headphones.getVolume).toHaveReturnedWith(<span class="hljs-number">0.5</span>);
</code></pre>
<p>If a method is called multiple times, you can verify all consecutive calls by using <code>toHaveBeenNthCalledWith()</code>. The first argument is the number of the consecutive call, the second is the expected result.</p>
<pre><code class="lang-typescript">expect(logger.log).toHaveBeenNthCalledWith(<span class="hljs-number">1</span>, {text: <span class="hljs-string">'message-1'</span>});
expect(logger.log).toHaveBeenNthCalledWith(<span class="hljs-number">2</span>, {text: <span class="hljs-string">'message-2'</span>});
expect(logger.log).toHaveBeenNthCalledWith(<span class="hljs-number">3</span>, {text: <span class="hljs-string">'message-3'</span>});
</code></pre>
<p>Below you can find a fully working example.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Subject } <span class="hljs-keyword">from</span> <span class="hljs-string">'rxjs'</span>;

<span class="hljs-keyword">class</span> Headphones {
  <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> maxVolume = <span class="hljs-number">1</span>;
  <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> minVolume = <span class="hljs-number">0</span>;
  <span class="hljs-keyword">private</span> volume = <span class="hljs-number">0.5</span>;
  <span class="hljs-keyword">private</span> volumeSubject = <span class="hljs-keyword">new</span> Subject&lt;<span class="hljs-built_in">number</span>&gt;();
  <span class="hljs-keyword">readonly</span> volumeChanges$ = <span class="hljs-built_in">this</span>.volumeSubject.asObservable();

  volumeUp(): <span class="hljs-built_in">void</span> {
    <span class="hljs-built_in">this</span>.changeVolume(<span class="hljs-number">0.1</span>);
  }

  volumeDown(): <span class="hljs-built_in">void</span> {
    <span class="hljs-built_in">this</span>.changeVolume(<span class="hljs-number">-0.1</span>);
  }

  getVolume(): <span class="hljs-built_in">number</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.volume;
  }

  <span class="hljs-keyword">private</span> changeVolume(value: <span class="hljs-built_in">number</span>): <span class="hljs-built_in">void</span> {
    <span class="hljs-built_in">this</span>.volume = <span class="hljs-built_in">this</span>.calculateVolumeLevel(value);
    <span class="hljs-built_in">this</span>.volumeSubject.next(<span class="hljs-built_in">this</span>.volume);
  }

  <span class="hljs-keyword">private</span> calculateVolumeLevel(value: <span class="hljs-built_in">number</span>): <span class="hljs-built_in">number</span> {
    <span class="hljs-keyword">let</span> updatedVolume = <span class="hljs-built_in">this</span>.volume + value;

    <span class="hljs-keyword">if</span> (updatedVolume &lt; <span class="hljs-built_in">this</span>.minVolume) {
      <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.minVolume;
    }

    <span class="hljs-keyword">if</span> (updatedVolume &gt; <span class="hljs-built_in">this</span>.maxVolume) {
      <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.maxVolume;
    }

    <span class="hljs-keyword">return</span> updatedVolume;
  }
}

<span class="hljs-keyword">class</span> Logger {
  log(data: unknown): <span class="hljs-built_in">void</span> {
    <span class="hljs-comment">// ...</span>
  }
}


describe(<span class="hljs-string">'Test class calls'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">let</span> headphones: Headphones;
  <span class="hljs-keyword">let</span> logger: Logger;

  beforeEach(<span class="hljs-function">() =&gt;</span> {
    headphones = <span class="hljs-keyword">new</span> Headphones();
    logger = <span class="hljs-keyword">new</span> Logger();
    jest.spyOn(headphones, <span class="hljs-string">'getVolume'</span>);
    jest.spyOn(logger, <span class="hljs-string">'log'</span>);
  });

  it(<span class="hljs-string">'getVolume should be called and return 0.5'</span>, <span class="hljs-function">() =&gt;</span> {
    headphones.getVolume();
    expect(headphones.getVolume).toHaveReturnedWith(<span class="hljs-number">0.5</span>);
  });

  it(<span class="hljs-string">'should call logger once upon a volume change'</span>, <span class="hljs-function">() =&gt;</span> {
    headphones.volumeChanges$.subscribe(<span class="hljs-function"><span class="hljs-params">volume</span> =&gt;</span> {
      <span class="hljs-keyword">const</span> data = {event: <span class="hljs-string">'VolumeChange'</span>, value: volume};
      logger.log(data);
    });

    headphones.volumeUp();
    expect(logger.log).toHaveBeenCalled();
  });

  it(<span class="hljs-string">'should call logger with payload once upon a volume change'</span>, <span class="hljs-function">() =&gt;</span> {
    headphones.volumeChanges$.subscribe(<span class="hljs-function"><span class="hljs-params">volume</span> =&gt;</span> {
      <span class="hljs-keyword">const</span> data = {event: <span class="hljs-string">'VolumeChange'</span>, value: volume};
      logger.log(data);
    });

    headphones.volumeUp();
    <span class="hljs-keyword">const</span> expected = {event: <span class="hljs-string">'VolumeChange'</span>, value: <span class="hljs-number">0.6</span>};
    expect(logger.log).toHaveBeenCalledWith(expected)
  });

  it(<span class="hljs-string">'should call logger 3 times upon a volume changes'</span>, <span class="hljs-function">() =&gt;</span> {
    headphones.volumeChanges$.subscribe(<span class="hljs-function"><span class="hljs-params">volume</span> =&gt;</span> {
      <span class="hljs-keyword">const</span> data = {event: <span class="hljs-string">'VolumeChange'</span>, value: volume};
      logger.log(data);
    });

    headphones.volumeUp();
    headphones.volumeUp();
    headphones.volumeDown();
    <span class="hljs-keyword">const</span> expected1 = {event: <span class="hljs-string">'VolumeChange'</span>, value: <span class="hljs-number">0.6</span>};
    expect(logger.log).toHaveBeenNthCalledWith(<span class="hljs-number">1</span>, expected1);
    <span class="hljs-keyword">const</span> expected2 = {event: <span class="hljs-string">'VolumeChange'</span>, value: <span class="hljs-number">0.7</span>};
    expect(logger.log).toHaveBeenNthCalledWith(<span class="hljs-number">2</span>, expected2);
    <span class="hljs-keyword">const</span> expected3 = {event: <span class="hljs-string">'VolumeChange'</span>, value: <span class="hljs-number">0.6</span>};
    expect(logger.log).toHaveBeenNthCalledWith(<span class="hljs-number">3</span>, expected3);
  });
});
</code></pre>
<h2 id="heading-exceptions-errors">Exceptions (errors)</h2>
<p>There are a few basic ways to test whether an exception was thrown:</p>
<ul>
<li><p>test if any exception was thrown</p>
</li>
<li><p>test if a certain exception class was thrown</p>
</li>
<li><p>test if an exception is an object with certain <code>message</code> property</p>
</li>
<li><p>test if a <code>string</code> was thrown</p>
</li>
</ul>
<p>To test an exception, you have to wrap the code in a function, otherwise, the exception will not be caught and the assertion will fail. Instead of</p>
<pre><code class="lang-typescript">expect(throwInvalidNumberException()).toThrow();
</code></pre>
<p>you need to use something like</p>
<pre><code class="lang-typescript">expect(<span class="hljs-function">() =&gt;</span> throwInvalidNumberException()).toThrow();
</code></pre>
<p>Let's assume that all tests contain the following declarations:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">class</span> AppException <span class="hljs-keyword">extends</span> <span class="hljs-built_in">Error</span> {

}

<span class="hljs-keyword">class</span> InvalidNumberException <span class="hljs-keyword">extends</span> AppException {

}

<span class="hljs-keyword">const</span> throwInvalidNumberException = (): <span class="hljs-function"><span class="hljs-params">never</span> =&gt;</span> {
  <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> InvalidNumberException(<span class="hljs-string">'Provided number is invalid'</span>);
}

<span class="hljs-keyword">const</span> throwGenericError = (): <span class="hljs-function"><span class="hljs-params">never</span> =&gt;</span> {
  <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Unknown error'</span>);
}

<span class="hljs-keyword">const</span> throwString = (): <span class="hljs-function"><span class="hljs-params">never</span> =&gt;</span> {
  <span class="hljs-keyword">throw</span> <span class="hljs-string">'Unexpected error'</span>;
}
</code></pre>
<p>To test if any exception was thrown it's enough to use <code>toThrow()</code> matcher without any argument.</p>
<pre><code class="lang-typescript">describe(<span class="hljs-string">'Test any exception'</span>, <span class="hljs-function">() =&gt;</span> {
  it(<span class="hljs-string">'should throw InvalidNumberException'</span>, <span class="hljs-function">() =&gt;</span> {
    expect(<span class="hljs-function">() =&gt;</span> throwInvalidNumberException()).toThrow();
  });

  it(<span class="hljs-string">'should throw generic error'</span>, <span class="hljs-function">() =&gt;</span> {
    expect(<span class="hljs-function">() =&gt;</span> throwGenericError()).toThrow();
  });

  it(<span class="hljs-string">'should throw string'</span>, <span class="hljs-function">() =&gt;</span> {
    expect(<span class="hljs-function">() =&gt;</span> throwString()).toThrow();
  });
});
</code></pre>
<p>To test if an exception is an instance of a certain class use <code>toThrow()</code> matcher passing a class reference as an argument.</p>
<pre><code class="lang-typescript">describe(<span class="hljs-string">'Test exception class'</span>, <span class="hljs-function">() =&gt;</span> {
  it(<span class="hljs-string">'should throw InvalidNumberException'</span>, <span class="hljs-function">() =&gt;</span> {
    expect(<span class="hljs-function">() =&gt;</span> throwInvalidNumberException()).toThrow(InvalidNumberException);
  });

  it(<span class="hljs-string">'should throw AppException'</span>, <span class="hljs-function">() =&gt;</span> {
    expect(<span class="hljs-function">() =&gt;</span> throwInvalidNumberException()).toThrow(AppException);
  });

  it(<span class="hljs-string">'should throw generic error'</span>, <span class="hljs-function">() =&gt;</span> {
    expect(<span class="hljs-function">() =&gt;</span> throwGenericError()).toThrow(<span class="hljs-built_in">Error</span>);
  });
});
</code></pre>
<p>To test if an exception is a string or an object with message property also use <code>toThrow()</code> matcher passing a string containing the expected error message.</p>
<pre><code class="lang-typescript">describe(<span class="hljs-string">'Test exception message'</span>, <span class="hljs-function">() =&gt;</span> {
  it(<span class="hljs-string">'should throw "Provided number is invalid" message'</span>, <span class="hljs-function">() =&gt;</span> {
    expect(<span class="hljs-function">() =&gt;</span> throwInvalidNumberException()).toThrow(<span class="hljs-string">'Provided number is invalid'</span>);
  });

  it(<span class="hljs-string">'should throw "Unknown error'</span>, <span class="hljs-function">() =&gt;</span> {
    expect(<span class="hljs-function">() =&gt;</span> throwGenericError()).toThrow(<span class="hljs-string">'Unknown error'</span>);
  });

  it(<span class="hljs-string">'should throw "Unexpected error"'</span>, <span class="hljs-function">() =&gt;</span> {
    expect(<span class="hljs-function">() =&gt;</span> throwString()).toThrow(<span class="hljs-string">'Unexpected error'</span>);
  });
});
</code></pre>
<h2 id="heading-asynchronous-code">Asynchronous code</h2>
<p>The <strong>basic</strong> way of testing asynchronous code is quite straightforward.</p>
<p>To test a resolved promise:</p>
<ul>
<li><p>define the <code>it</code> callback as <code>async</code> function</p>
</li>
<li><p><code>await</code> for a promise resolution</p>
</li>
<li><p>test the results</p>
</li>
</ul>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> resolvePromise = <span class="hljs-keyword">async</span> (): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">string</span>&gt; =&gt; {
  <span class="hljs-keyword">return</span> <span class="hljs-string">'resolved'</span>;
}

it(<span class="hljs-string">'should await for a promise'</span>, <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> resolvePromise();
  expect(result).toBe(<span class="hljs-string">'resolved'</span>);
});
</code></pre>
<p>To test a rejected promise you need to take some extra steps:</p>
<ul>
<li><p>define the <code>it</code> callback as <code>async</code> function</p>
</li>
<li><p>since there will be a <code>try/catch</code> block, add <code>expect.assertions()</code> call. It takes a number and verifies that this number of assertions will be called during the test</p>
</li>
<li><p>await for a promise in <code>try</code> block</p>
</li>
<li><p>catch the error in <code>catch</code> block</p>
</li>
<li><p>check the error instance</p>
</li>
</ul>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> rejectPromise = <span class="hljs-keyword">async</span> (): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">never</span>&gt; =&gt; {
  <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'rejected'</span>);
}

it(<span class="hljs-string">'should await for a rejected promise'</span>, <span class="hljs-keyword">async</span> () =&gt; {
  expect.assertions(<span class="hljs-number">1</span>);

  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">await</span> rejectPromise();
  } <span class="hljs-keyword">catch</span> (error) {
    expect(error).toBeInstanceOf(<span class="hljs-built_in">Error</span>);
  }
});
</code></pre>
<h2 id="heading-datasets">Datasets</h2>
<p>Let's assume, that you have the following code. <code>findMultipleEntries</code> function takes two arguments: an array of entries and an array of keys and returns entries matching the keys.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">interface</span> HasId {
  id: <span class="hljs-built_in">string</span>;
}

<span class="hljs-keyword">type</span> Nullable&lt;T&gt; = T | <span class="hljs-literal">null</span> | <span class="hljs-literal">undefined</span>;

<span class="hljs-keyword">const</span> findMultipleEntries = &lt;T <span class="hljs-keyword">extends</span> HasId&gt;(entries: Nullable&lt;T[]&gt;, keys: Nullable&lt;<span class="hljs-built_in">string</span>[]&gt;): T[] =&gt; {
  <span class="hljs-keyword">if</span> (!entries || !keys) {
    <span class="hljs-keyword">return</span> [];
  }

  <span class="hljs-keyword">return</span> entries.filter(<span class="hljs-function"><span class="hljs-params">entry</span> =&gt;</span> keys.includes(entry.id));
}
</code></pre>
<p>Instead of writing multiple duplicated tests, you might use the datasets. The dataset should at least contain all values that will be used inside test calls and also an expected result.</p>
<p>To define the dataset use the interface <code>Dataset</code> with 3 self-explanatory properties.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">interface</span> Dataset {
  entries: Nullable&lt;<span class="hljs-built_in">any</span>[]&gt;;
  keys: Nullable&lt;<span class="hljs-built_in">any</span>[]&gt;;
  expected: <span class="hljs-built_in">any</span>[]
};

<span class="hljs-keyword">const</span> datasets: Dataset[] = [
  {
    entries: <span class="hljs-literal">null</span>,
    keys: <span class="hljs-literal">null</span>,
    expected: []
  },
  {
    entries: <span class="hljs-literal">null</span>,
    keys: <span class="hljs-literal">undefined</span>,
    expected: []
  },
  {
    entries: <span class="hljs-literal">undefined</span>,
    keys: <span class="hljs-literal">null</span>,
    expected: []
  },
  {
    entries: <span class="hljs-literal">undefined</span>,
    keys: <span class="hljs-literal">undefined</span>,
    expected: []
  },
  {
    entries: [],
    keys: [],
    expected: []
  },
  {
    entries: [
      {id: <span class="hljs-string">'k1'</span>, value: <span class="hljs-number">1</span>},
      {id: <span class="hljs-string">'k3'</span>, value: <span class="hljs-number">3</span>},
      {id: <span class="hljs-string">'k5'</span>, value: <span class="hljs-number">5</span>},
    ],
    keys: [],
    expected: []
  },
  {
    entries: [],
    keys: [<span class="hljs-string">'k1'</span>, <span class="hljs-string">'k5'</span>, <span class="hljs-string">'k6'</span>],
    expected: []
  },
  {
    entries: [
      {id: <span class="hljs-string">'k22'</span>, value: <span class="hljs-number">22</span>},
      {id: <span class="hljs-string">'k23'</span>, value: <span class="hljs-number">23</span>},
    ],
    keys: [<span class="hljs-string">'k91'</span>, <span class="hljs-string">'k95'</span>],
    expected: []
  },
  {
    entries: [
      {id: <span class="hljs-string">'k1'</span>, value: <span class="hljs-number">1</span>},
      {id: <span class="hljs-string">'k3'</span>, value: <span class="hljs-number">3</span>},
      {id: <span class="hljs-string">'k5'</span>, value: <span class="hljs-number">5</span>},
    ],
    keys: [<span class="hljs-string">'k1'</span>, <span class="hljs-string">'k5'</span>, <span class="hljs-string">'k6'</span>],
    expected: [{id: <span class="hljs-string">'k1'</span>, value: <span class="hljs-number">1</span>},
      {id: <span class="hljs-string">'k5'</span>, value: <span class="hljs-number">5</span>},]
  },
];
</code></pre>
<p>To utilize datasets use <code>it.each</code> (or <code>describe.each</code> if executing multiple tests on a dataset) call. The <code>%j</code> is the first (and the only) positional parameter, that will convert a dataset (first and the only argument) to JSON. There are many other formatting options, so should you need more details, please refer to the official Jest documentation.</p>
<pre><code class="lang-typescript">describe(<span class="hljs-string">'Test findMultipleEntries'</span>, <span class="hljs-function">() =&gt;</span> {
  it.each(datasets)(<span class="hljs-string">'dataset: %j'</span>, <span class="hljs-function">(<span class="hljs-params">dataset: Dataset</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> actualResult = findMultipleEntries(dataset.entries, dataset.keys);
    expect(actualResult).toEqual(dataset.expected);
  });
});
</code></pre>
<p>The output should look similar to the following:</p>
<pre><code class="lang-bash">  Test findMultipleEntries
    ✓ dataset: {<span class="hljs-string">"entries"</span>:null,<span class="hljs-string">"keys"</span>:null,<span class="hljs-string">"expected"</span>:[]}
    ✓ dataset: {<span class="hljs-string">"entries"</span>:null,<span class="hljs-string">"expected"</span>:[]}
    ✓ dataset: {<span class="hljs-string">"keys"</span>:null,<span class="hljs-string">"expected"</span>:[]}
    ✓ dataset: {<span class="hljs-string">"expected"</span>:[]}
    ✓ dataset: {<span class="hljs-string">"entries"</span>:[],<span class="hljs-string">"keys"</span>:[],<span class="hljs-string">"expected"</span>:[]}
    ✓ dataset: {<span class="hljs-string">"entries"</span>:[{<span class="hljs-string">"id"</span>:<span class="hljs-string">"k1"</span>,<span class="hljs-string">"value"</span>:1},{<span class="hljs-string">"id"</span>:<span class="hljs-string">"k3"</span>,<span class="hljs-string">"value"</span>:3},{<span class="hljs-string">"id"</span>:<span class="hljs-string">"k5"</span>,<span class="hljs-string">"value"</span>:5}],<span class="hljs-string">"keys"</span>:[],<span class="hljs-string">"expected"</span>:[]}
    ✓ dataset: {<span class="hljs-string">"entries"</span>:[],<span class="hljs-string">"keys"</span>:[<span class="hljs-string">"k1"</span>,<span class="hljs-string">"k5"</span>,<span class="hljs-string">"k6"</span>],<span class="hljs-string">"expected"</span>:[]}
    ✓ dataset: {<span class="hljs-string">"entries"</span>:[{<span class="hljs-string">"id"</span>:<span class="hljs-string">"k22"</span>,<span class="hljs-string">"value"</span>:22},{<span class="hljs-string">"id"</span>:<span class="hljs-string">"k23"</span>,<span class="hljs-string">"value"</span>:23}],<span class="hljs-string">"keys"</span>:[<span class="hljs-string">"k91"</span>,<span class="hljs-string">"k95"</span>],<span class="hljs-string">"expected"</span>:[]}
    ✓ dataset: {<span class="hljs-string">"entries"</span>:[{<span class="hljs-string">"id"</span>:<span class="hljs-string">"k1"</span>,<span class="hljs-string">"value"</span>:1},{<span class="hljs-string">"id"</span>:<span class="hljs-string">"k3"</span>,<span class="hljs-string">"value"</span>:3},{<span class="hljs-string">"id"</span>:<span class="hljs-string">"k5"</span>,<span class="hljs-string">"value"</span>:5}],<span class="hljs-string">"keys"</span>:[<span class="hljs-string">"k1"</span>,<span class="hljs-string">"k5"</span>,<span class="hljs-string">"k6"</span>],<span class="hljs-string">"expected"</span>:[{<span class="hljs-string">"id"</span>:<span class="hljs-string">"k1"</span>,<span class="hljs-string">"value"</span>:1},{<span class="hljs-string">"id"</span>:<span class="hljs-string">"k5"</span>,<span class="hljs-string">"value"</span>:5}]}

Test Suites: 1 passed, 1 total
Tests:       9 passed, 9 total
</code></pre>
<h2 id="heading-naming">Naming</h2>
<p>My preferred way of naming tests is a structure of very long sentences</p>
<p>Imagine a simple application, that displays a client list. What would the flow look like?</p>
<ul>
<li><p>the user enters the page</p>
<ul>
<li><p>the skeleton is displayed</p>
</li>
<li><p>header is displayed</p>
</li>
<li><p>API clients request is made</p>
<ul>
<li><p>request error occurs</p>
<ul>
<li>an error is displayed</li>
</ul>
</li>
<li><p>request is successful</p>
<ul>
<li>the client list is displayed</li>
</ul>
</li>
</ul>
</li>
<li><p>add client button is displayed</p>
<ul>
<li><p>user clicks the button</p>
<ul>
<li>user is redirected to new URL</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>The tests should look similar to this structure. For example:</p>
<pre><code class="lang-typescript">describe(<span class="hljs-string">'When user enters the page'</span>, <span class="hljs-function">() =&gt;</span> {

  it(<span class="hljs-string">'should display page header'</span>, <span class="hljs-function">() =&gt;</span> {});

  it(<span class="hljs-string">'should display skeleton'</span>, <span class="hljs-function">() =&gt;</span> {});

  it(<span class="hljs-string">'should not display client list'</span>, <span class="hljs-function">() =&gt;</span> {});

  it(<span class="hljs-string">'should display "add user" button'</span>, <span class="hljs-function">() =&gt;</span> {});

  it(<span class="hljs-string">'should not display error'</span>, <span class="hljs-function">() =&gt;</span> {});

  it(<span class="hljs-string">'should send clients request'</span>, <span class="hljs-function">() =&gt;</span> {});

  describe(<span class="hljs-string">'and user clicks "add user" button'</span>, <span class="hljs-function">() =&gt;</span> {
    it(<span class="hljs-string">'should redirect to "/create" url'</span>, <span class="hljs-function">() =&gt;</span> {});
  });

  describe(<span class="hljs-string">'and clients request fails'</span>, <span class="hljs-function">() =&gt;</span> {
    it(<span class="hljs-string">'should display error'</span>, <span class="hljs-function">() =&gt;</span> {});

    it(<span class="hljs-string">'should not display skeleton'</span>, <span class="hljs-function">() =&gt;</span> {});

    it(<span class="hljs-string">'should not display client list'</span>, <span class="hljs-function">() =&gt;</span> {});
  });

  describe(<span class="hljs-string">'and clients request is successful'</span>, <span class="hljs-function">() =&gt;</span> {
    it(<span class="hljs-string">'should not display error'</span>, <span class="hljs-function">() =&gt;</span> {});

    it(<span class="hljs-string">'should not display skeleton'</span>, <span class="hljs-function">() =&gt;</span> {});

    it(<span class="hljs-string">'should display client list'</span>, <span class="hljs-function">() =&gt;</span> {});
  });
});
</code></pre>
<p>When you run this test suite, you'll get this output:</p>
<pre><code class="lang-bash">When user enters the page
  ✓ should display page header
  ✓ should display skeleton
  ✓ should not display client list
  ✓ should display <span class="hljs-string">"add user"</span> button
  ✓ should not display error
  ✓ should send clients request
  and user clicks <span class="hljs-string">"add user"</span> button
    ✓ should redirect to <span class="hljs-string">"/create"</span> url
  and clients request fails
    ✓ should display error
    ✓ should not display skeleton
    ✓ should not display client list
  and clients request is successful
    ✓ should not display error
    ✓ should not display skeleton
    ✓ should display client list
</code></pre>
<p>These <strong>sentences</strong> are easy to read and are quite verbose. Just a quick look at the results and you already know what's going on.</p>
<p>When your application grows bigger, and so do the tests, sometimes it's not always possible to keep that structure. At some point, you're going to add some nested blocks like <code>describe('Testing pagination')</code> that logically separate parts of the application.</p>
<p>There are of course many other <strong>best practices</strong> and <strong>guidelines</strong> on how to name your tests. Remember that the tests are mainly for us, the developers.</p>
<h1 id="heading-footnote">Footnote</h1>
<p>This article just scratched the surface of testing. Jest itself is much more extensive and can be supplemented by extra libraries and plugins. And of course, there are many other frameworks, tools and methodologies to explore. As always - you have to find what works for you.</p>
]]></content:encoded></item></channel></rss>