<?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: Georgi Parlakov</title>
    <description>The latest articles on DEV Community by Georgi Parlakov (@gparlakov).</description>
    <link>https://dev.to/gparlakov</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%2F151766%2F74e65d8b-b0dc-4b4a-9bf6-bc4138590c4c.png</url>
      <title>DEV Community: Georgi Parlakov</title>
      <link>https://dev.to/gparlakov</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gparlakov"/>
    <language>en</language>
    <item>
      <title>How to Run Apollo GraphQL in a QWIK Endpoint</title>
      <dc:creator>Georgi Parlakov</dc:creator>
      <pubDate>Sun, 09 Jul 2023 12:28:21 +0000</pubDate>
      <link>https://dev.to/gparlakov/how-to-run-apollo-graphql-in-a-qwik-endpoint-15p2</link>
      <guid>https://dev.to/gparlakov/how-to-run-apollo-graphql-in-a-qwik-endpoint-15p2</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WePdVQpu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/isj9zmg1suklj28pbyxe.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WePdVQpu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/isj9zmg1suklj28pbyxe.jpg" alt="QWIK + Apollo server" width="800" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Short version
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/gparlakov/qwik-endpoint-apollo-graphql"&gt;code&lt;/a&gt; Github&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Long version
&lt;/h2&gt;

&lt;p&gt;Highlights:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;starting with settin up QWIK and Apollo server&lt;/li&gt;
&lt;li&gt;we'll add a couple of routes

&lt;ul&gt;
&lt;li&gt;one to expose the graphql as an endpoint&lt;/li&gt;
&lt;li&gt;one to consume it&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;in the endpoint we'll use code inspired by the expressMiddlware for Apollo server 4 and update it so it works with the QWIK &lt;code&gt;RequestHandler&lt;/code&gt; interface&lt;/li&gt;
&lt;li&gt;on the consuming side

&lt;ul&gt;
&lt;li&gt;fetch will need a slighly different URL when working on the server (SSR) vs the client so we'll use the &lt;code&gt;onRequest&lt;/code&gt; and &lt;code&gt;routeLoader&lt;/code&gt; to provide the URL from QWIK down to the &lt;code&gt;gqlCall&lt;/code&gt; function&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  0. Setting up QWIK
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;start by setting up &lt;a href="https://qwik.builder.io/docs/getting-started/"&gt;QWIK&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;npm create qwik@latest&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;we used the empty app template&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;branch in &lt;a href="https://github.com/gparlakov/qwik-endpoint-apollo-graphql/tree/step-0-init-qwik"&gt;GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  1. Set up routes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;one route for the graphql endpoint

&lt;ul&gt;
&lt;li&gt;so create a file&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;one for a page that will consume that GQL&lt;/li&gt;
&lt;li&gt;branch in &lt;a href="https://github.com/gparlakov/qwik-endpoint-apollo-graphql/tree/step-1-endpoint-and-page-init"&gt;GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Setting up Apollo
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Apollo &lt;a href="https://www.apollographql.com/docs/apollo-server/getting-started"&gt;getting started docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;npm install @apollo/server graphql&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Typescript and node types already installed and tsconfig configured by qwik so we can skip that&lt;/li&gt;
&lt;li&gt;create the &lt;code&gt;./graphql/index.ts&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;start with the schema
&lt;/li&gt;
&lt;/ul&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;ApolloServer&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="s1"&gt;@apollo/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;startStandaloneServer&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="s1"&gt;@apollo/server/standalone&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// A schema is a collection of type definitions (hence "typeDefs")&lt;/span&gt;
    &lt;span class="c1"&gt;// that together define the "shape" of queries that are executed against&lt;/span&gt;
    &lt;span class="c1"&gt;// your data.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;typeDefs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`#graphql
    # Comments in GraphQL strings (such as this one) start with the hash (#) symbol.

    # This "Book" type defines the queryable fields for every book in our data source.
    type Book {
        title: String
        author: String
    }

    # The "Query" type is special: it lists all of the available queries that
    # clients can execute, along with the return type for each. In this
    # case, the "books" query returns an array of zero or more Books (defined above).
    type Query {
        books: [Book]
    }
    `&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;add a resolver and the server
&lt;/li&gt;
&lt;/ul&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;ApolloServer&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="s1"&gt;@apollo/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// A schema is a collection of type definitions (hence "typeDefs")&lt;/span&gt;
  &lt;span class="c1"&gt;// that together define the "shape" of queries that are executed against&lt;/span&gt;
  &lt;span class="c1"&gt;// your data.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;typeDefs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`#graphql
    # Comments in GraphQL strings (such as this one) start with the hash (#) symbol.

    # This "Book" type defines the queryable fields for every book in our data source.
    type Book {
      title: String
      author: String
    }

    # The "Query" type is special: it lists all of the available queries that
    # clients can execute, along with the return type for each. In this
    # case, the "books" query returns an array of zero or more Books (defined above).
    type Query {
      books: [Book]
    }
  `&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;books&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;The Awakening&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Kate Chopin&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="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;City of Glass&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Paul Auster&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="p"&gt;];&lt;/span&gt;


  &lt;span class="c1"&gt;// Resolvers define how to fetch the types defined in your schema.&lt;/span&gt;
  &lt;span class="c1"&gt;// This resolver retrieves books from the "books" array above.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resolvers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;books&lt;/span&gt;&lt;span class="p"&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="nx"&gt;books&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c1"&gt;// The ApolloServer constructor requires two parameters: your schema&lt;/span&gt;
  &lt;span class="c1"&gt;// definition and your set of resolvers.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ApolloServer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="nx"&gt;typeDefs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;resolvers&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;serverStarted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;on the last line we export the serverStarted which we'll use in the next step to expose the GraphQL API and UI&lt;/li&gt;
&lt;li&gt;branch in &lt;a href="https://github.com/gparlakov/qwik-endpoint-apollo-graphql/tree/step-2-setup-apollo"&gt;GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Starting the server in an endpoint
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;we have the endpoint in the &lt;code&gt;src/routes/graphql/index.ts&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;the following is inspired and using the code from expressMiddleware &lt;code&gt;@apollo/server/src/express4/index.ts&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&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;ContextFunction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;BaseContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;HeaderMap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;HTTPGraphQLRequest&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="s1"&gt;@apollo/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RequestHandler&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="s1"&gt;@builder.io/qwik-city&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;serverStarted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;server&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="s1"&gt;~/graphql&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;const&lt;/span&gt; &lt;span class="nx"&gt;onRequest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RequestHandler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;parseBody&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;getWritableStream&lt;/span&gt;&lt;span class="p"&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;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;serverStarted&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// from expressMiddleware node_modules/@apollo/server/src/express4/index.ts&lt;/span&gt;
    &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;assertStarted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;QWIK Endpoint&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// This `any` is safe because the overload above shows that context can&lt;/span&gt;
    &lt;span class="c1"&gt;// only be left out if you're using BaseContext as your context, and {} is a&lt;/span&gt;
    &lt;span class="c1"&gt;// valid BaseContext.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;defaultContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ContextFunction&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;[{}],&lt;/span&gt; &lt;span class="nx"&gt;BaseContext&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;async&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;parseBody&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;body&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;HeaderMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;httpGraphQLRequest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HTTPGraphQLRequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;search&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&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;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;executeHTTPGraphQLRequest&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="nx"&gt;httpGraphQLRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&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="nx"&gt;defaultContext&lt;/span&gt;&lt;span class="p"&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;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;httpGraphQLResponse&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;httpGraphQLResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;complete&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;httpGraphQLResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&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="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;httpGraphQLResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;

            &lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;

          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;writableStream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;getWritableStream&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;writer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;writableStream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getWriter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;encoder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;TextEncoder&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

          &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;httpGraphQLResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;asyncIterator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;encoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="nx"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;e&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;this basically let's the Apollo server have the body and headers&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;and then relays the response back to the client&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;in one go - if the &lt;code&gt;kind === complete&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;otherwise - in a stream
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dOcZJ7PH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/za3a0k1by85kzoypa4er.png" alt="GraphQL Sandbox - dev-only by default" width="800" height="303"&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;&lt;p&gt;branch in &lt;a href="https://github.com/gparlakov/qwik-endpoint-apollo-graphql/tree/step-3-start-apollo-in-the-endpoint"&gt;GitHub&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Consume from a page
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;in the &lt;code&gt;src/routes/page/index.tsx&lt;/code&gt; we have the placeholder of a page&lt;/li&gt;
&lt;li&gt;in the &lt;code&gt;src/routes/page/gql-call.ts&lt;/code&gt; let's create a function to make a gql call&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;-&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="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;gqlCall&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&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;url&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;body&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;controller&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;AbortController&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&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;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;Accept&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&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="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
        &lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&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;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;

          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;
            &lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;getReader&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;v&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="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`An error occurred: , /n &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusText&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; /n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fromCodePoint&lt;/span&gt;&lt;span class="p"&gt;(...(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[]))}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;it adds the necessary minimum headers, reads the body, and has a minimal error handling&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;now we can make a call in the page's component using the Qwik &lt;a href="https://qwik.builder.io/api/qwik/#resource"&gt;Resource&lt;/a&gt; primitive/component
