Skip to content

A Guide to (Typed) Reactive Forms in Angular - Part II (Building Dynamic Superforms)

In the first blog post of the series, we learned about Angular reactive forms and the data structures behind them. When developing real-world applications, however, you often need to leverage dynamic forms, as writing boilerplate for every form and its specific cases can be tedious and time-consuming. In certain situations, it may even be necessary to retrieve information from an API to construct the forms.

In this post, we will go over a convenient abstraction we can create to build dynamic and adaptable forms without repeating boilerplate. The trick is to create a "view model" for our data and use a service to transform that data into a reactive form. I was first introduced to this approach by my friend and former teammate Thomas Duft, and I've been using it ever since.

The approach outlined in the linked article worked great with untyped forms, but since now we can get our forms strictly typed, we'll want to upgrade it.

And here is where it gets a bit tricky. Remember how I mentioned you shouldn't predeclare your form groups earlier? If you want to recursively create a form from a config, you just have to. And it's a dynamic form, so you cannot easily type it. To solve the issue, I devised a trick inspired by a "Super Form" suggested by Bobby Galli. Assuming we will have interfaces defined for our data, using this approach, we can create dynamic type-safe forms.

First, we'll create types for our form config:

// this will be our ViewModel for configuring a FormGroup
export class FormSection<
  T extends {
    [K in keyof T]:
      | FormSection<any>
      | FormField<any>
      | (FormSection<any> | FormField<any>)[];
  } = any
> {
  public title?: string;
  public fields: T;

  constructor(section: {
    title?: string;
    fields: T;
  }) {
    this.title = section.title;
    this.fields = section.fields;
  }
}

// Let's define some editor types we'll be using in the templates later
export type FormEditor =
  | 'textInput'
  | 'passwordInput'
  | 'textarea'
  | 'checkbox'
  | 'select';

// And this will be a ViewModel for our FormControls
export class FormField<T> {
  public value: T;
  public editor: FormEditor;
  public validators: Validators;
  public label: string;
  public required: boolean;
  public options?: T[];

  constructor(field: {
    value: T;
    editor: FormEditor;
    validators: Validators;
    label: string;
    required: boolean;
    options?: T[];
  }) {
    this.value = field.value;
    this.editor = field.editor;
    this.validators = field.validators;
    this.label = field.label;
    this.required = field.required;
    this.options = field.options;
  }
}

And then we'll create some type mappings:

// We will use this type mapping to properly declare our form group
export type ControlsOf<T extends Record<string, any>> = {
  [K in keyof T]: T[K] extends Array<any>
    ? FormArray<AbstractControl<T[K][0]>>
    : T[K] extends Record<any, any>
    ? FormGroup<ControlsOf<T[K]>>
    : FormControl<T[K] | null>;
};

// We will use this type mapping to type our form config
export type ConfigOf<T> = {
  [K in keyof T]: T[K] extends (infer U)[]
    ? U extends Record<any, any>
      ? FormSection<ConfigOf<U>>[]
      : FormField<U>[]
    : T[K] extends Record<any, any>
    ? FormSection<ConfigOf<T[K]>>
    : FormField<T[K]>;
};

And now we can use our types in a service that will take care of creating nested dynamic forms:

import { Injectable } from '@angular/core';
import {
  AbstractControl,
  FormArray,
  FormControl,
  FormGroup,
} from '@angular/forms';
import { ConfigOf, ControlsOf, FormField, FormSection } from './forms.model';

@Injectable({
  providedIn: 'root',
})
export class FormsService {
  public createFormGroup<T extends Record<string, any>>(
    section: FormSection<ConfigOf<T>>
  ): FormGroup<ControlsOf<T>> {
    // we need to create an empty FormGroup first, so we can add FormControls recursively
    const group = new FormGroup({});

    Object.keys(section.fields).forEach((key: any) => {
      const field = section.fields[key];
      if (Array.isArray(field)) {
        group.addControl(key, this.createFormArray(field));
      } else {
        if (field instanceof FormSection) {
          group.addControl(key, this.createFormGroup(field));
        } else {
          group.addControl(key, new FormControl(field.value, field.validators));
        }
      }
    });

    // and we need to cast the group to the correct type before returning
    return group as unknown as FormGroup<ControlsOf<T>>;
  }

