The typed union type FormGroup in Angular stands out for its versatility and robustness

Within my application, users select a value from a dropdown menu to determine which type of FormGroup should be utilized. These formGroups serve as "additional information" based on the selection made.

I am currently working with three distinct types of formGroups, but also considering the possibility of not using a formGroup at all. I aim to enhance the codebase by implementing typing, as the existing code was developed before typed reactive forms gained popularity in Angular.

It is important to note that the FormGroup types do not share any common fields.

To see a simplified demonstration, you can visit this link.

My attempt at incorporating

FormGroup<TeacherForm | PolicemanForm | FireFighterForm>
did not yield the desired results.

Can anyone suggest the optimal approach for managing these different types of formGroups?

Thank you.

Answer №1

Utilize the FormRecord feature to generate forms that can dynamically add and remove controls.

This is how it operates:

  formGroup = new FormRecord({
     name: new FormControl(''),
     ...
   });   

   if (event.value === 'Teacher') {
      this.formGroup.setControl(
        'additionalInfo',
        new FormGroup(new TeacherForm()) -->FormGroup
      );
   }  

In your template, use the formGroupName directive to group related controls within the dynamic form group:

<ng-container formGroupName="additionalInfo">
    <input
  type="text"
  pInputText
  formControlName="specialization"
  placeholder="Specialization"
/>
  </ng-container>

Customized Working Example Link

Answer №2

We have the option to create a union type | where all parameters can be marked as optional using ?:. This allows for more flexibility in usage. Additionally, I consolidated the types into a single union type named UNION_FORM_GROUP_TYPES.

I chose to keep the types distinct because if you are certain about a specific type like "teacher," you can explicitly cast it using <TeacherForm> or as TeacherForm.

export class FireFighterForm {
  interventions?: FormControl<number | null> = new FormControl<number | null>(
    null
  );
}

export class PolicemanForm {
  department?: FormControl<number | null> = new FormControl<number | null>(
    null
  );
}

export class TeacherForm {
  specialization?: FormControl<number | null> = new FormControl<number | null>(
    null
  );
}

export type UNION_FORM_GROUP_TYPES =
  | TeacherForm
  | PolicemanForm
  | FireFighterForm
  | null;

FULL CODE:

import { Component, OnInit } from '@angular/core';
import { ImportsModule } from './imports';
import { FormControl, FormGroup } from '@angular/forms';
import { DropdownChangeEvent } from 'primeng/dropdown';
import { FireFighterForm } from './forms/firefighter.form';
import { PolicemanForm } from './forms/policeman.form';
import { TeacherForm } from './forms/teacher.form';

export type UNION_FORM_GROUP_TYPES =
  | TeacherForm
  | PolicemanForm
  | FireFighterForm
  | null;

@Component({
  selector: 'dropdown-reactive-forms-demo',
  templateUrl: './dropdown-reactive-forms-demo.html',
  styles: `.card,form { display: flex; flex-direction: column; gap: 10px;}`,
  standalone: true,
  imports: [ImportsModule],
})
export class DropdownReactiveFormsDemo {
  personTypes: string[] = [
    'Teacher',
    'Policeman',
    'Firefighter',
    'Bank',
    'Shop',
    'Insurance Company',
  ];
  selectedPersonType: string | undefined;

  formGroup = new FormGroup({
    name: new FormControl(''),
    surname: new FormControl(''),
    additionalInfo: new FormGroup<UNION_FORM_GROUP_TYPES>({}), // HOW TO TYPE PROPERLY?
    // additionalInfo: new FormGroup<TeacherForm | PolicemanForm | FireFighterForm | null>(null) // does not work
  });

  onPersonTypeChange(event: DropdownChangeEvent) {
    if (event.value === 'Teacher') {
      this.formGroup.setControl(
        'additionalInfo',
        new FormGroup<UNION_FORM_GROUP_TYPES>(new TeacherForm())
      );
    } else if (event.value === 'Policeman') {
      this.formGroup.setControl(
        'additionalInfo',
        new FormGroup<UNION_FORM_GROUP_TYPES>(new PolicemanForm())
      );
    } else if (event.value === 'Firefighter') {
      this.formGroup.setControl(
        'additionalInfo',
        new FormGroup<UNION_FORM_GROUP_TYPES>(new FireFighterForm())
      );
    }
  }
}

Live Demo on StackBlitz

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Angular2 and ReactiveX: Innovative Pagination Strategies

