<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Victor Shelepen</title>
    <description>The latest articles on DEV Community by Victor Shelepen (@victorshelepen).</description>
    <link>https://dev.to/victorshelepen</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F450558%2Fe44adace-af7e-420f-ae31-047023d4c162.jpeg</url>
      <title>DEV Community: Victor Shelepen</title>
      <link>https://dev.to/victorshelepen</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/victorshelepen"/>
    <language>en</language>
    <item>
      <title>A TypeScript Utility for Type-Safe Angular Forms</title>
      <dc:creator>Victor Shelepen</dc:creator>
      <pubDate>Fri, 08 Aug 2025 11:28:08 +0000</pubDate>
      <link>https://dev.to/victorshelepen/a-typescript-utility-for-type-safe-angular-forms-4lko</link>
      <guid>https://dev.to/victorshelepen/a-typescript-utility-for-type-safe-angular-forms-4lko</guid>
      <description>&lt;p&gt;Hello, I'd like to share a code snippet that helps keep your Angular forms strongly typed and consistent with your data interfaces. I'm working on a full-stack TypeScript project where a single set of interfaces serves as the foundation for the database models, services, DTOs, and UI components. The following snippet of code implements this concept.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;FormArray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;FormControl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;FormGroup&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@angular/forms&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;GForm&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;IInterface&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;FormGroup&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Key&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;IInterface&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; 
    &lt;span class="nx"&gt;IInterface&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt; &lt;span class="nx"&gt;ArrayItem&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;FormArray&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;GForm&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ArrayItem&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;FormControl&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ArrayItem&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IInterface&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;FormControl&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IInterface&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt;
          &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;GForm&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;IInterface&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;FormControl&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;IInterface&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FormControl&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;IInterface&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's how I use it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ICar&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nl"&gt;form&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GForm&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ICar&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormGroup&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;FormControl&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Validators&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Validators&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;minLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)]),&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;FormControl&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Validators&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Validators&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;minLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)]),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;IDrivingLicence&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;fullname&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;car&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ICar&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormGroup&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;fullname&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;FormControl&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;car&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;FormControl&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ICar&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;defaultCar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;nonNullable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;},)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;GForm&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;IDrivingLicence&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'm wondering if a similar utility already exists in the Angular library—I may have missed it. I'd appreciate it if someone could point me to a ready-made solution if one exists. My solution isn't perfect, either. It doesn't cover every edge case I've encountered. I would be grateful for any comments or suggestions on how to improve this snippet.&lt;/p&gt;

&lt;h4&gt;
  
  
  Notes
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;null&lt;/code&gt; type is included because a form's controls can become &lt;code&gt;null&lt;/code&gt; when the form is reset. You can still use validators to make &lt;code&gt;null&lt;/code&gt; values invalid.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;FormControl&amp;lt;T[Key] | null&amp;gt;&lt;/code&gt; is used for complex objects when you want to manage that object's value with a single custom form control rather than a nested &lt;code&gt;FormGroup&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Avoid ObjectId Type for Ambiguous Typing System Across Tiers by Ditching ObjectId in Favor of Strings</title>
      <dc:creator>Victor Shelepen</dc:creator>
      <pubDate>Tue, 20 May 2025 08:31:37 +0000</pubDate>
      <link>https://dev.to/victorshelepen/avoid-objectid-type-for-ambiguous-typing-system-across-tiers-by-ditching-objectid-in-favor-of-1a1j</link>
      <guid>https://dev.to/victorshelepen/avoid-objectid-type-for-ambiguous-typing-system-across-tiers-by-ditching-objectid-in-favor-of-1a1j</guid>
      <description>&lt;h2&gt;
  
  
  The recipe first
&lt;/h2&gt;

&lt;p&gt;Avoid using ObjectId Type for Ambiguous Typing System Across Tiers by favoring Strings instead. This will result in simpler backend types and DTOs that are reusable at the front end without requiring any special tools. The main disadvantage of this approach is a slight decrease in performance, as strings are larger than ObjectIds and can impact fast sorting operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  My story
&lt;/h2&gt;

&lt;p&gt;This scenario originated from a real application. It seems we followed the basic rules in different steps but ultimately failed. It would be great if we had changed the architecture at the start. Our application has a stack consisting of NestJS, Angular, and Mongoose, all written in TypeScript.&lt;/p&gt;

&lt;p&gt;We successfully implemented reusing typings across tiers, but it appeared complicated. Each tier had its own typing system, which could have been optimized. A clue to the problem is that the type of the primary field _id is ObjectId, a complex type present only on the backend. This made it unnatural for it to be used on both the frontend and backend.&lt;/p&gt;

&lt;p&gt;Some tools were implemented to reuse these types on ObjectId. New UI types were created, which seemed to improve the application's typing system. However, the application became convoluted, and we didn't want to research the case of using plain string IDs because we thought our approach was correct and it worked everywhere except for MeteorJS projects.&lt;/p&gt;

&lt;p&gt;If we refused to use the ObjectId type, we could eliminate the need for these tools, new UI types, and even DTOs that work on both frontend and backend. To test this idea, we created a separate project where I implemented the concept. The only disadvantage is the potential impact on MongoDB performance, which I couldn't measure.&lt;/p&gt;

&lt;p&gt;Additionally, we experimented with meaningful primary IDs where demanded custom solutions where string type is demanded.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Start server and test scripts - NPM script - ready solution.</title>
      <dc:creator>Victor Shelepen</dc:creator>
      <pubDate>Mon, 01 Nov 2021 09:53:52 +0000</pubDate>
      <link>https://dev.to/victorshelepen/start-server-and-test-scripts-npm-script-ready-solution-poa</link>
      <guid>https://dev.to/victorshelepen/start-server-and-test-scripts-npm-script-ready-solution-poa</guid>
      <description>&lt;p&gt;&lt;strong&gt;Summary&lt;/strong&gt; I have made my alternative finding different pitfalls. Meanwhile We were applying the library &lt;a href="//npmjs.com/package/start-server-and-test"&gt;start-server-and-test&lt;/a&gt; to our solution. Surprisingly  &lt;a href="//npmjs.com/package/start-server-and-test"&gt;start-server-and-test&lt;/a&gt; covered all pitfalls. The solution solves the problem.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs3ew4vp4cizb0obg07lu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs3ew4vp4cizb0obg07lu.png" alt=" " width="723" height="81"&gt;&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;It is a simple script and we need to understand it. &lt;strong&gt;It does how it is written.&lt;/strong&gt; It runs the server and then tests it. For details check the documentation page of &lt;a href="//npmjs.com/package/start-server-and-test"&gt;start-server-and-test&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pitfalls:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All processes have to be ordered.&lt;/li&gt;
&lt;li&gt;Pipes have to be disposable to be finished.&lt;/li&gt;
&lt;li&gt;Tests need to know when the server starts to start testing.&lt;/li&gt;
&lt;li&gt;The script has to know when tests are finished to terminate all processes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;It takes only three parameters:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The first: To run the server.&lt;/li&gt;
&lt;li&gt;The second: Server path. Address to wait for its availability.&lt;/li&gt;
&lt;li&gt;The third: Test commands.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How it works&lt;/strong&gt;&lt;br&gt;
The script runs the first to start the main development server and waits until it is up. The second parameter is the server path that the script checks if the development server is available. The development server continues running. The script runs tests that are set in the third parameter. The script waits for the finish of the tests. When tests are passed the script terminates the development server and disposes of the main thread.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S.&lt;/strong&gt; It should help you to start using the script faster.&lt;/p&gt;

</description>
      <category>node</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