  public createFormArray<T extends Record<string, any>>(
    fields: unknown[]
  ): FormArray<AbstractControl<T>> {
    const array: FormArray<AbstractControl<any>> = new FormArray(
      []
    ) as unknown as FormArray<AbstractControl<T>>;

    fields.forEach((field) => {
      if (field instanceof FormSection) {
        array.push(this.createFormGroup(field));
      } else {
        const { value, validators } = field as FormField<T>;
        array.push(new FormControl(value, validators));
      }
    });

    return array as unknown as FormArray<AbstractControl<T>>;
  }
}

And that's it. Now we can use our FormService to create forms. Let's say we have the following User model:

export type User = {
  email: string;
  name: string;
}

We can create a form for this user from config in the following way:

  const userFormConfig = new FormSection<ConfigOf<User>>({
      title: 'User Form',
      fields: {
        email: new FormField<string>({
          value: '',
          validators: [Validators.required, Validators.email],
          label: 'Email',
          editor: 'textInput',
          required: true,
        }),
        name: new FormField<string>({
          value: '',
          validators: [Validators.required],
          label: 'Name',
          editor: 'textInput',
          required: true,
        })
      }
  });

  const userForm = this.formsService.createFormGroup<User>(userFormConfig);

If we would check the type of userForm.value now, we would see that it's correctly inferred as:

Partial<{
    email: string | null;
    name: string | null;
}>

Outputting the Dynamic Forms

To display the dynamic forms, we can write a simple component that takes the FormSection or FormField as an Input() along with our FormGroup and displays the form recursively.

We can use a setter to assign either field or section property when the view model is passed into the component, so we can conveniently use them in our template. Our form component's TypeScript code will look something like this:

import { Component, Input } from '@angular/core';
import { FormField, FormSection } from '../forms.model';
import { FormArray, FormGroup } from '@angular/forms';

@Component({
  selector: 'app-form',
  templateUrl: './form.component.html',
  styleUrls: ['./form.component.scss'],
})
export class FormComponent {
  private fieldConfig?: FormField<any>;
  private sectionConfig?: FormSection<any>;
  private arrayConfig?: (FormSection<any> | FormField<any>)[];
  private sectionFieldsArray?: [string, FormField<any>][];

  @Input()
  public set config(
    config:
      | FormField<any>
      | FormSection<any>
      | (FormSection<any> | FormField<any>)[]
  ) {
    this.fieldConfig = config instanceof FormField ? config : undefined;
    this.arrayConfig = Array.isArray(config) ? config : undefined;
    this.sectionConfig = config instanceof FormSection ? config : undefined;

    this.sectionFieldsArray = Object.entries(this.sectionConfig?.fields || {});
  }

  public get sectionFields(): [string, FormField<any>][] {
    return this.sectionFieldsArray || [];
  }

  public get field(): FormField<any> | undefined {
    return this.fieldConfig;
  }

  public get section(): FormSection<any> | undefined {
    return this.sectionConfig;
  }

  public get array(): (FormSection<any> | FormField<any>)[] | undefined {
    return this.arrayConfig;
  }

  ngAfterViewInit() {
    console.log(this.arrayConfig);
  }

  @Input()
  public key!: string;

  @Input()
  public group!: FormGroup;

  public get sectionGroup(): FormGroup {
    return this.group.get(this.key) as FormGroup;
  }

  public get formArray(): FormArray {
    return this.group.get(this.key) as FormArray;
  }
}

And our template will reference a new form component for each section field in case we have passed in a FormSection and it will have a switch case to display the correct control in case a FormField has been passed in:

<ng-container *ngIf="field">
  <label>{{ field.label }}</label>
  <div [ngSwitch]="field.editor" [formGroup]="group">
    <textarea *ngSwitchCase="'textarea'" [formControlName]="key"></textarea>
    <input
      *ngSwitchCase="'passwordInput'"
      [formControlName]="key"
      type="input"
    />
    <input *ngSwitchCase="'checkbox'" [formControlName]="key" type="checkbox" />
    <input *ngSwitchDefault [formControlName]="key" type="text" />
  </div>
</ng-container>

<ng-container *ngIf="section">
  <div>
    <h3 *ngIf="section?.title">{{ section.title }}</h3>
    <app-form
      *ngFor="let sectionField of sectionFields"
      [config]="sectionField[1]"
      [key]="sectionField[0]"
      [group]="key ? sectionGroup : group"
    ></app-form>
  </div>
</ng-container>

<ng-container *ngIf="array">
  <div [formGroup]="group">
    <div [formArrayName]="key">
      <div *ngFor="let item of array; let i = index">
        <app-form
          [config]="item"
          [key]="i.toString()"
          [group]="sectionGroup"
        ></app-form>
      </div>
    </div>
  </div>