&lt;/li&gt;
&lt;/ul&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;Resource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;component$&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useResource$&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useSignal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useTask$&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="s1"&gt;@builder.io/qwik&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;gqlCall&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="s1"&gt;./gql-call&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;titlesQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`#gql
query BookTitlesQuery {
    books {
        title
    }
}
`&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;component$&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useSignal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useResource$&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt;&lt;span class="nx"&gt;track&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;track&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;gqlCall&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;books&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;title&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="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;titlesQuery&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="nx"&gt;onClick$&lt;/span&gt;&lt;span class="o"&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="nx"&gt;reload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="mi"&gt;1&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="na"&gt;reload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Resource&lt;/span&gt;
                &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="nx"&gt;onResolved&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{({&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;data&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;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;books&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;books&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&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;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;}&amp;lt;/&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;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&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;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;



&lt;ul&gt;
&lt;li&gt;branch in &lt;a href="https://github.com/gparlakov/qwik-endpoint-apollo-graphql/tree/step-4-consume-from-qwik-page"&gt;GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Import the GraphQL schema
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;In a Real-world® scenario we would probably read that off of an environment variable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;const typeDefsFile = process.env.GRAPHQL_SCHEMA&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;To consume the GraphQL schema from a file we need a couple of changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;starting with the external file  &lt;code&gt;src/graphql/schema.graphql&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;for this demo we'll just copy over the book schema from &lt;code&gt;index.ts&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;then in our &lt;code&gt;src/graphql/index.ts&lt;/code&gt; we can
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;typeDefs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./schema.graphql?raw&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;so at runtime &lt;code&gt;typeDefs&lt;/code&gt; will hold the raw contents of the schema.graphql file allowing for the Apollo server to be built&lt;/li&gt;
&lt;li&gt;branch in &lt;a href="https://github.com/gparlakov/qwik-endpoint-apollo-graphql/tree/step-5-external-graphql-schema"&gt;GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Thus we have a single process that runs both the QWIK ssr and client code as well as the GraphQL server. We can use the endpoint /graphql by the app or externally. &lt;/p&gt;

&lt;p&gt;Cheers.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧞‍🙏 Thanks for reading!
&lt;/h2&gt;

&lt;p&gt;Give some 💖 or 🦄  if you liked this post.&lt;br&gt;
These help other people find this post and encourage me to write more. Thanks!&lt;/p&gt;

&lt;p&gt;Check out my projects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://npmjs.com/packages/scuri"&gt;SCuri&lt;/a&gt; — Unit test boilerplate automation (with Enterprise support option too)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://npmjs.com/packages/ngx-forms-typed"&gt;ngx-forms-typed&lt;/a&gt; — Angular form, only strong typed!&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://npmjs.com/packages/ngx-show-form-control"&gt;ngx-show-form-control&lt;/a&gt; — Visualize/Edit any FormControl/Group&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://www.buymeacoffee.com/bHQk8Cu"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WPvXTOye--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.buymeacoffee.com/buttons/bmc-new-btn-logo.svg" alt="Buy me a coffee" width="24" height="36"&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>await for Observables</title>
      <dc:creator>Georgi Parlakov</dc:creator>
      <pubDate>Fri, 03 Jun 2022 04:23:01 +0000</pubDate>
      <link>https://dev.to/gparlakov/await-for-observables-3ljc</link>
      <guid>https://dev.to/gparlakov/await-for-observables-3ljc</guid>
      <description>&lt;p&gt;&lt;a href="https://gparlakov.medium.com/await-for-observables-234467302c4e?source=rss-8b336464cb11------2"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vA56tvPn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/1%2AwAjL4H9zMOIABK9QGNCpjg.png" alt="" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Subscribe for me, please!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gparlakov.medium.com/await-for-observables-234467302c4e?source=rss-8b336464cb11------2"&gt;Continue reading on Medium »&lt;/a&gt;&lt;/p&gt;

</description>
      <category>subscription</category>
      <category>rxjs</category>
      <category>free</category>
      <category>unittesting</category>
    </item>
    <item>
      <title>How does RxJs retry Work?  </title>
      <dc:creator>Georgi Parlakov</dc:creator>
      <pubDate>Mon, 26 Apr 2021 18:38:58 +0000</pubDate>
      <link>https://dev.to/gparlakov/how-does-rxjs-retry-work-412p</link>
      <guid>https://dev.to/gparlakov/how-does-rxjs-retry-work-412p</guid>
      <description>&lt;p&gt;Turns out, &lt;code&gt;retry&lt;/code&gt; operator, like many others subscribes to the observable that it is applied on. What's different is that &lt;code&gt;retry&lt;/code&gt; works on the &lt;code&gt;error&lt;/code&gt; case. On the &lt;code&gt;error&lt;/code&gt; case it re-subscribes to the source observable as many times as attempts it was given. &lt;/p&gt;

&lt;p&gt;So &lt;code&gt;retry(2)&lt;/code&gt; will listen for error and retry once, then listen for another error and retry a second time and then on the third error it will give up and let the error bubble up to your error handler.&lt;/p&gt;

&lt;p&gt;For more details, &lt;strong&gt;code&lt;/strong&gt;, and step by step head over &lt;a href="https://www.educative.io/edpresso/what-is-the-rxjs-retry-operator-and-how-does-it-work"&gt;https://www.educative.io/edpresso/what-is-the-rxjs-retry-operator-and-how-does-it-work&lt;/a&gt;&lt;/p&gt;

</description>
      <category>rxjs</category>
      <category>retry</category>
      <category>operators</category>
      <category>underthehood</category>
    </item>
    <item>
      <title>Yes — The Angular CLI is Creating Unit Tests Wrong</title>
      <dc:creator>Georgi Parlakov</dc:creator>
      <pubDate>Sat, 03 Oct 2020 15:13:11 +0000</pubDate>
      <link>https://dev.to/gparlakov/yes-the-angular-cli-is-creating-unit-tests-wrong-3pof</link>
      <guid>https://dev.to/gparlakov/yes-the-angular-cli-is-creating-unit-tests-wrong-3pof</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--I_JSIZn0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AJCZp0tTOoDnsyEe56kP0Tg.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--I_JSIZn0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AJCZp0tTOoDnsyEe56kP0Tg.jpeg" alt=""&gt;&lt;/a&gt;Photo by &lt;a href="https://unsplash.com/@nevenkrcmarek?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Neven Krcmarek&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/moon?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I strongly agree with &lt;a href="https://medium.com/thinkster-io/the-angular-cli-is-creating-your-unit-tests-wrong-4b42412e082c"&gt;The Angular CLI is creating your unit tests wrong&lt;/a&gt; article!&lt;/p&gt;

&lt;p&gt;In fact, I’ve got both an alternative &lt;strong&gt;approach&lt;/strong&gt; and a &lt;strong&gt;tool&lt;/strong&gt; to &lt;strong&gt;automate&lt;/strong&gt; a lot of the &lt;strong&gt;boilerplate&lt;/strong&gt; :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the tool is SCuri — &lt;a href="https://www.npmjs.com/package/scuri?source=responses-----b8d05adf3fa2----0-----------------respond_sidebar-----------"&gt;https://www.npmjs.com/package/scuri&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;approach described in &lt;a href="https://dev.to/gparlakov/an-opinionated-approach-to-testing-angular-492f"&gt;Opinionated approach to Angular testing&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;my favourite — &lt;a href="https://medium.com/angular-in-depth/angular-testbed-considered-harmful-3f91f647d1fd"&gt;TestBed considered harmful&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>angular</category>
      <category>productivity</category>
      <category>webdev</category>
      <category>tech</category>
    </item>
    <item>
      <title>The Page Pattern</title>
      <dc:creator>Georgi Parlakov</dc:creator>
      <pubDate>Tue, 01 Sep 2020 15:11:44 +0000</pubDate>
      <link>https://dev.to/gparlakov/the-page-pattern-3c</link>
      <guid>https://dev.to/gparlakov/the-page-pattern-3c</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8n_EcoTo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/1%2Az9wppfDO7UjiiHTnCS3sgg.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8n_EcoTo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/1%2Az9wppfDO7UjiiHTnCS3sgg.jpeg" alt="Mother and daughter separated by a narrow creek" width="800" height="532"&gt;&lt;/a&gt;Photo by &lt;a href="https://unsplash.com/@simonrae?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Simon Rae&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/separate?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Separating the business logic problem in Angular.
&lt;/h4&gt;

&lt;p&gt;This article aims to give you a tool to help keep those business and UI concerns separate. It’s the result of a year’s worth of experimentation in a couple of teams I worked with. It might seem unnatural and complex at first, but then again everything new is like that right?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Optimize for the case that the software we write will change and evolve!&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There are 2 problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We want to separate 💸business logic from user✨interface logic! But that requires a lot of thought effort, which leads to the second problem.&lt;/li&gt;
&lt;li&gt;People just want to deliver their feature/fix their bug and go home! And not be constantly worrying: “Where do I put this logic?”&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Why are these a problem?&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;1. Why does business logic need to be separate from user interface logic?&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Let’s start with what each is. Think of a select list or a dropdown or a select combo box&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;we’d like to keep all instances of this look and feel the same (UI)&lt;/li&gt;
&lt;li&gt;every instance will show different stuff (business)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In such fairly low-level concepts, it’s trivial to see how business and UI logic need to be separate. Every list will contain different entities people, addresses, orders, etc., but we’d like all of the app’s dropdown-s to look and feel the same for the user’s sake.&lt;/p&gt;

&lt;p&gt;We slip into mixing our 💸business and ✨ UI logic when we get to more complex scenarios and get all kinds of pressure! Take your pick: I want to finish faster! I want to make it clever and future proof and reusable as f..k! My boss wants me to finish this by tonight! We promised we would deliver this sprint!&lt;/p&gt;

&lt;p&gt;All this is true and real. What is also true and real is your future self having to &lt;strong&gt;maintain&lt;/strong&gt; the code you write today. Help it and separate your business logic from the UI.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Because business requirements evolve — add new payment method — 💸 &lt;strong&gt;business&lt;/strong&gt; concern!&lt;/li&gt;
&lt;li&gt;Because users say — hey this wizard/form/whatever looks cool and feels natural and makes my life easy, make the other page look the same — ✨ &lt;strong&gt;UI&lt;/strong&gt;  concern!&lt;/li&gt;
&lt;li&gt;Because users say — refresh the data on this grid after adding a row — 💸 &lt;strong&gt;business&lt;/strong&gt; concern!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Any of those listed above requirements become really hard or near impossible if we have all our logic in the UI component. Then look and behavior become inseparable. And such requirements are the bread and butter of what we do — evolving software to meet the user’s needs! Ideally, we would have a perfect understanding of what the users need, how to present it and when to update it! We do not! That’s why any living software has issues, bugs, enhancements.&lt;/p&gt;

&lt;p&gt;So, let’s optimize for the case that our software will change and evolve!&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;2. We don’t want to think!&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;We, the people, need a clear set of rules as to what/how/where/when!&lt;/p&gt;

&lt;p&gt;But G, you might say, wait a minute! I’m a smart developer, I don’t need rules! I create the rules in code!&lt;/p&gt;

&lt;p&gt;I know, how you feel, I’ve felt the same way. The rules thing can come across insulting. Bear with me, I’ll explain.&lt;/p&gt;

&lt;p&gt;It turns out that our brain wants to spend as little energy as possible. It’s a survival and evolutionary thing. The brain does not want to think about the same things every time and looks for the repeatable pattern to memorize and offload to &lt;a href="https://www.youtube.com/watch?v=UBVV8pch1dM"&gt;the second system&lt;/a&gt; which automates the boring tasks. Think of tying your shoelaces, buttoning a button, washing the dishes, writing code(?!). You don’t want to constantly be thinking about these things — you’ve got more important things to worry about! Where do I go on vacation? Do I need a new car, and can I afford it? The big rocks!&lt;/p&gt;

&lt;p&gt;OK, enough about that! Let’s say for the sake of argument that you agree with me that people need a clear set of rules to make their life bearable. Se here’s my list:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Know your 💸business logic from your ✨UI logic!&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=8FWdQVNeTlI"&gt;Keep ’em separated&lt;/a&gt;!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Simple! Right?&lt;/p&gt;

&lt;p&gt;But, G, how exactly do I do that?!&lt;/p&gt;

&lt;p&gt;One set of rules that can give you that is the &lt;a href="https://redux.js.org/"&gt;Redux pattern&lt;/a&gt; — NgRx, MobX, etc. Its trade-off is the complex but flexible one.&lt;/p&gt;

&lt;p&gt;Or you can implement the Page pattern.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Introducing the Page pattern&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;A “Page” is what the user interacts with, in one scrollable screen.&lt;/p&gt;

&lt;p&gt;It’s not necessarily one component, though one component will often need to be the container of this “Page”! It’s not one route, it can be many, though the container component would probably have one route taking users to it.&lt;/p&gt;

&lt;p&gt;The “Page” is the business entity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the user profile page&lt;/li&gt;
&lt;li&gt;the reservation page&lt;/li&gt;
&lt;li&gt;the cat owner’s page&lt;/li&gt;
&lt;li&gt;the billing page&lt;/li&gt;
&lt;li&gt;the landing page&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The “Page” (1) owns the data (💸 &lt;strong&gt;business&lt;/strong&gt; concern!) and it (2) requires the user input and the data render to be done on the ✨UI level:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The “Page” expects to get notified about user’s input and intent: onChangePassword onChangePasswordCancel, onChangePasswordConfirm. Also "Page" wants to know when user enters and leaves its domain: onPageEnter and onPageLeave.&lt;/p&gt;

&lt;p&gt;The “Page” owns multi-step, 💸 business logic (BL), processes, like the delete user account with a confirmation dialog step:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;How do you know what to put in it? Can the user see or click on it? Then its a ✨ UI concern — put it in the Component!&lt;/p&gt;

&lt;p&gt;Anything else goes in the 💸 Page!&lt;/p&gt;

&lt;p&gt;That’s a bit extreme, I know. The idea is for its implementation by us, the people, to be simple, to make the decision as to what to put where a no-brainer! Thus we take care of both the 1. Know your 💸BL and your ✨ UI! 2. Keep ’em separated!&lt;/p&gt;

&lt;p&gt;For example, let’s say that we have a grid of bills:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the bills are displayed on a grid with columns: status(paid/due), name (electricity home, water home, water at mum’s), due date, provider&lt;/li&gt;
&lt;li&gt;the bill can be drilled into for details&lt;/li&gt;
&lt;li&gt;the bill can be selected&lt;/li&gt;
&lt;li&gt;multiple bills can be selected&lt;/li&gt;
&lt;li&gt;bill can be paid&lt;/li&gt;
&lt;li&gt;bill can be hidden (some bills you don’t want to see — take care of by someone else)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The UI would be the Grid, the checkboxes on the rows representing the selection, the buttons Pay and Hide. Let’s create a component for that:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Why don’t I just put everything in the component and be done with it? This all looks too complex, too much repetition, too much! What did we gain by separating all this?&lt;/p&gt;

&lt;p&gt;Software lives, breathes, and changes. That’s just a fact of life. Especially while developing it, but even when in maintenance mode — no new features, just bug fixes. So, let’s imagine what happens next:&lt;/p&gt;

&lt;h4&gt;
  
  
  Change request — Add payment method!
&lt;/h4&gt;

&lt;p&gt;That would make the BillPaymentPage present a new step with a list of payment methods before showing the confirmation dialog. No need to change the UI around that. And we have the "multi-payment-method" logic all in our page where we can extract it away in case we'd like to use it in another "Page".&lt;/p&gt;

&lt;h4&gt;
  
  
  Change request — Use the same confirmation dialog on the logout button!
&lt;/h4&gt;

&lt;p&gt;Our confirmation dialog knows only what it needs and can be easily placed in as many places as needed! No bills-payment-page-specific logic in it!&lt;/p&gt;

&lt;h4&gt;
  
  
  Bug — After hide the data does not refresh.
&lt;/h4&gt;

&lt;p&gt;It only affects business logic so we can write a unit test, and then fix the bug, making sure we see no more of that!&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;How about Angular Services?&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;That’s a good question — why not use what the framework has provided? We can. Go right ahead. It can be used just as easy as the “Page” would. It is standard and well-known, which is a good thing for the people using it! It has a few downsides too. Very often these services start as entity specific request proxies (UserService, BillsService) and then become page-specific. What I mean is a situation similar to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I have a change request for the BillsPaymentPage&lt;/li&gt;
&lt;li&gt;I see the BillsService does the Http Requesting of the Bill entity&lt;/li&gt;
&lt;li&gt;I’ll just put my implementation in the BillsService&lt;/li&gt;
&lt;li&gt;Soon the BillsService becomes the BillsPaymentPageService, but the name remains the same&lt;/li&gt;
&lt;li&gt;repeat from the top for another developer for a different page&lt;/li&gt;
&lt;li&gt;now the BillsService is one giant ball of mud and no one can figure it out, let alone try to refactor or reuse any of its logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And then the Page:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it has a name that explains what it is responsible for&lt;/li&gt;
&lt;li&gt;it can own the data — cache it or not and it can reliably do so because it is the single source of truth&lt;/li&gt;
&lt;li&gt;no one will mistake the BillsOverviewPage responsibility with the BillsPaymentPage&lt;/li&gt;
&lt;/ul&gt;




</description>
      <category>rules</category>
      <category>frontend</category>
      <category>separationofconcerns</category>
      <category>businesslogic</category>
    </item>
    <item>
      <title>Angular TestBed considered harmful!</title>
      <dc:creator>Georgi Parlakov</dc:creator>
      <pubDate>Tue, 19 May 2020 03:31:28 +0000</pubDate>
      <link>https://dev.to/gparlakov/angular-testbed-considered-harmful-ikc</link>
      <guid>https://dev.to/gparlakov/angular-testbed-considered-harmful-ikc</guid>
      <description>&lt;p&gt;&lt;a href="https://medium.com/@gparlakov/angular-testbed-considered-harmful-3f91f647d1fd?source=rss-8b336464cb11------2"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HBoqzxgd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1920/1%2AzRrWMheynuR0VHmmepHr5A.jpeg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Why I don’t use Angular TestBed and perhaps you shouldn’t either&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/@gparlakov/angular-testbed-considered-harmful-3f91f647d1fd?source=rss-8b336464cb11------2"&gt;Continue reading on Medium »&lt;/a&gt;&lt;/p&gt;

</description>
      <category>angular</category>
      <category>javascript</category>
      <category>unittesting</category>
      <category>programming</category>
    </item>
    <item>
      <title>How to read Azure Dev Ops logs from Node.js using REST API</title>
      <dc:creator>Georgi Parlakov</dc:creator>
      <pubDate>Wed, 22 Apr 2020 14:17:52 +0000</pubDate>
      <link>https://dev.to/gparlakov/how-to-read-azure-dev-ops-logs-from-node-js-using-rest-api-4f2n</link>
      <guid>https://dev.to/gparlakov/how-to-read-azure-dev-ops-logs-from-node-js-using-rest-api-4f2n</guid>
      <description>&lt;h2&gt;
  
  
  Step by step guide
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Getting some logs can't be that hard, can it? I can surely do that in a few lines of code!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Well, that's what I though... initially. It turns out it's more than just calling a GET endpoint.&lt;/p&gt;

&lt;p&gt;Some of it is that the response from the logs endpoint is actually a zip file. And inside that there are multiple file entries - one for each pipeline task. Plus there's the authorization part. So...&lt;/p&gt;

&lt;p&gt;This article will take you step by step from a blank file to having the logs from your Azure Dev Ops Release pipeline. As you may know they are available online, but to get to them there are a couple of steps/clicks one must go through. You may want to get the logs and process them programmatically. For example I had to check if a particular string is part of the Release pipeline logs.&lt;/p&gt;

&lt;p&gt;Prerequisites:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;node js - install &lt;a href="https://nodejs.org/en/download/"&gt;Windows/MacOS&lt;/a&gt; or &lt;a href="https://www.devroom.io/2011/10/24/installing-node-js-and-npm-on-ubuntu-debian/"&gt;Linux&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Azure Dev Ops account - sign up &lt;a href="https://azure.microsoft.com/en-us/services/devops/"&gt;here&lt;/a&gt; (don't worry it's &lt;em&gt;free&lt;/em&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What we'll do
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Start with a blank node.js project and include dependencies - &lt;a href="////npmjs.com/packages/axios"&gt;axios&lt;/a&gt; and &lt;a href="////npmjs.com/packages/yauzl"&gt;yauzl&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Get a personal access token(PAT) from Azure Dev Ops and store it in an environment variable. Use that for authorization.&lt;/li&gt;
&lt;li&gt;Get the zipped logs via the &lt;a href="https://docs.microsoft.com/en-us/rest/api/azure/devops/"&gt;Azure Dev Ops REST API&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Unzip in memory and read the text contents.&lt;/li&gt;
&lt;li&gt;We'll read logs out of a Release Pipeline run, but at the end there is a section on how to convert the script and read a Build Pipeline script.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;If you only want the finished script here's the gist for reading logs from a &lt;a href="https://gist.github.com/gparlakov/426820697dc2574da6e6f6f6b31d5498"&gt;Release Pipeline&lt;/a&gt; and &lt;a href="https://gist.github.com/gparlakov/95e41ebee68c99baf36f36c12278deaf"&gt;Build Pipeline&lt;/a&gt;. I've left reminders &lt;code&gt;// TODO Replace with your own&lt;/code&gt; for the variables.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  My setup
&lt;/h2&gt;

&lt;p&gt;I'll be using &lt;a href="////npmjs.com/packages/ts-node"&gt;ts-node&lt;/a&gt; because I prefer typescript safety and don't want to have to deal with the transpilation step. So instead of &lt;code&gt;node index.js&lt;/code&gt; I'll do &lt;code&gt;ts-node index.ts&lt;/code&gt;. The script should work as plain js, after the types are removed if you so prefer.&lt;/p&gt;

&lt;p&gt;My shell is &lt;code&gt;bash&lt;/code&gt; running inside Windows Subsystem for Linux (&lt;a href="https://docs.microsoft.com/en-us/windows/wsl/install-win10"&gt;WSL&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Start
&lt;/h2&gt;

&lt;p&gt;In a folder &lt;code&gt;azdo-logs&lt;/code&gt; initialize a node package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;azdo-logs
&lt;span class="nb"&gt;cd &lt;/span&gt;azdo-logs
npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Expect to see an output similar to:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fRE6p-Hj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/l0fbpdomngwable86bpc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fRE6p-Hj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/l0fbpdomngwable86bpc.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;index.ts&lt;/code&gt; file and include these lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;/// &amp;lt;reference types="node" /&amp;gt;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AZURE_ACCESS_TOKEN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accessToken&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;||&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt; &lt;span class="o"&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="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Please provide an access token&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="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;token is present!&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We'd like to be sure the token is there, safely hidden in your &lt;strong&gt;private environment variable&lt;/strong&gt; and NOT checked in with the code!&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;reference&lt;/code&gt; on top gives us access to the nodejs types. You might need to install them as a dev dependency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i @types/node &lt;span class="nt"&gt;-D&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Add dependencies
&lt;/h2&gt;

&lt;p&gt;Globally install &lt;code&gt;ts-node&lt;/code&gt; and &lt;code&gt;typescript&lt;/code&gt; to execute our script.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;-g&lt;/span&gt; ts-node typescript
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Install &lt;code&gt;axios&lt;/code&gt; and &lt;code&gt;yauzl&lt;/code&gt; in our package. The flag &lt;code&gt;-s&lt;/code&gt; will save  them to our &lt;code&gt;package.json&lt;/code&gt;. And &lt;code&gt;@types/yauzl&lt;/code&gt; will give us typing, adding it to &lt;code&gt;devDependencies&lt;/code&gt; with the &lt;code&gt;-D&lt;/code&gt; flag&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i axios yauzl &lt;span class="nt"&gt;-s&lt;/span&gt;
npm i @types/yauzl &lt;span class="nt"&gt;-D&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--guJVmZ9Y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/8yjlvxcznqdfzs48f7wc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--guJVmZ9Y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/8yjlvxcznqdfzs48f7wc.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is how &lt;code&gt;package.json&lt;/code&gt; looks like now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"azdo-logs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"index.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"echo &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Error: no test specified&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; &amp;amp;&amp;amp; exit 1"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"keywords"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"author"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"license"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ISC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"axios"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^0.19.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"yauzl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^2.10.0"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"devDependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@types/yauzl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^2.9.1"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Get Azure Dev Ops token
&lt;/h2&gt;

&lt;p&gt;It can be acquired via the profile menu&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;open personal access tokens page 
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4aNcPhd7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/91zh2vgwpyhb6ldbt6nr.png" alt="Alt Text"&gt;
&lt;/li&gt;
&lt;li&gt;create a new access token with Release &lt;code&gt;Read&lt;/code&gt; permission 
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tKSrU9Oy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5m15mxd5m398dq8klass.png" alt="Alt Text"&gt;
&lt;/li&gt;
&lt;li&gt;store it because you will not be able to see it anymore(&lt;em&gt;you'll be able to recreate it if you lose it&lt;/em&gt;) 
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rrXSc4bS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3ej02n36wt4pxsbeb59l.png" alt="Alt Text"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally place that in an environment variable on you local machine or in safe storage (e.g. &lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/process/variables?view=azure-devops&amp;amp;tabs=classic%2Cbatch#secret-variables"&gt;secret environment variable&lt;/a&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;AZURE_ACCESS_TOKEN &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"token-placeholder-not-actual-thing"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;# replace token-placeholder-not-actual-thing with your token&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;or Windows command line&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight batchfile"&gt;&lt;code&gt;&lt;span class="kd"&gt;set&lt;/span&gt; &lt;span class="kd"&gt;AZURE_ACCESS_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"token-placeholder-not-actual-thing"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I've added this line to my &lt;code&gt;.bashrc&lt;/code&gt; file so the PAT is available on bash start and I don't have to remember to &lt;code&gt;export&lt;/code&gt; it every time I start a terminal. &lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8rilhXNs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/dehrtnmb6l0rhnj4d0ih.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8rilhXNs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/dehrtnmb6l0rhnj4d0ih.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;br&gt;
On Windows you can add it to your Environment Variables. &lt;em&gt;Keep in mind you'll need to restart your session (logout/login) for the env variables to take effect.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now run &lt;code&gt;ts-node index.ts&lt;/code&gt; and you should see &lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--boWSUqLM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5qlp0il9ux9bdg4jgwiv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--boWSUqLM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5qlp0il9ux9bdg4jgwiv.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;br&gt;
For more details on how to get personal access token see &lt;a href="https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&amp;amp;tabs=preview-page"&gt;this link&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Ok - we now have the token and dependencies!&lt;/p&gt;
&lt;h3&gt;
  
  
  4. Get the Azure Dev Ops Organization and Project
&lt;/h3&gt;

&lt;p&gt;To get the logs, we'll need the organization and project names as well as the release id we'd like to read the logs of. The latter would increment for each subsequent run - release 1, 2, 3 so we'd need to provide it per call. For this example, I'll target the &lt;a href="https://dev.azure.com/gparlakov/Scuri/_releaseProgress?_a=release-environment-logs&amp;amp;releaseId=58&amp;amp;environmentId=87"&gt;release pipeline&lt;/a&gt; for a package I maintain.&lt;/p&gt;

&lt;p&gt;The project name and organization we can get from the Azure Dev Ops UI: &lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZVCq17Cs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/tl6ph1g593765ev5tu3p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZVCq17Cs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/tl6ph1g593765ev5tu3p.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In my case, it's organization 'gparlakov' and project 'Scuri'. Add those lines in &lt;code&gt;index.ts&lt;/code&gt; and replace with your org and project names:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;project&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Scuri&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;organization&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gparlakov&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Authenticate
&lt;/h3&gt;

&lt;p&gt;To get authorization for the API endpoint using a personal access token (PAT) we need to send a header with the token encoded in base64 format adhering to a &lt;a href="https://docs.microsoft.com/en-us/rest/api/azure/devops/?view=azure-devops-rest-5.1#assemble-the-request"&gt;specific contract&lt;/a&gt;. Add the following at the and of &lt;code&gt;index.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Basic &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`PAT:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-TFS-FedAuthRedirect&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Suppress&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// we can't handle auth redirect so - suppress&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;const&lt;/span&gt; &lt;span class="nx"&gt;axiosInstance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;baseURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`https://vsrm.dev.azure.com/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;organization&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/_apis/`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We need to import the axios module, at the top of &lt;code&gt;index.ts&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;axios&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  6. Get the logs for your release
&lt;/h3&gt;

&lt;p&gt;For this example, I'll use an actual release with the &lt;code&gt;id&lt;/code&gt; of &lt;code&gt;58&lt;/code&gt; (replace with your own). Appending to &lt;code&gt;index.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;releaseId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;58&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;axiosInstance&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`release/releases/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;releaseId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/logs`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;responseType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stream&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="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;logs&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;logs missing&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Received bytes:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;read&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Running &lt;code&gt;ts-node index.ts&lt;/code&gt; should yield something similar to:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--H3ckuh2K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/vqg1gwl37gpnqbzrh46b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--H3ckuh2K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/vqg1gwl37gpnqbzrh46b.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That proves we are authorized to use this REST API endpoint!&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Unzip the logs
&lt;/h3&gt;

&lt;p&gt;Delete or comment out the &lt;code&gt;console.log&lt;/code&gt; line - we'll not need it for now and change the &lt;code&gt;axiosInstance&lt;/code&gt; call so it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;axiosInstance&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`release/releases/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;releaseId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/logs`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;responseType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stream&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="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;logs&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;logs missing&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;readLogs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&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;then&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;logs&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;and finally the &lt;code&gt;readLogs&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;readLogs&lt;/span&gt;
  &lt;span class="nx"&gt;zipBuffer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NodeJS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReadableStream&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;skippedFor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&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;span class="c1"&gt;// we'll reject the promise when we can't read anything from the zip&lt;/span&gt;
  &lt;span class="c1"&gt;// and resolve it when we could read (some) plus add the errors for the skipped parts&lt;/span&gt;
  &lt;span class="c1"&gt;// in the end we'd like to say - yes the logs contain the Proof OR no the logs do not contain the proof but there were skipped parts&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rej&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="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;es&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;zipChunks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

    &lt;span class="nx"&gt;zipBuffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data&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;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;zipChunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nx"&gt;zipBuffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;end&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;yauzl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fromBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;zipChunks&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;lazyEntries&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="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;zipfile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// can not even open the archive just reject the promise&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;rej&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;zipfile&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

          &lt;span class="nx"&gt;zipfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;entry&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="c1"&gt;// Directory file names end with '/'.&lt;/span&gt;
              &lt;span class="c1"&gt;// Note that entries for directories themselves are optional.&lt;/span&gt;
              &lt;span class="c1"&gt;// An entry's fileName implicitly requires its parent directories to exist.&lt;/span&gt;
              &lt;span class="nx"&gt;zipfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readEntry&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="c1"&gt;// file entry&lt;/span&gt;
              &lt;span class="nx"&gt;zipfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;openReadStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;readStream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                  &lt;span class="nx"&gt;es&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                  &lt;span class="c1"&gt;// skip this one - could not read it from zip&lt;/span&gt;
                  &lt;span class="nx"&gt;zipfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readEntry&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;readStream&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                  &lt;span class="c1"&gt;// just skip - could not get a read stream from it&lt;/span&gt;
                  &lt;span class="nx"&gt;es&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Could not create a readable stream for the log &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nx"&gt;fileName&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
                        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;missing file name&amp;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;zipfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readEntry&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                  &lt;span class="nx"&gt;readStream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data&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;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
                  &lt;span class="nx"&gt;readStream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&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;e&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;es&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="c1"&gt;// skip this one - could not read it from zip&lt;/span&gt;
                    &lt;span class="nx"&gt;zipfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readEntry&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                  &lt;span class="p"&gt;});&lt;/span&gt;
                  &lt;span class="nx"&gt;readStream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;end&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nx"&gt;zipfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readEntry&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                  &lt;span class="p"&gt;});&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
              &lt;span class="p"&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;zipfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;once&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;end&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;zipfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="na"&gt;skippedFor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;es&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
          &lt;span class="p"&gt;});&lt;/span&gt;

          &lt;span class="nx"&gt;zipfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readEntry&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// can't read the archive - reject the promise&lt;/span&gt;
          &lt;span class="nx"&gt;rej&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Could not read the zipfile contents&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="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;There seems to be a lot going on here. It boils down to working with 3 streams.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First, we read the &lt;code&gt;zipFile&lt;/code&gt; push into the &lt;code&gt;zipChunks&lt;/code&gt; and concat those into a &lt;code&gt;Buffer&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Then, use that &lt;code&gt;Buffer&lt;/code&gt; in the &lt;code&gt;yauzl.fromBuffer()&lt;/code&gt; call which returns an object that has a &lt;code&gt;readEntry()&lt;/code&gt; method. I think of it as a &lt;code&gt;next&lt;/code&gt;, because it reads the next entry in the archive.&lt;/li&gt;
&lt;li&gt;We get a &lt;code&gt;readStream&lt;/code&gt; for each zip file entry. That is a &lt;code&gt;ReadableStream&lt;/code&gt; that we push into the &lt;code&gt;chunks&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Finally, we concat all files' chunks into a buffer and read a string out of it:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Done!
&lt;/h3&gt;

&lt;p&gt;We now have a &lt;code&gt;string&lt;/code&gt; variable containing all our logs!&lt;/p&gt;

&lt;p&gt;Here's a gist of the final &lt;a href="https://gist.github.com/gparlakov/426820697dc2574da6e6f6f6b31d5498"&gt;index.ts&lt;/a&gt;. I've left reminders &lt;code&gt;// TODO Replace with your own&lt;/code&gt; for the variables.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reading a build pipeline logs
&lt;/h3&gt;

&lt;p&gt;To read the logs from a build pipeline, we would need to&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add the "Build: Read" permission to our token or issue a new one with that permission:
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--x_9m5kMi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/csbuv3rlsad1pyi6wwjq.png" alt="Alt Text"&gt;
&lt;/li&gt;
&lt;li&gt;Change a bit (just remove one piece) the auth logic:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Basic &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-TFS-FedAuthRedirect&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Suppress&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// we can't handle auth redirect so - suppress&lt;/span&gt;
   &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Change the base URL:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;   &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;axiosInstance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
     &lt;span class="na"&gt;baseURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`https://dev.azure.com/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;organization&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/_apis/`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Change the endpoint address and provide a build number (in my case I'll use &lt;a href="https://dev.azure.com/gparlakov/Scuri/_build/results?buildId=208&amp;amp;view=logs&amp;amp;j=12f1170f-54f2-53f3-20dd-22fc7dff55f9"&gt;this build&lt;/a&gt;)
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buildId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="nx"&gt;axiosInstance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`build/builds/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;buildId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/logs`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="na"&gt;responseType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stream&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="na"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/zip&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Here's the final &lt;a href="https://gist.github.com/gparlakov/95e41ebee68c99baf36f36c12278deaf"&gt;script&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Memory consumption note
&lt;/h3&gt;

&lt;p&gt;This whole approach keeps a few buffers in memory, basically copying the zip file a few times* in memory. Considering that we are reading pipeline logs, this should not be a problem. I expect they won't be too large. If that's a problem for you, store the archive locally (though that may be a &lt;a href="https://en.wikipedia.org/wiki/Zip_bomb"&gt;security consern&lt;/a&gt; as Samuel Attard @marshallofsound pointed out) and then use the other method of &lt;code&gt;yauzl&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createWriteStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-temp-zip-file.zip&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="nx"&gt;yauzl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-temp-zip-file.zip&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="na"&gt;lazyEntries&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="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;zipfile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;//... same code from here on down&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;*the response stream, the chunks, the buffer, the zip content chunks, their buffer and finally the string - for a 50k log we end up using 250k RAM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Restful API &lt;a href="https://docs.microsoft.com/en-us/rest/api/azure/devops/?view=azure-devops-rest-5.1"&gt;docs&lt;/a&gt; - really helpful&lt;/li&gt;
&lt;li&gt;nodejs &lt;a href="https://github.com/microsoft/azure-devops-node-api"&gt;client&lt;/a&gt; for the API (but its around 116k minified+GZipped! according to &lt;a href="https://bundlephobia.com/result?p=azure-devops-node-api@10.1.1"&gt;bundlephobia&lt;/a&gt; ~830k worth of script for your runtime to parse - &lt;strong&gt;for each request&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;Docs for &lt;a href="https://github.com/axios/axios#request-config"&gt;axios&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Docs for &lt;a href="https://github.com/thejoshwolfe/yauzl"&gt;yauzl&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>node</category>
      <category>azdo</category>
      <category>zip</category>
      <category>rest</category>
    </item>
    <item>
      <title>5+ tips for Angular Library authors</title>
      <dc:creator>Georgi Parlakov</dc:creator>
      <pubDate>Sun, 05 Apr 2020 14:06:53 +0000</pubDate>
      <link>https://dev.to/gparlakov/5-tips-for-angular-library-authors-1ino</link>
      <guid>https://dev.to/gparlakov/5-tips-for-angular-library-authors-1ino</guid>
      <description>&lt;p&gt;Here’s what works. I found that out the hard way.&lt;/p&gt;

&lt;p&gt;These tips are the result of me publishing a few Angular libraries and making some silly mistakes. Hope you find some of them useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use the &lt;strong&gt;CLI&lt;/strong&gt; and library schematic (ng g library) to scaffold your library. It does a lot of work you’ll otherwise need to do manually: Updates angular.json and adds an ng-package.json config file which allows package config. Ng packager emits a standard Angular library with a public-api.ts which is what is expected by the ng compiler.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jzTcL9Kt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/b964357ohp66flv6ncox.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jzTcL9Kt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/b964357ohp66flv6ncox.png" alt="Command line output of the command ng generate library useful-lib. Creating files and updating existing ones."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A library has it’s &lt;strong&gt;own package.json (&lt;/strong&gt;and readme.md, tsconfig.lib.json, etc.&lt;strong&gt;)&lt;/strong&gt;. That makes sense since you'd like to publish the library and the app separately and &lt;strong&gt;independently&lt;/strong&gt; from one another. So you’ll need to update them and not the main app ones (like I did :). If we take the useful-lib example from the previous tip:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TlOe0J6h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/fv8cg5ok0skrunl8n35v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TlOe0J6h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/fv8cg5ok0skrunl8n35v.png" alt="The folder view of an Angular workspace with a library highlighting there are actually two package.json files."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Add the repository to lib’s package.json "repository":"github.com/author/repo". Users would want to visit your open source repo too for issues, code, etc. And allows NPM to get images and other assets from it. That is if your readme.md file references an image from /assets/my-lib.png npm will figure out that it’s actually &lt;a href="https://github.com/author/my-lib/assets/my-lib.png/raw"&gt;https://raw.githubusercousntent.com/author/my-lib/HEAD?assets/my-lib.png&lt;/a&gt;. &lt;em&gt;For real-world example see the first image in my&lt;/em&gt; &lt;a href="https://www.npmjs.com/package/ngx-show-form-control"&gt;&lt;em&gt;package&lt;/em&gt;&lt;/a&gt; &lt;em&gt;and it’s&lt;/em&gt; &lt;a href="https://raw.githubusercontent.com/gparlakov/forms-typed/master/projects/show-form-control/README.md"&gt;&lt;em&gt;raw readme&lt;/em&gt;&lt;/a&gt; &lt;em&gt;for example.&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use tags&lt;/strong&gt;! Example: &lt;code&gt;npm publish dist\my-package --tag alpha&lt;/code&gt;. Anything not tagged is considered latest and will be installed for users doing npm install. Meaning that your package version 1.0.0-alpha.0 published without tags (hence tagged latest) will end up in the users’ node_modules. Then you’ll start getting issues as for production-ready version. Here’s a snapshot of a package I maintain:&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Sm78HPo6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/l0qx6xs9lb2v16hc0t1e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Sm78HPo6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/l0qx6xs9lb2v16hc0t1e.png" alt="npm version"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Document it and make it pretty — add &lt;strong&gt;pics&lt;/strong&gt; , &lt;strong&gt;quick start, examples,&lt;/strong&gt; and all the things users need to get started using a project. As the author, it’s easy to forget about those things because you know your work in and out. Users don’t so try and make them feel at home by giving them a guide. Think of what you’ll need or better yet — have someone try and use your library and give you feedback. Here’s a pretty, if somewhat contrived example of readme:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1m5tyMs---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3npsvoi8c4x82gbe1tyg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1m5tyMs---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3npsvoi8c4x82gbe1tyg.png" alt="readme example"&gt;&lt;/a&gt;&lt;/p&gt;
Thanks, &lt;a href="https://unsplash.com/@hansonluu"&gt;https://unsplash.com/@hansonluu&lt;/a&gt; for the pretty pic



&lt;ul&gt;
&lt;li&gt;Don’t publish manually — have an automated process that does it for you. When fixing a bug or adding a feature you focus on development. The packaging and pushing to NPM (&lt;em&gt;or any other package registry&lt;/em&gt;) is not interesting so it’s easy to mess it up or even completely forget about it. I know I have. An example from my &lt;a href="https://dev.azure.com/gparlakov/Scuri/_release?_a=releases&amp;amp;view=mine&amp;amp;definitionId=1"&gt;AzDO&lt;/a&gt; pipeline:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--F2cEsPNK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/y6yh2r1fmtbsea58bogh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--F2cEsPNK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/y6yh2r1fmtbsea58bogh.png" alt="pipeline examples"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read the angular docs about libraries: &lt;a href="https://angular.io/guide/creating-libraries"&gt;Angular.io&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks for reading. I hope it helps!&lt;/p&gt;




&lt;h2&gt;
  
  
  🧞‍🙏 Thanks for reading!
&lt;/h2&gt;

&lt;p&gt;Give some 💖 or 🦄  if you liked this post.&lt;br&gt;
These help other people find this post and encourage me to write more. Thanks!&lt;/p&gt;

&lt;p&gt;Check out my projects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://npmjs.com/packages/scuri"&gt;SCuri&lt;/a&gt; — Unit test boilerplate automation (with Enterprise support option too)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://npmjs.com/packages/ngx-forms-typed"&gt;ngx-forms-typed&lt;/a&gt; — Angular form, only strong typed!&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://npmjs.com/packages/ngx-show-form-control"&gt;ngx-show-form-control&lt;/a&gt; — Visualize/Edit any FormControl/Group&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://www.buymeacoffee.com/bHQk8Cu"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PT2F9GG6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.buymeacoffee.com/buttons/bmc-new-btn-logo.svg" alt="Buy me a coffee"&gt;&lt;span&gt;Buy me a coffee&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>angular</category>
      <category>package</category>
      <category>npm</category>
    </item>
    <item>
      <title>Angular Forms Story: Strong Types</title>
      <dc:creator>Georgi Parlakov</dc:creator>
      <pubDate>Thu, 02 Apr 2020 15:53:30 +0000</pubDate>
      <link>https://dev.to/gparlakov/angular-forms-story-strong-types-dmg</link>
      <guid>https://dev.to/gparlakov/angular-forms-story-strong-types-dmg</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uO992n_z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A83ZeAMjJiNn9se6GGuyasw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uO992n_z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A83ZeAMjJiNn9se6GGuyasw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How I strongly typed my Angular forms. And you can too!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published in Angular-In-Depth&lt;/em&gt; &lt;a href="https://indepth.dev/angular-forms-story-strong-types/"&gt;&lt;em&gt;here&lt;/em&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is part of the &lt;em&gt;Angular Forms Story&lt;/em&gt; series:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Angular Forms Story: Dev Tooling (&lt;/em&gt;&lt;a href="https://dev.to/gparlakov/angular-forms-story-a-dev-tool-2ejc"&gt;&lt;em&gt;link&lt;/em&gt;&lt;/a&gt;&lt;em&gt;)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Angular Forms Story: Strong Types&lt;/em&gt; (this one)&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Angular Forms Story: The Whole Story&lt;/em&gt; (coming up)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Angular Reactive Forms are &lt;strong&gt;not strongly typed&lt;/strong&gt;! AbstractControl and it’s implementations FormControl FormGroup and FormArray do not support strong typing their value or changes or any other property/method. For the longest time, I accepted and worked around that, considering it just one of life’s things — so it goes…&lt;/p&gt;

&lt;p&gt;In this article, I want to share the process I went through while gradually &lt;strong&gt;adding strong types&lt;/strong&gt; to my forms. If you just want to see the end result see &lt;a href="https://www.npmjs.com/package/ngx-forms-typed"&gt;ngx-forms-typed&lt;/a&gt;. It’s &lt;strong&gt;strong type, backward compatible&lt;/strong&gt; (&lt;em&gt;works with Angular 2.4.0 !&lt;/em&gt;) and &lt;strong&gt;reduces boilerplate.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This work draws inspiration and ideas from&lt;/em&gt; &lt;a href="https://timdeschryver.dev/blog/working-with-angular-forms-in-an-enterprise-environment"&gt;&lt;em&gt;“Working with Angular forms in an enterprise environment”&lt;/em&gt;&lt;/a&gt; &lt;em&gt;and&lt;/em&gt; &lt;a href="https://dev.to/maxime1992/building-scalable-robust-and-type-safe-forms-with-angular-3nf9"&gt;&lt;em&gt;“Building scalable robust and type safe forms with Angular”&lt;/em&gt;&lt;/a&gt; &lt;em&gt;and others. I would&lt;/em&gt; &lt;strong&gt;&lt;em&gt;encourage&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;you to check those articles out.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For my job, I had to do a reasonably large form, enough so to make me fear the &lt;strong&gt;business&lt;/strong&gt; logic &lt;strong&gt;mixing&lt;/strong&gt; with the &lt;strong&gt;form&lt;/strong&gt; logic and sub-parts of the form. I wanted to have a &lt;strong&gt;strongly-typed&lt;/strong&gt; , &lt;strong&gt;easy&lt;/strong&gt; to use and &lt;strong&gt;@angular/forms_-y_&lt;/strong&gt;  &lt;strong&gt;way&lt;/strong&gt; to extract sub-forms components from a large form. Much like we extract components to handle their own concerns. The (sub)form component would need to conform to the following requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;have a &lt;strong&gt;strongly typed&lt;/strong&gt;  model&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;@angular/forms &lt;/strong&gt; —  &lt;strong&gt;compatible&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;use &lt;strong&gt;existing abstraction&lt;/strong&gt; for &lt;strong&gt;communication&lt;/strong&gt; (the AbstractControl and the ControlValueAccessor) to handle validation, status changes, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In short, I wanted to have a &lt;strong&gt;component&lt;/strong&gt; that could be used as a &lt;strong&gt;single control&lt;/strong&gt; in a &lt;strong&gt;larger form&lt;/strong&gt; &lt;em&gt;AND&lt;/em&gt; be a &lt;strong&gt;form&lt;/strong&gt; in its &lt;strong&gt;own right&lt;/strong&gt; with multiple fields, validation, status changes. And all the while keeping the &lt;strong&gt;parent form&lt;/strong&gt; happily unaware of implementation details and communicating via the AbstractControl API.&lt;br&gt;&lt;br&gt;
For example Address as a sub-form in a Person form and in an Order form. But the full form in a NewAddress form.&lt;/p&gt;

&lt;p&gt;In the beginning, I was decorating my FormGroup-s and their constituent FormControl-s with types, like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--y5cF8WMY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/918/1%2A904NyfxnM8nTg3GuJcjd7w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--y5cF8WMY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/918/1%2A904NyfxnM8nTg3GuJcjd7w.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now I could &lt;strong&gt;rely&lt;/strong&gt; on strong typing during refactoring or adding/removing features. Any Person &lt;strong&gt;type change&lt;/strong&gt; would trickle down to the form — for example, if the property address was to be added I would get an &lt;strong&gt;error immediately&lt;/strong&gt; (&lt;em&gt;vs run time and maybe&lt;/em&gt;):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QNMLqgG1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2ATXzVvV0KuClxZO0PQphBqA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QNMLqgG1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2ATXzVvV0KuClxZO0PQphBqA.png" alt="Screenshot describing an error of type: “ Property ‘address’ is missing in type ‘{ name: FormControl; email: FormControl; }’"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then I suffered the form.controls.get('name') way of reaching my controls. I did not like the pattern of creating a public getter for it. Mainly, I wanted &lt;strong&gt;type safety&lt;/strong&gt;. So:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--y5cF8WMY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/918/1%2A904NyfxnM8nTg3GuJcjd7w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--y5cF8WMY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/918/1%2A904NyfxnM8nTg3GuJcjd7w.png" alt="Screenshot demonstrating `controls` FormGroup property being strong typed."&gt;&lt;/a&gt;Yaay, intellisense! Productivity!&lt;/p&gt;

&lt;p&gt;Now I had intellisense for my controls! Oh joy and productivity 😂⚒👷‍♂️. And with a bit more finesse (&lt;em&gt;and beating Typescript into submission&lt;/em&gt;) I had a type-dependent form group (or model-dependent if you will). See this little change trickling down:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0wDA7BFS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AXKsqKXHA5xqd2509GDRb3g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0wDA7BFS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AXKsqKXHA5xqd2509GDRb3g.png" alt=""&gt;&lt;/a&gt;Notice the &lt;code&gt;email&lt;/code&gt; changed into &lt;code&gt;emailS&lt;/code&gt; and causing Typescript giving me very helpful advice :)&lt;/p&gt;

&lt;p&gt;Did you notice the as unknown as PersonFormGroup (at the end of the form group instantiation form = new Form(…) as unknown …)— that’s what I was referring to as &lt;em&gt;beating Typescript&lt;/em&gt;. In this case, I knew better than it what the actual shape of the run-time thing is! This is the only case so far usually, Typescript❤ ️knows better!&lt;/p&gt;

&lt;p&gt;Taking this work to the next step is to type as much of the FormControl/FormGroup/FormArray types as possible. And the result is the package &lt;a href="https://www.npmjs.com/package/ngx-forms-typed"&gt;ngx-forms-typed&lt;/a&gt;. It provides the TypedFormControl (&lt;a href="https://github.com/gparlakov/forms-typed/blob/21e99c91877746b506dd64ad0e5a127eeed15bac/projects/forms/src/lib/forms-typed.ts#L29-L36"&gt;GitHub&lt;/a&gt; src)TypedFormGroupand TypedFormArray types.&lt;/p&gt;

&lt;p&gt;For example, let’s take a look at TypedFormControl:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;It adds a strong type to the value and valueChanges properties of FormControl so that you know what shape the value is when using it. It also strong types the method setValue so that you’d need to pass in a value of the expected type and maybe options — also strong typed. reset method is strongly typed too. See ResetValue &lt;a href="https://github.com/gparlakov/forms-typed/blob/12e51ba0f19ce5d812399c8bf1ec4d159247891f/projects/forms/src/lib/forms-typed.ts#L17"&gt;source&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And &lt;strong&gt;helper functions&lt;/strong&gt; for creating instances with those types typedFormControl(&lt;a href="https://github.com/gparlakov/forms-typed/blob/21e99c91877746b506dd64ad0e5a127eeed15bac/projects/forms/src/lib/forms-typed.ts#L37-L55"&gt;GitHub src&lt;/a&gt;), typedFormArray and typedFormGroup which make the creation of forms strongly typed too:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--W3MsSdYr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AlxN6LNnl8NNgiA3VFvBgLg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--W3MsSdYr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AlxN6LNnl8NNgiA3VFvBgLg.png" alt=""&gt;&lt;/a&gt;TS error pushing us towards the correct type of the forms control.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CVz3C35g--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AamcHNuZb-ea7iNnKaIH8-A.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CVz3C35g--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AamcHNuZb-ea7iNnKaIH8-A.png" alt=""&gt;&lt;/a&gt;Does intellisense too!&lt;/p&gt;

&lt;p&gt;The function itself only instantiates a FormGroup which is to say that it is &lt;strong&gt;compatible&lt;/strong&gt; with &lt;strong&gt;existing forms&lt;/strong&gt; code and can be used next to it &lt;strong&gt;without breaking&lt;/strong&gt;  it:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;I wanted to enable forms and sub-forms to communicate i.e. when sub-form get’s touched — touch the parent when it gets invalid — make the parent invalid too. These two are &lt;strong&gt;supported&lt;/strong&gt; by the ControlValueAccessor abstraction. I also wanted to force the sub-form to get touched on cue from the parent, in order to show validation, which is &lt;strong&gt;not supported&lt;/strong&gt; by ControlValueAccessor. I wanted to use existing ControlValueAccessor-AbstractControl channels of communication.&lt;/p&gt;

&lt;p&gt;I came up with a builder-like pattern for interaction with controls of a form group/array. It comes in a function called forEachControlIn (see full code in &lt;a href="https://github.com/gparlakov/forms-typed/blob/master/projects/forms/src/lib/forms-util.ts"&gt;GitHub&lt;/a&gt;). Its goal is to make interacting with controls in a form and between forms easy. It &lt;strong&gt;relies&lt;/strong&gt; on having &lt;strong&gt;references&lt;/strong&gt; to the forms.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--i5s9cjtk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2A5fqB3okBdg2mR-A_.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--i5s9cjtk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2A5fqB3okBdg2mR-A_.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, to avoid boilerplate I tied it all in a ControlValueAccessorConnector (see full code on &lt;a href="https://github.com/gparlakov/forms-typed/blob/master/projects/forms/src/lib/control-value-accessor-connector.ts"&gt;GitHub&lt;/a&gt;). It handles all the connection logic between a parent form and a sub-form.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jtidpHVK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2AGT3_uWEirg5xKkYB.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jtidpHVK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2AGT3_uWEirg5xKkYB.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;See a working example &lt;a href="https://angular-forms-story-strong-types.stackblitz.io/"&gt;here&lt;/a&gt; and code in Stackblitz:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://stackblitz.com/edit/angular-forms-story-strong-types?embed=1&amp;amp;file=src/app/party-form/party-form.component.ts&amp;amp;view=editor"&gt;angular-forms-story-strong-types - StackBlitz&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Just like a user would expect, pressing the Submit button shows validation for the whole page. Blurring an input control shows validation only for that control.&lt;/p&gt;

&lt;p&gt;There’s an example of a nested form using formGroup directive in the party-form.component and a stand-alone one, using ngModel in app.component.&lt;/p&gt;

&lt;p&gt;You can see the library’s readme &lt;a href="https://github.com/gparlakov/forms-typed/blob/master/projects/forms/README.md"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This was a short introduction to the library. Would you like to see a deep dive? Vote &lt;a href="https://forms.gle/Hm1R2uqFmq4n7q6k8"&gt;here&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/media/2c1089cfe2771558f59ffd9ec0463dc4/href"&gt;&lt;/a&gt;&lt;a href="https://medium.com/media/2c1089cfe2771558f59ffd9ec0463dc4/href"&gt;https://medium.com/media/2c1089cfe2771558f59ffd9ec0463dc4/href&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This approach (and package) is not the only one and there are several other approaches. Check out the next article in the series where I summarize my research in the @angular/forms space of packages, PRs, and articles.&lt;/p&gt;

&lt;p&gt;I am working on a few Open Source Angular Dev Tooling projects. Check them out at:&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/gparlakov/scuri/blob/master/README.md"&gt;SCuri — Unit test boilerplate automation&lt;/a&gt; (&lt;a href="https://tidelift.com/subscription/pkg/npm-scuri?utm_source=npm-scuri&amp;amp;utm_medium=referral&amp;amp;utm_campaign=enterprise"&gt;&lt;em&gt;with Enterprise support option too&lt;/em&gt;&lt;/a&gt;)&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/gparlakov/forms-typed/blob/master/projects/forms/README.md"&gt;ngx-forms-typed — Angular form, only strong typed!&lt;/a&gt;&lt;br&gt;&lt;br&gt;
 &lt;a href="https://github.com/gparlakov/forms-typed/blob/master/projects/show-form-control/README.md"&gt;ngx-show-form-control — Visualize/Edit any FormControl/Group&lt;/a&gt;&lt;/p&gt;

</description>
      <category>reactiveforms</category>
      <category>angular</category>
      <category>typescript</category>
      <category>strongtyping</category>
    </item>
    <item>
      <title>Angular Forms Story: A Dev Tool</title>
      <dc:creator>Georgi Parlakov</dc:creator>
      <pubDate>Tue, 17 Mar 2020 21:24:26 +0000</pubDate>
      <link>https://dev.to/gparlakov/angular-forms-story-a-dev-tool-2ejc</link>
      <guid>https://dev.to/gparlakov/angular-forms-story-a-dev-tool-2ejc</guid>
      <description>&lt;h4&gt;
  
  
  A simple tool to make Dev’s life easy :)
&lt;/h4&gt;

&lt;p&gt;This is part of the &lt;em&gt;Angular Forms Story&lt;/em&gt; series:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Angular Forms Story: A Dev Tool (this one)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Angular Forms Story: Strong Types (&lt;a href="https://dev.to/gparlakov/angular-forms-story-strong-types-dmg"&gt;&lt;em&gt;link&lt;/em&gt;&lt;/a&gt;)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Angular Forms Story: The Whole Story (coming up)&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More than once, while developing forms in Angular application I had to template hack &lt;code&gt;{{form.value | json}}&lt;/code&gt; and &lt;code&gt;{{form.valid}}&lt;/code&gt; and form.pending while editing to check if my form control was actually doing what I wanted it to do.&lt;br&gt;&lt;br&gt;
I thought:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Wouldn’t it be nice if there was a component to show me all the info about the form?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So I created it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/ngx-show-form-control"&gt;ngx-show-form-control&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KlDruukl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A1BAlwPZqnLHawbezLSQqOQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KlDruukl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A1BAlwPZqnLHawbezLSQqOQ.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Named&lt;/strong&gt; and &lt;strong&gt;draggable&lt;/strong&gt; window!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Status &lt;/strong&gt; — dirty/pristine, touched/untouched, valid/invalid/pending/disabled&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Value&lt;/strong&gt; JSON.stringify-ed. &lt;strong&gt;Editable &lt;/strong&gt; — edited value appears in form!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minify&lt;/strong&gt; and &lt;strong&gt;Maximize&lt;/strong&gt; And MORE…
* &lt;strong&gt;Resizable&lt;/strong&gt; (see bottom right corner)
* Multiple &lt;strong&gt;instances&lt;/strong&gt; supported
* &lt;strong&gt;Only&lt;/strong&gt; visible in &lt;strong&gt;dev&lt;/strong&gt; builds! &lt;strong&gt;No&lt;/strong&gt; danger of it showing in &lt;strong&gt;production&lt;/strong&gt;!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;See it in  &lt;strong&gt;action&lt;/strong&gt; :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zVZKeTUN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://cdn-images-1.medium.com/max/640/0%2A1woRcThhCMII4edO.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zVZKeTUN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://cdn-images-1.medium.com/max/640/0%2A1woRcThhCMII4edO.gif" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Making it a directive and supporting older Angular versions is on the roadmap. It now needs Angular v5 at least — due to using Metadata version 4&lt;/p&gt;

&lt;p&gt;Go check it out and submit issues at &lt;a href="https://github.com/gparlakov/forms-typed/issues"&gt;https://github.com/gparlakov/forms-typed/issues&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I am working on a few Open Source Angular Dev Tooling projects. Check them out at:&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/gparlakov/scuri/blob/master/README.md"&gt;SCuri — Unit test boilerplate automation&lt;/a&gt; (&lt;a href="https://tidelift.com/subscription/pkg/npm-scuri?utm_source=npm-scuri&amp;amp;utm_medium=referral&amp;amp;utm_campaign=enterprise"&gt;&lt;em&gt;with Enterprise support option too&lt;/em&gt;&lt;/a&gt;)&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/gparlakov/forms-typed/blob/master/projects/forms/README.md"&gt;ngx-forms-typed — Angular form, just typed!&lt;/a&gt;&lt;br&gt;&lt;br&gt;
 &lt;a href="https://github.com/gparlakov/forms-typed/blob/master/projects/show-form-control/README.md"&gt;ngx-show-form-control — Visualize/Edit any FormControl/Group&lt;/a&gt;&lt;/p&gt;

</description>
      <category>tooling</category>
      <category>development</category>
      <category>angular</category>
      <category>devtools</category>
    </item>
    <item>
      <title>Angular Unit Test Automation</title>
      <dc:creator>Georgi Parlakov</dc:creator>
      <pubDate>Tue, 15 Oct 2019 06:59:56 +0000</pubDate>
      <link>https://dev.to/gparlakov/angular-unit-test-automation-43po</link>
      <guid>https://dev.to/gparlakov/angular-unit-test-automation-43po</guid>
      <description>&lt;p&gt;How can I automate my unit test creation and update chores?&lt;/p&gt;

&lt;h3&gt;
  
  
  What if I told you that I can auto-generate your unit tests? And then maintain them?
&lt;/h3&gt;

&lt;h4&gt;
  
  
  TL;DR;
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;No chores. No boring boilerplate. Just tests.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Install SCuri and &lt;strong&gt;create&lt;/strong&gt; or &lt;strong&gt;update&lt;/strong&gt; a &lt;strong&gt;spec&lt;/strong&gt; file by:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;**npm install scuri**
 **ng g scuri:spec --name src/app/my.component.ts** 
_# OR to update an existing spec_
**ng g scuri:spec --name src/app/with-spec.component.ts --update**
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;This requires &lt;a href="https://github.com/angular/angular-cli/releases/tag/v6.0.0"&gt;&lt;strong&gt;Angular CLI 6&lt;/strong&gt;&lt;/a&gt;. For previous versions see &lt;a href="https://github.com/gparlakov/scuri#rule-result-function"&gt;troubleshooting section in the readme&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vBMWdnJD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://cdn-images-1.medium.com/max/960/0%2AdAWU-QVKc5zSyBR_.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vBMWdnJD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://cdn-images-1.medium.com/max/960/0%2AdAWU-QVKc5zSyBR_.gif" alt="Screencast of using the SCuri tool on the command line"&gt;&lt;/a&gt;Caption actually uses the &lt;a href="https://www.npmjs.com/package/@angular-devkit/schematics-cli"&gt;schematics CLI&lt;/a&gt; which supports Angular &amp;lt; 6&lt;/p&gt;
&lt;h3&gt;
  
  
  Wow, this looks great. But what’s the catch?
&lt;/h3&gt;

&lt;p&gt;There is actually just one:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Structure your tests in a specific fashion.*&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;*or rather keep it as the tool generates them&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SCuri&lt;/strong&gt; assumes that the tests will have a specific &lt;strong&gt;structure&lt;/strong&gt;. It &lt;strong&gt;assumes&lt;/strong&gt; there is a &lt;strong&gt;setup&lt;/strong&gt; function (or creates one if it’s missing) where the &lt;strong&gt;setting up&lt;/strong&gt; of the spec &lt;strong&gt;conditions&lt;/strong&gt; occurs. And that the &lt;strong&gt;names&lt;/strong&gt; of the tests include the names of &lt;strong&gt;public methods&lt;/strong&gt; on the class-under-test. Read more about it in &lt;a href="https://dev.to/gparlakov/an-opinionated-approach-to-testing-angular-492f"&gt;this article (see point 4 and on&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;These assumptions &lt;strong&gt;allow&lt;/strong&gt; it (SCuri) to &lt;strong&gt;automate&lt;/strong&gt; the &lt;strong&gt;tasks&lt;/strong&gt; of creation and update of unit tests for Angular components, services, and plain old typescript classes.&lt;/p&gt;
&lt;h4&gt;
  
  
  But wait, ng c component already generates a spec, right? Why do I need another tool?
&lt;/h4&gt;

&lt;p&gt;Yes, it does. But as soon as the first &lt;strong&gt;dependency&lt;/strong&gt; is added to that generated component and the &lt;strong&gt;test&lt;/strong&gt; needs to be &lt;strong&gt;updated&lt;/strong&gt; , that’s where Angular CLI falls &lt;strong&gt;short&lt;/strong&gt;. And that’s where SCuri  &lt;strong&gt;shines&lt;/strong&gt;!&lt;/p&gt;
&lt;h3&gt;
  
  
  Let’s try it!
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Assuming SCuri is installed&lt;/em&gt; &lt;strong&gt;npm install scuri&lt;/strong&gt; &lt;em&gt;in the project folder and the&lt;/em&gt; &lt;strong&gt;&lt;em&gt;Angular&lt;/em&gt;&lt;/strong&gt;  &lt;strong&gt;&lt;em&gt;CLI&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;is version&lt;/em&gt; &lt;strong&gt;&lt;em&gt;6&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;or greater.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dl2G3awS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/917/1%2AchBXG2HVQT5Lcpr67oPv3w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dl2G3awS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/917/1%2AchBXG2HVQT5Lcpr67oPv3w.png" alt=""&gt;&lt;/a&gt;I’ve created a demo component with no tests&lt;/p&gt;

&lt;p&gt;ng g demo --skipTests&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;ng g scuri:spec --name src/app/demo/demo.component.ts&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3m4ulOIg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A8UqIH5jf0Ftxei3IZuhz9A.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3m4ulOIg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A8UqIH5jf0Ftxei3IZuhz9A.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;There are a couple of things to note here:  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;strong&gt;setup&lt;/strong&gt; function. This is where all the setting up of tests preconditions and dependencies would occur.
&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;autoSpy&lt;/strong&gt; function. This is what will create automatic mocks for your dependencies. It will take &lt;strong&gt;any class&lt;/strong&gt; and &lt;strong&gt;mock&lt;/strong&gt; its &lt;strong&gt;public&lt;/strong&gt;  &lt;strong&gt;methods&lt;/strong&gt; (s_ee details in_ &lt;a href="https://github.com/gparlakov/scuri#autospy-path-in-tsconfigjson"&gt;&lt;em&gt;SCuri readme&lt;/em&gt;&lt;/a&gt;). And it can be &lt;strong&gt;generated&lt;/strong&gt; for you by the tool!
ng g scuri:autospy — for jasmine:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---bIbBCDR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/893/1%2AA_lUzyRjzhJ2wV_bDTE1gg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---bIbBCDR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/893/1%2AA_lUzyRjzhJ2wV_bDTE1gg.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now for the fun part:&lt;/p&gt;

&lt;p&gt;Create a service:&lt;/p&gt;

&lt;p&gt;ng g service my&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RtSQ-g-a--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/656/1%2A5LBTamsm5sVGQIsiNgI3pQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RtSQ-g-a--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/656/1%2A5LBTamsm5sVGQIsiNgI3pQ.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Include it it the component dependencies. Let’s also add a public event handler method for good measure.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Now run ng g scuri:spec --name src/app/demo/demo.component.ts --update&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NLUSAUuE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AzWcLuIqE_HuPvikHnOm7XA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NLUSAUuE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AzWcLuIqE_HuPvikHnOm7XA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;And now the &lt;strong&gt;dependency&lt;/strong&gt; is &lt;strong&gt;mocked&lt;/strong&gt; , &lt;strong&gt;included&lt;/strong&gt; in the &lt;strong&gt;MyComponent build&lt;/strong&gt; function! And the &lt;strong&gt;new method&lt;/strong&gt; has a &lt;strong&gt;test scaffolded&lt;/strong&gt; for it!&lt;/p&gt;

&lt;p&gt;All that’s left is the logic, that &lt;strong&gt;only you&lt;/strong&gt; , the human being, can author.&lt;/p&gt;

&lt;h4&gt;
  
  
  What else can I use the setup function for?
&lt;/h4&gt;

&lt;p&gt;For each test case, we usually require some setup. And leaving that in the it body makes it difficult to read. So we can use the builder that setup returns and attach methods on it for setting up the specific details.&lt;/p&gt;

&lt;p&gt;For example, let’s say that we need to fetch some data in ngOnInit&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;We run ng g scuri:spec --name src/app/demo/demo.component.ts --update again to update dependencies. And now we can use the mock created by autoSpy in the test:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Or we can move the mock part in the setup builder:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Now our test is &lt;strong&gt;cleaner&lt;/strong&gt; and calls a method that is named so as to &lt;strong&gt;speak&lt;/strong&gt; to the &lt;strong&gt;reader&lt;/strong&gt; what the goal is, rather than expect them to &lt;strong&gt;read through&lt;/strong&gt; the mock declaration and &lt;strong&gt;extrapolate&lt;/strong&gt; what the author meant. The name carries the author’s &lt;strong&gt;intent&lt;/strong&gt;! &lt;em&gt;(this is a contrived and trivial example — think of what your components do and how you mock that!)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Notice how we call the withHeroes in the default builder method. That’s because every time we call ngOnInit we’ll need an observable to subscribe to, otherwise our we’ll get an exception that there is no subscribe method on undefined&lt;/p&gt;

&lt;h4&gt;
  
  
  Let’s put SCuri to the test! (:
&lt;/h4&gt;

&lt;p&gt;Let’s try it on a real-world project! What better than &lt;a href="https://github.com/gothinkster/angular-realworld-example-app"&gt;The Real World Example App&lt;/a&gt;! It’s an example Medium clone Angular app, that just so happens has no unit tests (as of today).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone [https://github.com/gothinkster/angular-realworld-example-app](https://github.com/gothinkster/angular-realworld-example-app)
cd angular-realworld-example-app
npm install scuri
ng g scuri:spec --name src/app/home/home.component.ts
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;And now the src/app/home/home.component.spec.ts has the dependencies mocked and ready, and the public method tests scaffolded. All ready for you to do the tests magick.&lt;/p&gt;

&lt;p&gt;No chores. No boring boilerplate. Just tests.&lt;/p&gt;




</description>
      <category>jest</category>
      <category>testing</category>
      <category>angular2</category>
      <category>angular</category>
    </item>
    <item>
      <title>An Opinionated Approach to Testing Angular</title>
      <dc:creator>Georgi Parlakov</dc:creator>
      <pubDate>Fri, 12 Jul 2019 13:26:51 +0000</pubDate>
      <link>https://dev.to/gparlakov/an-opinionated-approach-to-testing-angular-492f</link>
      <guid>https://dev.to/gparlakov/an-opinionated-approach-to-testing-angular-492f</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AbiRyJxXPSuGSXADNxgyhQw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AbiRyJxXPSuGSXADNxgyhQw.png"&gt;&lt;/a&gt;Angular plus Jest. Logos from &lt;a href="https://worldvectorlogo.com/logo/angular-icon" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;a href="https://worldvectorlogo.com/logo/angular-icon" rel="noopener noreferrer"&gt;https://worldvectorlogo.com/logo/angular-icon&lt;/a&gt; &lt;a href="https://freebiesupply.com/logos/jest-logo/" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;a href="https://freebiesupply.com/logos/jest-logo/" rel="noopener noreferrer"&gt;https://freebiesupply.com/logos/jest-logo/&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Informed by a few years of writing, reading and depending on tests.
&lt;/h4&gt;

&lt;h5&gt;
  
  
  Originally posted at &lt;a href="https://medium.com/ng-gotchas/an-opinionated-approach-to-testing-angular-4cf14ef7463f" rel="noopener noreferrer"&gt;https://medium.com/ng-gotchas/an-opinionated-approach-to-testing-angular-4cf14ef7463f&lt;/a&gt;
&lt;/h5&gt;

&lt;p&gt;First, let me lay out the &lt;strong&gt;basic points&lt;/strong&gt; and then we’ll jump into  &lt;strong&gt;details&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Disclaimer: I will not try to convince you that unit testing is a good idea. If you don’t think it is, go read something else. The opinions expressed here may seem arbitrary and just plain wrong if you are not relying on unit tests for your everyday work.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;1. No logic in the templates &lt;/strong&gt; — just call an event &lt;strong&gt;handler&lt;/strong&gt; public &lt;strong&gt;method&lt;/strong&gt;. We can &lt;strong&gt;trust Angular&lt;/strong&gt; to do its part (that is already tested — &lt;a href="https://github.com/angular/angular/blob/master/packages/common/test/directives/ng_if_spec.ts" rel="noopener noreferrer"&gt;example&lt;/a&gt;) which is to call us. And &lt;strong&gt;we&lt;/strong&gt; do our part —  &lt;strong&gt;test&lt;/strong&gt; only &lt;strong&gt;our code&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.&lt;/strong&gt; Include &lt;strong&gt;all dependencies&lt;/strong&gt; as &lt;strong&gt;injectable&lt;/strong&gt; constructor params. Document, Date.now(), requestAnimationFrame etc.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.&lt;/strong&gt; All &lt;strong&gt;properties&lt;/strong&gt; and &lt;strong&gt;methods&lt;/strong&gt; used by the template are *&lt;em&gt;public *&lt;/em&gt;. This might be obvious to you but it was not for me initially. And the &lt;a href="https://marketplace.visualstudio.com/items?itemName=Angular.ng-template" rel="noopener noreferrer"&gt;language service&lt;/a&gt; wasn’t there to tell me otherwise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4.&lt;/strong&gt; In &lt;strong&gt;each test&lt;/strong&gt; have a setup function that sets up all required state and infrastructure for the test.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5.&lt;/strong&gt; In the setup function use the &lt;strong&gt;builder pattern&lt;/strong&gt; to &lt;strong&gt;set up&lt;/strong&gt; the &lt;strong&gt;dependencies&lt;/strong&gt; and &lt;strong&gt;construct&lt;/strong&gt; the class under test.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6.&lt;/strong&gt; Use Jest (&lt;a href="https://jestjs.io/" rel="noopener noreferrer"&gt;jest.io&lt;/a&gt;) [Jest’s snapshot feature (this one is marginally useful)]&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7.&lt;/strong&gt; For button clicking and input typing i.e. what the user uses the UI for, create a few UI (aka end-to-end) tests which actually use the user interface&lt;/p&gt;

&lt;p&gt;All right — now for some details:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. No logic&lt;/strong&gt; in the &lt;strong&gt;templates &lt;/strong&gt; — just call the event handler public method — this way we can trust Angular to do its part (which is already tested &lt;a href="https://github.com/angular/angular/blob/master/packages/common/test/directives/ng_if_spec.ts" rel="noopener noreferrer"&gt;example&lt;/a&gt;). &lt;strong&gt;We test&lt;/strong&gt; only &lt;strong&gt;our code&lt;/strong&gt;. Consider the following two versions of the same template:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The top one looks like a smart and readable choice when writing the template but &lt;strong&gt;actually&lt;/strong&gt; requires for your &lt;strong&gt;test&lt;/strong&gt; to &lt;strong&gt;use&lt;/strong&gt; the &lt;strong&gt;template&lt;/strong&gt; , which needs to get compiled etc. It also requires any person reading the code to have both the my.component.html and my.component.ts open in order to follow the logic.&lt;br&gt;&lt;br&gt;
The &lt;strong&gt;bottom&lt;/strong&gt; one allows for the test to &lt;strong&gt;instantiate&lt;/strong&gt; the component class (no compiling of the template and so on) and &lt;strong&gt;simply call&lt;/strong&gt; the public &lt;strong&gt;method&lt;/strong&gt; onHereButtonClick(). The name of the handler is a &lt;strong&gt;convention&lt;/strong&gt; combining the element name and the event name — onHereButtonClick — when here button is clicked do something — the logic.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;This way the my.component.ts holds the logic and not the template and a person reading it can follow the logic that much easier.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;…trusting that the DOM and/or Angular will call my code and when I call them they will do their job…&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;1.1.&lt;/strong&gt; That allows us to only test the class without compiling the whole component with its .css and .html. Much faster, easier and no need to keep all the .ts and .html and .spec.ts open in order to write the tests&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Include all &lt;strong&gt;dependencies&lt;/strong&gt; as &lt;strong&gt;injectable&lt;/strong&gt; constructor params. Document, Date.now(), requestAnimationFrame etc.
Any direct calls to browser APIs like Date.now() or requestAnimationFrame or setTimeout become hard to test. But, if we wrap those in services the tests become trivial.&lt;/li&gt;
&lt;/ol&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The &lt;strong&gt;document&lt;/strong&gt; is already provided by the framework: &lt;a href="https://github.com/angular/angular/blob/2b44be984e0932bab83fcea015fc5b4ee2d3b151/packages/common/src/dom_tokens.ts#L11-L19" rel="noopener noreferrer"&gt;https://github.com/angular/angular/blob/2b44be984e0932bab83fcea015fc5b4ee2d3b151/packages/common/src/dom_tokens.ts#L11-L19&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.1.&lt;/strong&gt; Use the AAA — &lt;a href="https://medium.com/@pjbgf/title-testing-code-ocd-and-the-aaa-pattern-df453975ab80" rel="noopener noreferrer"&gt;arrange act assert&lt;/a&gt;pattern/convention. It allows for the person reading the code to &lt;strong&gt;discern what&lt;/strong&gt; happens &lt;strong&gt;where&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
Any similar convention —ex When Then Should — would do the trick as long as the whole team is on board of course.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;All &lt;strong&gt;properties&lt;/strong&gt; and &lt;strong&gt;methods&lt;/strong&gt; used by the template are &lt;strong&gt;public&lt;/strong&gt;  . This might be &lt;strong&gt;obvious&lt;/strong&gt; to you but it was not for me initially. And the &lt;a href="https://marketplace.visualstudio.com/items?itemName=Angular.ng-template" rel="noopener noreferrer"&gt;language service&lt;/a&gt; wasn’t there to tell me otherwise. Since the method needs to be used by the template, which is ‘external’ to the class, it needs to be public. That’s how I think about it. Also in the test that allows us to decide what we need to test — any public method.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In each test have a setup function that sets up all required state and infrastructure for the test.&lt;br&gt;&lt;br&gt;
Instead of relying on the TestBed we shift its responsibilities to a setup function. We call that function in each test (or in beforeEach hook) and it returns to us an object that allows for specifying what is relevant to this test and constructing the class-under-test. In case of changes in the dependencies or the public API of the class (due to everchanging requirements), we’ll be able to update the test in a single place — the setup function.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;4.1&lt;/strong&gt; Use autoSpy. That’s a function that can create an object with the methods of the class &lt;strong&gt;mocked automatically&lt;/strong&gt;.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;It allows the returned object to get used like:expect(o.method).hasBeenCalledWith('arg1'); and o.method.mockReturnValue(10).&lt;/p&gt;

&lt;p&gt;This is a simple implementation. If a service needs to return a complex object you need to take the mock and add that behavior. See the example in the next section (5.) withTempUser method on the builder object.&lt;/p&gt;

&lt;p&gt;A similar idea is implemented in the &lt;a href="https://github.com/hirezio/jasmine-auto-spies" rel="noopener noreferrer"&gt;jasmine-auto-spies&lt;/a&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In the setup function use the &lt;strong&gt;builder pattern&lt;/strong&gt; to &lt;strong&gt;set up&lt;/strong&gt; the &lt;strong&gt;dependencies&lt;/strong&gt; and &lt;strong&gt;construct&lt;/strong&gt; the class under test. The builder pattern allows for dot-chain declaring of each particular test’s needs.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the following example, we need to have a temporary user on our site which only stores their email in their localStorage . Only tested one of the methods for conciseness.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;In the builder we have the withTempUser method that allows us to specify that there is a temp user with a certain email. That takes the messy mock code and:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;moves&lt;/strong&gt; it in one place, out of the test for improved test &lt;strong&gt;readability&lt;/strong&gt; and &lt;strong&gt;maintainability&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;preserves&lt;/strong&gt; the &lt;strong&gt;intention&lt;/strong&gt; for the person reading so they can see what we wanted to have happen, instead of trying to guess by reading the implementation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For each &lt;strong&gt;business&lt;/strong&gt; logic &lt;strong&gt;case&lt;/strong&gt; , we can add a similar &lt;strong&gt;method&lt;/strong&gt; that does a piece of dependency or state &lt;strong&gt;set up&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5.1.&lt;/strong&gt; That (the setup function implementing the builder pattern) also allows for some automation. Basically, a tool that can read the class-under-test and create a test for it following these guidelines. And update that test when the class-under-test changes. See &lt;a href="https://www.npmjs.com/package/scuri" rel="noopener noreferrer"&gt;SCURI&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Use Jest&lt;/strong&gt;. It has (almost) &lt;strong&gt;identical API&lt;/strong&gt; to &lt;strong&gt;Jasmine&lt;/strong&gt; so migrating existing &lt;strong&gt;tests&lt;/strong&gt; and testing &lt;strong&gt;skills&lt;/strong&gt; is trivial. Jest has a bit &lt;strong&gt;better errors test&lt;/strong&gt; than jasmine, like showing you exactly &lt;strong&gt;what&lt;/strong&gt; is the &lt;strong&gt;difference&lt;/strong&gt; between the expected and the actual value in a failed test. It does not &lt;strong&gt;depend&lt;/strong&gt; on starting a &lt;strong&gt;server&lt;/strong&gt; and running a &lt;strong&gt;browser.&lt;/strong&gt; Instead, it uses the &lt;a href="https://github.com/jsdom/jsdom" rel="noopener noreferrer"&gt;JsDOM&lt;/a&gt;library and everything runs in a node.js process. It &lt;strong&gt;parallelizes&lt;/strong&gt; the tests using all available CPU cores. It supports &lt;a href="https://jestjs.io/docs/en/mock-functions.html#mocking-modules" rel="noopener noreferrer"&gt;mocking js modules&lt;/a&gt; so that your tests see the mocked ../my-service.js instead of the actual ../my-service.js Don’t rely on this for all unit tests, but this feature saved our butts on a few occasions.&lt;br&gt;&lt;br&gt;
Finally — for our use case at &lt;a href="https://propy.com" rel="noopener noreferrer"&gt;propy.com&lt;/a&gt;— we could not start Chrome on our build machine and that was the main turning point to switch to our Angular unit tests using Jest.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Switching&lt;/strong&gt; to Jest is even simpler when using this &lt;a href="https://github.com/briebug/jest-schematic" rel="noopener noreferrer"&gt;Jest Schematic&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
Finally, Jest has a nice console UI when running in jest --watch mode. Lets you filter test by file name, test name, status failed or just changed i.e. understands git changes and runs only the currently changed files — which makes a lot of sense.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2APwUQseev7g-5gWFY.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2APwUQseev7g-5gWFY.png"&gt;&lt;/a&gt;An error report from Jest. From the jestjs.io site.&lt;/p&gt;

&lt;p&gt;Using Jest’s &lt;strong&gt;snapshot&lt;/strong&gt; feature proves to be &lt;strong&gt;marginally&lt;/strong&gt; useful and a &lt;strong&gt;major pain&lt;/strong&gt; to maintain. For the 6 months we used it we had maybe 1 or 2 instances that it may be caught something. That is — during a code review, I noticed that after changing something in a component a whole section disappeared from its rendered HTML (which is what we did a snapshot on). I am now not doing snapshots on the new components I write.&lt;/p&gt;

&lt;p&gt;See this great article for a stronger opinion on that &lt;a href="https://medium.com/@tomgold_48918/why-i-stopped-using-snapshot-testing-with-jest-3279fe41ffb2" rel="noopener noreferrer"&gt;https://medium.com/@tomgold_48918/why-i-stopped-using-snapshot-testing-with-jest-3279fe41ffb2&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;For &lt;strong&gt;testing&lt;/strong&gt; the actual &lt;strong&gt;UI&lt;/strong&gt; , like button clicking and input typing (what the user experiences) create UI (aka end-to-end) tests that actually exercise the UI as a user would. I use &lt;strong&gt;Puppeteer&lt;/strong&gt; ( &lt;a href="https://pptr.dev/" rel="noopener noreferrer"&gt;https://pptr.dev/&lt;/a&gt;) because it’s developer friendly, but have also used Protractor ( &lt;a href="https://www.protractortest.org/#/" rel="noopener noreferrer"&gt;https://www.protractortest.org/#/&lt;/a&gt;) and heard good things about &lt;strong&gt;Cypress&lt;/strong&gt; ( &lt;a href="https://www.cypress.io/" rel="noopener noreferrer"&gt;https://www.cypress.io/&lt;/a&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Bonus thoughts:&lt;br&gt;&lt;br&gt;
Unit tests that do &lt;strong&gt;button clicking&lt;/strong&gt; and &lt;strong&gt;event dispatching&lt;/strong&gt; I find expensive to write and very &lt;strong&gt;low&lt;/strong&gt; return on investment ( &lt;strong&gt;ROI&lt;/strong&gt; ). I tend to not write any of those. Instead, I control the instantiation of the class-under-test (component, directive, service) via the setup builder then invoke the public methods and examine the results, &lt;strong&gt;trusting&lt;/strong&gt; that the &lt;strong&gt;DOM&lt;/strong&gt; and/or &lt;strong&gt;Angular&lt;/strong&gt; will call my code what I declared I wanted it called and when I call them— they will do their job.&lt;/p&gt;

&lt;p&gt;They are expensive to write because I need to uncover the underlying DOM calls, usually hidden by Angular (e.g. dispatchEvent, addClass, focus) all the while Angular actually provides that functionality and has tested it thoroughly.&lt;/p&gt;

&lt;p&gt;Hey, thanks for reading and happy to discuss — how do you do your testing?&lt;/p&gt;




</description>
      <category>angular</category>
      <category>javascript</category>
      <category>testing</category>
      <category>jest</category>
    </item>
  </channel>
</rss>