Currently, I am diving into the world of ReactiveX. To make things easier to understand, I have removed error checking, logging, and other unnecessary bits. One of my services returns a collection of objects in JSON format: getPanels() { return this. ...

Using the Ajax method from a separate class in TypeScript: A step-by-step guide

Recently, I started learning about typescript and ajax. One of the challenges I encountered was while creating a method in typescript for making ajax calls that can be used across classes: myFunc(value: string): JQueryPromise<any> { var dfd = $. ...

Typescript encounters transpilation issues when the spread operator is omitted for undefined values {...undefined}

I am currently working on a TypeScript project where I have encountered a peculiar issue. Within some of my TypeScript files, I am including a plain JavaScript/Node file named config.js. The content of config.js is as follows: 'use strict'; modu ...

There seems to be a problem with the [at-loader] node_modules@typesjasmine

My webpack build suddenly started failing with no package updates. I believe a minor version change is causing this issue, but I'm unsure how to resolve it. Can someone provide guidance on what steps to take? ERROR in [at-loader] node_modules\@t ...

What is the best way to repurpose a variable in Angular's TypeScript?

I'm currently working on an application that utilizes the following technologies. In my Typescript file named "test.page.ts", there is a variable called "response: any" that I need to reuse in another Typescript file named "test2.page.html" by calling ...

What is the best approach to defining a type for a subclass (such as React.Component) in typescript?

Can someone help me with writing a type definition for react-highlight (class Highlightable)? I want to extend Highlightable and add custom functionality. The original Highlightable JS-class is a subclass of React.Component, so all the methods of React.Com ...

Turn off TypeScript's type validation during production builds

For my petite project, I am utilizing Next.js with TypeScript. A thought has been lingering in my mind lately: is there a way to turn off the types validity checks while executing npm run build? Since the type checking occurs during npm run dev, it seems ...

Tips on ensuring that only one Angular Material expansion panel expands at a time

I have designed a mat expansion panel and I would like to ensure that only one panel can be expanded at a time. In other words, I want it so that when one record is expanded and I click on another record of the mat expansion, the previously expanded reco ...

Guide to customizing Material UI theme using Typescript in a separate file

Trying to customize Material UI theme overrides can be a bit tricky, as seen in the example below: // theme.ts const theme: Theme = createMuiTheme({ overrides: { MuiButton: { root: { display: 'inline-block', fontWeigh ...

Difficulty in connecting React to Node.js with the use of axios

Recently, I embarked on a project using React and Node to create an app that allows users to add people data to a database. The frontend is built with React and can be accessed at localhost:3000, while the backend, developed with Node, runs on localhost:33 ...

Angular overlooks file-loader during AOT compilation

I am working on creating a documentation website using Angular. The docs will be written in markdown and displayed through the ngx-markdown plugin. While everything works perfectly fine on a JIT build, I am facing an issue where AOT build always removes th ...

What is the reason why modifying a nested array within an object does not cause the child component to re-render?

Within my React app, there is a page that displays a list of item cards, each being a separate component. On each item card, there is a table generated from the nested array objects of the item. However, when I add an element to the nested array within an ...

Angular 4 Datepicker failing to show date in the format of MM/DD/YYYY

Within my Angular 4 project, I have incorporated a datepicker in the HTML code. This is what it looks like in the HTML: <mat-form-field class="col-sm-12 nopadding pull-left"> <input matInput [matDatepicker]="date" placeholder="Please select ...

Utilizing Google Closure Library with Angular 6

I am looking to integrate the google closure library into my angular 6 application. To achieve this, I have utilized the following commands: npm install google-closure-compiler and npm install google-closure-library. My application can be successfully co ...

In a situation where Typescript fails to provide enforcement, how can you effectively indicate that a function is not defined for specific value(s)?

If I were to utilize Typescript to create a function called mean that calculates the mean of an array of numbers, how should I handle the scenario where the array is empty? Enforcing that an array must be non-empty can be inconvenient, so what would be th ...

What is the best way to implement a dynamic back button in Next.js?

Being familiar with creating a standard back button, I am now eager to craft one that directs the user back by one step in the URL rather than returning to the previous page. This way, I can utilize the button in various locations without needing to alter ...

Compiling TypeScript files with an incorrect path when importing, appending "index" at the end of the @angular/material library

I'm currently working on creating a library to collect and distribute a series of Angular components across various projects, with a dependency on angular/material2. My objective is to eventually publish it on npm. However, I've encountered an i ...

Issue encountered in TypeScript: Property 'counter' is not found in the specified type '{}'.ts

Hey there, I'm currently facing an issue while trying to convert a working JavaScript example to TypeScript (tsx). The error message I keep encountering is: Property 'counter' does not exist on type '{}'.ts at several locations wh ...

Retrieve information from an axios fetch call

Having an issue with the response interface when handling data from my server. It seems that response.data.data is empty, but response.data actually contains the data I need. Interestingly, when checking the type of the last data in response.data.data, it ...

How can I create a computed field in TypeORM by deriving its value from other fields within the same Entity?

My goal is to implement a 'rating' field in my User Entity. Within the User Entity, there exists a relationship with the Rating Entity, where the User has a field called ratingsReceived that eagerly loads all Ratings assigned to that User. The & ...