</ng-container>

That way, we can display the whole form just by referencing one component, such as

 <app-form [config]="formViewModel" [group]="form"></app-form>

Check out an example on StackBlitz.

In the next (and last) post of the series, we will learn about building custom Form Controls.

This Dot Labs is a development consultancy that is trusted by top industry companies, including Stripe, Xero, Wikimedia, Docusign, and Twilio. This Dot takes a hands-on approach by providing tailored development strategies to help you approach your most pressing challenges with clarity and confidence. Whether it's bridging the gap between business and technology or modernizing legacy systems, you’ll find a breadth of experience and knowledge you need. Check out how This Dot Labs can empower your tech journey.

You might also like

A Guide to (Typed) Reactive Forms in Angular - Part III (Creating Custom Form Controls) cover image

A Guide to (Typed) Reactive Forms in Angular - Part III (Creating Custom Form Controls)

So far in the series, we have learned the basics of Angular Reactive forms and created some neat logic to construct and display dynamic forms. But our work is still not done yet. Whether we just want to make our controls look good and enhance them with some markup, or whether we need a more complex control than a simple textarea, input or checkbox, we'll either need to use a component library such as Angular Material Components or get familiar with the ControlValueAccessor` interface. Angular Material, by the way, uses ControlValueAccessor` in its components and I recommend looking into the source code if you want to learn some advanced use cases (I have borrowed a lot of their ideas in the past). In this post, however, we will build a basic custom control from scratch. A common requirement for a component that cannot be satisfied by using standard HTML markup I came across in many projects is having a searchable combobox**. So let's build one. We will start by creating a new Angular component and we can do that with a handy ng cli command: ` ng generate component form-fields/combobox ` Then we'll implement displaying data passed in the form of our FormField` class we have defined earlier in a list and allowing for filtering and selecting the options: `TypeScript // combobox.component.ts import { Component, ElementRef, Input, ViewChild } from '@angular/core'; import { FormField } from '../../forms.model'; @Component({ selector: 'app-combobox', templateUrl: './combobox.component.html', styleUrls: ['./combobox.component.scss'], }) export class ComboboxComponent { private filteredOptions?: (string | number)[]; // a simple way to generate a "unique" id for each component // in production, you should rather use a library like uuid public id = String(Date.now() + Math.random()); @ViewChild('input') public input?: ElementRef; public selectedOption = ''; public listboxOpen = false; @Input() public formFieldConfig!: FormField; public get options(): (string | number)[] { return this.filteredOptions || this.formFieldConfig.options || []; } public get label(): string { return this.formFieldConfig.label; } public toggleListbox(): void { this.listboxOpen = !this.listboxOpen; if (this.listboxOpen) { this.input?.nativeElement.focus(); } } public closeListbox(event: FocusEvent): void { // timeout is needed to prevent the list box from closing when clicking on an option setTimeout(() => { this.listboxOpen = false; }, 150); } public filterOptions(filter: string): void { this.filteredOptions = this.formFieldConfig.options?.filter((option) => { return option.toString().toLowerCase().includes(filter.toLowerCase()); }); } public selectOption(option: string | number): void { this.selectedOption = option.toString(); this.listboxOpen = false; } } ` `HTML {{ label }} &#9660; {{ option }} ` > Note: For the sake of brevity, we will not be implementing keyboard navigation and aria labels. I strongly suggest referring to W3C WAI patterns to get guidelines on the markup and behavior of an accessible combo box. While our component now looks and behaves like a combo box, it's not a form control yet and is not connected with the Angular forms API. That's where the aforementioned ControlValueAccessor` comes into play along with the `NG_VALUE_ACCESSOR` provider. Let's import them first, update the `@Component` decorator to provide the value accessor, and declare that our component is going to implement the interface: `TypeScript import { ControlValueAccessor, NGVALUE_ACCESSOR } from '@angular/forms'; @Component({ selector: 'app-combobox', templateUrl: './combobox.component.html', styleUrls: ['./combobox.component.scss'], providers: [ { // provide the value accessor provide: NGVALUE_ACCESSOR, // for our combobox component useExisting: ComboboxComponent, // and we don't want to override previously provided value accessors // we want to provide an additional one under the same "NGVALUE_ACCESSOR" token instead multi: true, }, ], }) export class ComboboxComponent implements ControlValueAccessor { ` Now, the component should complain about a few missing methods that we need to satisfy the ControlValueAccessor` interface: - A writeValue` method that is called whenever the form control value is updated from the forms API (e.g. with `patchValue()`). - A registerOnChange` method, which registers a callback function for when the value is changed from the UI. - A registerOnTouched` method that registers a callback function that marks the control when it's been interacted with by the user (typically called in a `blur` handler). - An optional setDisabledState` method that is called when we change the form control `disabled` state- Our (pretty standard) implementation will look like the following: `TypeScript private onChanged!: Function; private onTouched!: Function; public disabled = false; // This will write the value to the view if the form control is updated from outside. public writeValue(value: any) { this.value = value; } // Register a callback function that is called when the control's value changes in the UI. public registerOnChange(onChanged: Function) { this.onChanged = onChanged; } // Register a callback function that is called by the forms API on initialization to update the form model on blur. public registerOnTouched(onTouched: Function) { this.onTouched = onTouched; } public setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; } public setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; } ` We don't have to update the template a lot, but we can add [disabled]="disabled"` attribute on our button and input to disable the interactive UI elements if the provided form control was disabled. The rest of the work can be done in the component's TypeScript code. We'll call `this.onTouched()` in our `closeListbox` method, and create a `value` setter that updates our internal value and also notifies the model about the value change: `TypeScript public set value(val: string | number) { this.selectedOption = val.toString(); this.onChanged && this.onChanged(this.selectedOption); this.onTouched && this.onTouched(); } ` You can check out the full implementation on StackBlitz. Conclusion In this series, we've explored the powerful features of Angular reactive forms, including creating and managing dynamic typed forms. We also demonstrated how to use the ControlValueAccessor interface to create custom form controls, such as a searchable combo box. This knowledge will enable you to design complex and dynamic forms in your Angular applications. While the examples provided here are basic, they serve as a solid foundation for building more advanced form controls and implementing validation, accessibility, and other features that are essential for a seamless user experience. By mastering Angular reactive forms and custom form controls, you'll be able to create versatile and maintainable forms in your web applications. If you want to further explore the topic and prefer a form of a video, you can check out an episode of JavaScript Marathon by my amazing colleague Chris. Happy coding!...

Angular 17: Continuing the Renaissance cover image

Angular 17: Continuing the Renaissance

Angular 17: A New Era November 8th marked a significant milestone in the world of Angular with the release of Angular 17. This wasn't just any ordinary update; it was a leap forward, signifying a new chapter for the popular framework. But what made this release truly stand out was the unveiling of Angular's revamped website, complete with a fresh brand identity and a new logo. This significant transformation represents the evolving nature of Angular, aligning with the modern demands of web development. To commemorate this launch, we also hosted a release afterparty, where we went deep into its new features with Minko Gechev from the Angular core team, and Google Developer Experts (GDEs) Brandon Roberts, Deborah Kurata, and Enea Jahollari. But what exactly are these notable new features in the latest version? Let's dive in and explore. The Angular Renaissance Angular has been undergoing a significant revival, often referred to as Angular's renaissance, a term coined by Sarah Drasner, the Director of Engineering at Google, earlier this year. This revival has been particularly evident in its recent versions. The Angular team has worked hard to introduce many new improvements, focusing on signal-based reactivity, hydration, server-side rendering, standalone components, and migrating to esbuild and Vite for a better and faster developer experience. This latest release, in particular, marks many of these features as production-ready. Standalone Components About a year ago, Angular began a journey toward modernity with the introduction of standalone components. This move significantly enhanced the developer experience, making Angular more contemporary and user-friendly. In Angular's context, a standalone component is a self-sufficient, reusable code unit that combines logic, data, and user interface elements. What sets these components apart is their independence from Angular's NgModule system, meaning they do not rely on it for configuration or dependencies. By setting a standalone: true` flag, you no longer need to embed your component in an NgModule and you can bootstrap directly off that component: `typescript // ./app/app.component.ts @Component({ selector: 'app', template: 'hello', standalone: true }) export class AppComponent {} // ./main.ts import { bootstrapApplication } from '@angular/platform-browser'; import { AppComponent } from './app/app.component'; bootstrapApplication(AppComponent).catch(e => console.error(e)); ` Compared to the NgModules way of adding components, as shown below, you can immediately see how standalone components make things much simpler. `ts // ./app/app.component.ts import { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], }) export class AppComponent { title = 'CodeSandbox'; } // ./app/app.module.ts import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { AppComponent } from './app.component'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { } // .main.ts import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; platformBrowserDynamic() .bootstrapModule(AppModule) .catch((err) => console.error(err)); ` In this latest release, the Angular CLI now defaults to generating standalone components, directives, and pipes. This default setting underscores the shift towards a standalone-centric development approach in Angular. New Syntax for Enhanced Control Flow Angular 17 introduces a new syntax for control flow, replacing traditional structural directives like ngIf` or `ngFor`, which have been part of Angular since version 2. This new syntax is designed for fine-grained change detection and eventual zone-less operation when Angular completely migrates to signals. It's more streamlined and performance-efficient, making handling conditional or list content in templates easier. The @if` block replaces `*ngIf` for expressing conditional parts of the UI. `ts @if (a > b) { {{a}} is greater than {{b}} } @else if (b > a) { {{a}} is less than {{b}} } @else { {{a}} is equal to {{b}} } ` The @switch` block replaces `ngSwitch`, offering benefits such as not requiring a container element to hold the condition expression or each conditional template. It also supports template type-checking, including type narrowing within each branch. ```ts @switch (condition) { @case (caseA) { Case A. } @case (caseB) { Case B. } @default { Default case. } } ``` The @for` block replaces `*ngFor` for iteration and presents several differences compared to its structural directive predecessor, `ngFor`. For example, the tracking expression (calculating keys corresponding to object identities) is mandatory but offers better ergonomics. Additionally, it supports `@empty` blocks. `ts @for (item of items; track item.id) { {{ item.name }} } ` Defer Block for Lazy Loading Angular 17 introduces the @defer` block, a dramatically improving lazy loading of content within Angular applications. Within the `@defer` block framework, several sub-blocks are designed to elegantly manage different phases of the deferred loading process. The main content within the `@defer` block is the segment designated for lazy loading. Initially, this content is not rendered, becoming visible only when specific triggers are activated or conditions are met, and after the required dependencies have been loaded. By default, the trigger for a `@defer` block is the browser reaching an idle state. For instance, take the following block: it delays the loading of the calendar-imp` component until it comes into the viewport. Until that happens, a placeholder is shown. This placeholder displays a loading message when the `calendar-imp` component begins to load, and an error message if, for some reason, the component fails to load. `ts @defer (on viewport) { } @placeholder { Calendar placeholder } @loading { Loading calendar } @error { Error loading calendar } ` The on` keyword supports a wide a variety of other conditions, such as: - idle` (when the browser has reached an idle state) - interaction` (when the user interacts with a specified element) - hover` (when the mouse has hovered over a trigger area) - timer(x)` (triggers after a specified duration) - immediate` (triggers the deferred load immediately) The second option of configuring when deferring happens is by using the when` keyword. For example: `ts @defer (when isVisible) { } ` Server-Side Rendering (SSR) Angular 17 has made server-side rendering (SSR) much more straightforward. Now, a --ssr` option is included in the `ng new` command, removing the need for additional setup or configurations. When creating a new project with the `ng new` command, the CLI inquires if SSR should be enabled. As of version 17, the default response is set to 'No'. However, for version 18 and beyond, the plan is to enable SSR by default in newly generated applications. If you prefer to start with SSR right away, you can do so by initializing your project with the `--ssr` flag: `shell ng new --ssr ` For adding SSR to an already existing project, utilize the ng add` command of the Angular CLI: `shell ng add @angular/ssr ` Hydration In Angular 17, the process of hydration, which is essential for reviving a server-side rendered application on the client-side, has reached a stable, production-ready status. Hydration involves reusing the DOM structures rendered on the server, preserving the application's state, and transferring data retrieved from the server, among other crucial tasks. This functionality is automatically activated when server-side rendering (SSR) is used. It offers a more efficient approach than the previous method, where the server-rendered tree was completely replaced, often causing visible UI flickers. Such re-rendering can adversely affect Core Web Vitals, including Largest Contentful Paint (LCP), leading to layout shifts. By enabling hydration, Angular 17 allows for the reuse of the existing DOM, effectively preventing these flickers. Support for View Transitions The new View Transitions API, supported by some browsers, is now integrated into the Angular router. This feature, which must be activated using the withViewTransitions` function, allows for CSS-based animations during route transitions, adding a layer of visual appeal to applications. To use it, first you need to import withViewTransitions`: `ts import { provideRouter, withViewTransitions } from '@angular/router'; ` Then, you need to add it to the provideRouter` configuration: `ts bootstrapApplication(AppComponent, { providers: [ provideRouter(routes, withViewTransitions()) ] }) ` Other Notable Changes - Angular 17 has stabilized signals, initially introduced in Angular 16, providing a new method for state management in Angular apps. - Angular 17 no longer supports Node 16. The minimal Node version required is now 18.13. - TypeScript version 5.2 is the least supported version starting from this release of Angular. - The @Component` decorator now supports a `styleUrl` attribute. This allows for specifying a single stylesheet path as a string, simplifying the process of linking a component to a specific style sheet. Previously, even for a single stylesheet, an array was required under `styleUrls`. Conclusion With the launch of Angular 17, the Angular Renaissance is now in full swing. This release has garnered such positive feedback that developers are showing renewed interest in the framework and are looking forward to leveraging it in upcoming projects. However, it's important to note that it might take some time for IDEs to adapt to the new templating syntax fully. While this transition is underway, rest assured that you can still write perfectly valid code using the old templating syntax, as all the changes in Angular 17 are backward compatible. Looking ahead, the future of Angular appears brighter than ever, and we can't wait to see what the next release has in store!...

Unit Testing Qwik Components cover image

Unit Testing Qwik Components

Unit Testing Qwik Components Qwik is a new, superfast JavaScript framework from builder.io. Created by Miško Hevery, the author of AngularJS, Qwik aims to deliver instant loading web applications of any size or complexity through resumability. This is accomplished partially by delaying the execution and download of JavaScript for as long as possible while providing an excellent developer experience. > "You know React? You know Qwik." > — qwik.builder.io Given Miško's background as the creator of AngularJS, it's interesting that writing Qwik feels very similar to writing React. It even has a React compatibility mode. Personally, I have experience using mostly Angular, but I found that developing with Qwik was very smooth and enjoyable. So, although I am definitely hesitant to jump on new technologies, after attending Miško's workshop, browsing through all the resources at framework.dev tinkering with Qwik, and building a couple of apps with it, I became a fan. However, being a responsible software developer, I then wanted to add unit tests to my Qwik app. And because Qwik is relatively new, I found out there was little documentation on how to do this. Furthermore, until recently, there were no tools to easily set up and interact with Qwik components in unit tests. But that didn't stop me. I explored, asked, and waited for a PR to be merged, and now I am proud to present a comprehensive guide to unit testing Qwik apps. So, without further ado, let's dive into setting up our testing environment. Configuring a Testing Environment There are several options when it comes to choosing a testing framework, and I have even experimented with using Jest, as it is the popular choice for unit testing. However, since Qwik uses Vite, I found that using Vitest was the most straightforward and efficient option. Vitest is specifically designed to be the go-to test runner for Vite projects, which makes it a great fit. If you are familiar with Jest, you will be happy to know that the Vitest API is very similar, so you won't have to learn much to use it effectively. Before moving further to the actual setup, let's make sure you have a Qwik project to test. If you don't yet, you can simply create one by running npm create qwik@latest`. Now, let's set up vitest in our Qwik project. It takes 3 easy steps: 1. Install Vitest as a dev dependency by running npm i vitest --save-dev` from the root of your project. This will update the __devDependencies__ array in your __package.json__ file. 2. Update your vite.config.ts__ file to include a test configuration in the `defineConfig` function. You can start with an empty object for now, but you can always add configuration options later on. Your __vite.config.ts__ should look something like this: ` JavaScript // ./vite.config.ts import { defineConfig } from 'vite'; import { qwikVite } from '@builder.io/qwik/optimizer'; import { qwikCity } from '@builder.io/qwik-city/vite'; import tsconfigPaths from 'vite-tsconfig-paths'; export default defineConfig(() => { return { plugins: [qwikCity(), qwikVite(), tsconfigPaths()], preview: { headers: { 'Cache-Control': 'public, max-age=600', }, }, test: {}, // this is the config entry we are adding }; }); ` 3. Update the "scripts" section of your package.json__ file to include commands for running vitest once, running it in watch mode, and for generating coverage reports. Your "scripts" section should look something like this: ` JSON "scripts": { ... "test": "vitest --run", "test.watch": "vitest", "coverage": "vitest run --coverage" } ` After completing these steps, you should be able to run npm run test` and `npm run coverage` from your terminal to use Vitest. Note that if you run npm run test` and you don't have any tests yet, which is likely the case at this point, you will see the message `No test files found, exiting with code 1`. Don't worry. We are about to add some tests shortly. Writing Tests By default, vitest will look for files with names that match the pattern '**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}`. In a Qwik project that uses TypeScript and returns JSX elements, it makes sense to use the `*.spec.tsx` or `*.test.tsx` pattern. I prefer to use the`.spec` suffix. But `.test` works just as well, and is equally valid. Feel free to choose the one that you like best. To proceed with writing a unit test, we will need a component to test. For this example, we will use a simple counter component that has a div` that displays a number and a `button` that increments it. To create the component, follow these steps: 1. Go to the src/components__ directory, and create a new folder called __counter__. 2. Inside the counter__ folder, create a new file called __counter.tsx__ and add the following code: ` TypeScript import { component$, useStore } from "@builder.io/qwik"; export const Counter = component$(() => { const state = useStore({ count: 0, }); return ( state.count++}> + {state.count} ); }); ` 3. Next to the counter.tsx__ file, create a new file called __counter.spec.tsx__ file next to it. This file will contain the unit tests for the Counter component. Now that we have our component and our test file, we can proceed to write our unit tests. Let's open counter.spec.tsx__ and add some boilerplate along with a simple test to verify we have everything set up correctly: ` TypeScript import { describe, expect, it } from "vitest"; import { Counter } from "./counter"; describe("Counter component", function () { it('should assert true', async () => { // this should always pass expect(true).toBe(true); }); }); ` We have a describe` method that groups related tests together (in our case, we're grouping all the tests we will write for our Counter component), an `expect` method which is used to make assertions about the values returned by the code being tested, and an `it` method that defines an individual test. If we now run npm run test`, we should see the following output: ` Test Files 1 passed (1) Tests 1 passed (1) ` Writing a Simple Test Case Now that we verified that our dummy test work, let's actually test our component's behavior. First, we want to test that it renders correctly, so we will remove our dummy test and leverage the new createDOM method from @builder.io/qwik.testing` to render our Counter component inside the test: ` TypeScript import { createDOM } from "@builder.io/qwik/testing"; // import the createDOM method import { describe, expect, it } from "vitest"; import { Counter } from "./counter"; describe("Counter component", function () { it("should render", async () => { // create the component's DOM and get back the container and a render method const { screen, render } = await createDOM(); // call the render method with the JSX node of our Counter component as a parameter await render(); // get the div that displays the count from our container const countElement = screen.querySelector(".count"); // assert the displayed count is "0" which is the default value expect(countElement?.textContent).toBe("0"); }); }); ` Rerunning the test should result in a pass if we did everything correctly. Now we know our component does render and displays zero as the count. Testing Interactions "Great," you say, "but I want to test the component's logic, not just the initial render!". Well, you're in luck because the createDOM` method returns one more property - `userEvent` - that you can use to interact with the component's DOM. This allows us to simulate user interactions, such as clicking a button, and test whether the component responds correctly. For example, we can test whether our counter correctly increments the count by clicking the button. Here is the updated code example: ` TypeScript it("should increment on click", async () => { // we retrieve the userEvent` method along with `screen` and `render` const { screen, render, userEvent } = await createDOM(); // render the component await render(); // get the div that displays the count from our container const countElement = screen.querySelector(".count"); // assert the displayed count is "0" which is the default value expect(countElement?.textContent).toBe("0"); // pass a selector that matches the increment button as a first parameter // and the name of the event we want to trigger ("click") as the second parameter await userEvent("button.increment", "click"); // assert the displayed count is now incremented from 0 to 1 expect(countElement?.textContent).toBe("1"); }); ` With this additional test, we can be confident that our Counter component not only renders correctly, but also updates the count as expected when the increment button is clicked. Voilà, our component's behavior has been verified! Mocking Qwik Hooks So far, we have a test that verifies the component's logic, which is great! However, there may come a time when we need to mock parts of the component's logic to test certain scenarios. Unfortunately, here comes a little trade-off for the sake of Qwik's quickness. The component$ wrapper does not expose the internals of the component, so we cannot easily modify it for our tests. Fortunately, the most common thing we need to mock in Qwik components are hooks__ such as `useLocation()` or `useStore()`. We can do this by using `vi.mock`. As we can read in the Vitest docs, the `vi.mock` method takes two arguments: the path to the module that we want to mock, and a function that returns the mocked module. For example, if we want to modify the initial count in our useStore()` hook to be _1_, we can mock the entire @builder.io/qwik module and return the actual module with the modified initial value. We can use the JavaScript `bind` method to do this. The call to `vi.mock` can be placed anywhere in the code as it is hoisted and will always be called before modules are imported, but for clarity, we will put it in a `beforeAll` block: ` TypeScript import { createDOM } from "@builder.io/qwik/testing"; import { describe, expect, it, vi, beforeAll } from "vitest"; import { Counter } from "./counter"; beforeAll(() => { // mock useStore to start with count of 1 instead of 0 vi.mock("@builder.io/qwik", async () => { const qwik = await vi.importActual( "@builder.io/qwik" ); return { ...qwik, // return most of the module unchanged // leverage bind to set the initial state of useStore useStore: qwik.useStore.bind("initialState", { count: 1 }), }; }); }); describe("Counter component", function () { it("should increment on click", async () => { // we retrieve the userEvent` method along with `screen` and `render` const { screen, render, userEvent } = await createDOM(); // render the component await render(); // get the div that displays the count from our container const countElement = screen.querySelector(".count"); // assert the displayed count is "1" - the default value set by our mock expect(countElement?.textContent).toBe("1"); // pass a selector that matches the increment button as a first parameter // and the name of the event we want to trigger ("click") as the second parameter await userEvent("button.increment", "click"); // assert the displayed count is now incremented from 1 to 2 expect(countElement?.textContent).toBe("2"); }); }); ` Mocking the useLocation` hook is even simpler, as it is not expected to change during a test. In this case, we can directly return an object from the mock. Here is an example of how to mock the `useLocation` hook: ` TypeScript vi.mock("@builder.io/qwik", async () => { const qwik = await vi.importActual( "@builder.io/qwik" ); return { ...qwik, // return a hardcoded object for every useLocation call useLocation: { params: {}, href: "/mock", pathname: "mock", query: {}, } }; }); ` Conclusion As a newcomer to the world of JavaScript frameworks, Qwik offers a unique approach to building fast, efficient web applications. However, we shouldn't forget about the quality and robustness of our code. In this blog post, we provided a comprehensive guide on how to set up and write tests for your Qwik components. Setting up a testing environment for a Qwik app is relatively straightforward. Qwik uses Vite as its build tool, which means that the most efficient option for unit testing is to use Vitest, a test runner specifically designed for Vite projects. We showed how to install Vitest and update the Vite config file to include a test configuration, and went through examples of how to write tests for Qwik components, including how to mock hooks and test interactions with the DOM. By following the steps and examples in this post, you can easily add unit tests to your Qwik app, and ensure that your components are working correctly. This will give you confidence in your code, and allow you to build more complex and robust applications with Qwik. And if you found this article a bit overwhelming, do not worry, you can check out the Qwik starter kit which includes examples of already implemented tests. Have fun writing Qwik and reliable apps!...

Being a CTO at Any Level: A Discussion with Kathy Keating, Co-Founder of CTO Levels cover image

Being a CTO at Any Level: A Discussion with Kathy Keating, Co-Founder of CTO Levels

In this episode of the engineering leadership series, Kathy Keating, co-founder of CTO Levels and CTO Advisor, shares her insights on the role of a CTO and the challenges they face. She begins by discussing her own journey as a technologist and her experience in technology leadership roles, including founding companies and having a recent exit. According to Kathy, the primary responsibility of a CTO is to deliver the technology that aligns with the company's business needs. However, she highlights a concerning statistic that 50% of CTOs have a tenure of less than two years, often due to a lack of understanding and mismatched expectations. She emphasizes the importance of building trust quickly in order to succeed in this role. One of the main challenges CTOs face is transitioning from being a technologist to a leader. Kathy stresses the significance of developing effective communication habits to bridge this gap. She suggests that CTOs create a playbook of best practices to enhance their communication skills and join communities of other CTOs to learn from their experiences. Matching the right CTO to the stage of a company is another crucial aspect discussed in the episode. Kathy explains that different stages of a company require different types of CTOs, and it is essential to find the right fit. To navigate these challenges, Kathy advises CTOs to build a support system of advisors and coaches who can provide guidance and help them overcome obstacles. Additionally, she encourages CTOs to be aware of their own preferences and strengths, as self-awareness can greatly contribute to their success. In conclusion, this podcast episode sheds light on the technical aspects of being a CTO and the challenges they face. Kathy Keating's insights provide valuable guidance for CTOs to build trust, develop effective communication habits, match their skills to the company's stage, and create a support system for their professional growth. By understanding these key technical aspects, CTOs can enhance their leadership skills and contribute to the success of their organizations....