The beauty of crafting intricate forms with Angular's reactive nested

In my Angular project, I am exploring the concept of nesting multiple reactive forms within different components.

For instance, I have a component called NameDescComponent that includes a form with two inputs - one for name and one for description, along with a submit button.

The goal is to reuse this component across various pages and forms. Here's a snippet of the HTML code for the component:

<form [formGroup]="nameDescForm" (ngSubmit)="customEmit()">
    <div fxLayout="row" fxLayoutGap="10px" fxFlex>
      <mat-form-field>
        <input matInput placeholder="Name" formControlName="name">
      </mat-form-field>
      <mat-form-field fxFlex>
        <input matInput placeholder="Description" formControlName="description">
      </mat-form-field>
    </div>
    <div fxLayout="column" fxLayoutGap="10px">
      <button type="submit" mat-raised-button color="primary">
        {{buttonText}}
      </button>
      <div>
      </div>
    </div>
  </form>

And here's an excerpt from the corresponding TypeScript file:

public nameDescForm: FormGroup;

@Input() public buttonText: string;
@Output() public save: EventEmitter<any> = new EventEmitter<any>();
@Output() public nameDescFormEmit: EventEmitter<FormGroup> = new EventEmitter<FormGroup>();

constructor(fb: FormBuilder) {
this.nameDescForm = fb.group({
'name': ['', Validators.required],
'description': ['']
});
}

public ngOnInit() {
console.log(this.nameDescForm);
this.nameDescFormEmit.emit(this.nameDescForm);
}

public customEmit() {
this.save.emit();
}

Furthermore, in a separate page where I include the NameDescComponent, I embed it within another form like so:

<form [formGroup]="parentForm" (ngSubmit)="customEmit()">

  <app-name-description (nameDescFormEmit)="getNameDescForm($event)" buttonText="Save" (save)="save()"></app-name-description>

  <input type="test" formControlName="test">

</form>

Currently, I pass the nameDescFrom data from its component to the ParentComponent using Output and EventEmitter. Although this approach works, I find myself having to independently manage both forms' validity during submission.

I'm curious if there might be a more efficient way to handle this, perhaps accessing the nameDescFrom directly within the parent form?

Thank you

Answer №1

If you want to combine your form with nested forms and streamline the validation process for all of them, you can utilize the formbuilder to construct the entire model object structure within the main form component. Next, in the HTML template, include custom elements for the sub-forms (e.g., <nested-form>), which will display the nested forms.

Check out this example: https://stackblitz.com/edit/angular-m5fexe)

Helpful Angular documentation links:

Code :

export class Form1Component  {
  @Input() name: string;

  public dummyForm: FormGroup;

  constructor(
      private _fb: FormBuilder,
  ) {
      this.createForm();
  }

  createForm() {
    this.dummyForm = this._fb.group({
      username: ['username', Validators.required],
      nestedForm: this._fb.group({        
        complement1: ['complement1', Validators.required],
        complement2: ['complement2', Validators.required],
      })
    });
  }

  submit() {
    if (this.dummyForm.valid) {
      console.log('form AND subforms are valid', this.dummyForm.value);
    } else {
      console.warn('form AND/OR subforms are invalid', this.dummyForm.value);
    }
  }
}

Template for the Form1Component :

<form [formGroup]="dummyForm" (ngSubmit)="submit()">    
    <div>
      <label for="username">Root Input</label>
      <input type="text" id="username" formControlName="username"/>
    </div>
    <nested-form [parentForm]="dummyForm"></nested-form>
    <button>Send</button>    
  </form>

Nested form code:

export class NestedFormComponent {
  @Input()
  public parentForm: FormGroup;
}

Nested form template :

<form [formGroup]="parentForm">
    <div formGroupName="nestedForm">
      <div>
        <label for="complement1">Nested input 1</label>
        <input type="text" formControlName="complement1"/>
      </div>
      <div>
        <label for="complement1">Nested input 1</label>
        <input type="text" formControlName="complement2"/>
      </div>
    </div>
  </form>

Answer №2

If you want to enhance your Angular forms, consider using a custom form control to streamline the process.

Essentially, a custom component acts as a connection point between the main form and any nested forms, ensuring smooth communication between the two. By listening for changes in the nested form, the custom component can update its value accordingly. Despite containing values from multiple nested form controls, the parent form will view the custom control as a single entity.

To implement this functionality, the custom component must adhere to the ControlValueAccessor interface provided by Angular. This allows for seamless management of the custom control's state, including aspects like value, validity, touch status, and more. With this approach, the parent form can interact with the custom control just like any other form element.

To learn more about this technique, check out these resources:

https://medium.com/@majdasab/implementing-control-value-accessor-in-angular-1b89f2f84ebf

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

utilizing BrowserRouter for dynamic routing in react-router-dom

I'm currently facing a challenge with creating a multi-tenant SaaS solution. Each tenant needs to be able to use a subdomain, so that I can extract the subdomain from the URL and make a call to a REST API to retrieve data specific to that tenant. For ...

Encase all jQuery functionalities within a custom class

I am looking to create a JavaScript class that encapsulates jQuery's DOM functions, but I want these functions to only interact with a single property of that class. class Foo { constructor() { this.$wrapper = $('<div>wrapper</div ...

Using JavaScript to calculate dimensions based on the viewport's width and height

I have been trying to establish a responsive point in my mobile Webview by implementing the following JavaScript code: var w = window.innerWidth-40; var h = window.innerHeight-100; So far, this solution has been working effectively. However, I noticed th ...

ReactJS form submissions failing to detect empty input values

My goal is to use react to console.log the input value. Below is the code I've created: import React from 'react'; import ReactDOM from 'react-dom'; class App extends React.Component{ constructor() { super(); this.proce ...

Gaining access to the isolated scope of a sibling through the same Angular directive led to a valuable discovery

I am currently working on an angularjs directive that creates a multi-select dropdown with a complex template. The directives have isolated scopes and there is a variable called open in the dropdown that toggles its visibility based on clicks. Currently, t ...

Holding off $ajax requests until specific code finishes executing

I'm facing an issue with incorporating geolocation data into my $ajax call URL. Currently, both console.log(lat/lon) calls return the initial value of 0, indicating that the geolocation call is too late to provide the updated values. This results in t ...

Is there a way to deactivate the spin buttons for an input number field?

Is there a way to create an input element with type number in Vue using createElement() in TypeScript and then disable the spin buttons for increment and decrement? I attempted to use the following CSS: input[type=number]::-webkit-inner-spin-button, input ...

Implementing a custom type within a generic function

Struggling with a particular problem, I am trying to figure out whether it is possible to pass a custom type or if only native TypeScript types (such as string and number) can be passed into a generic type implementation for an interface: type coordinates ...

Testing the functionality of an event through unit test cases

I'm currently working on writing unit test cases for the function shown below: selected (event:any) { let selectedValue = event.target.value.substring(0,3); this.seletedBatch = selectedValue; this.enableSubmitButton = true } My test cases are a ...

Troubleshooting the issue with default useAsDefault routing in Angular 2

I have implemented Angular 2 for routing and Node for local hosting. However, I encountered an issue where using 'useAsDefault:true' for my route caused the nav bar links to stop functioning properly. The URL would redirect to http://localhost/ ...

Utilizing Tick formatting in Chart.js with Typescript: A step-by-step guide

When setting Chart.js to use the en-US locale, the scale numbers are formatted optimally. If I try using a tick callback as shown in the documentation: ticks: { callback: function(value) { return value.toString(); } } I notice that the ...

Displaying time text in input element due to browser bug

I am faced with a perplexing puzzle that has left me scratching my head. Here are two seemingly identical pieces of javascript code, but one behaves unexpectedly (take note of the Console.Log): Updates the UI once, then abruptly stops updating: http://js ...

Customizing content-type header in Angular httpclient

I need help sending a block of ndjson to an API using Angular httpClient. The API requires each JSON object to be newline delineated, rather than accepting an array of objects. This means I have to send a string of JSON objects with newlines between them. ...

Error 404 encountered while trying to access a website with parameters using Vue.js

Currently, I am in the process of building a website using VueJS and recently discovered how to use url parameters. Everything was working perfectly on my local machine - I could easily navigate to different pages by including parameters in the URL. For e ...

External function does not support jQuery types

In my theme.js file, I currently have the following code: jQuery(function ($) { accordion($) }) const accordion = ($) => ... By placing the accordion function directly into the jQuery function, Typescript is able to assist with the installed jquery ...

Unable to save input from <from> in the React state object

I'm currently working on a project in reactjs where I need to store user input in the react state object. I followed an example from reactjs.com, but it seems like the input is not being stored in the state object as expected. class CreateMovieForm ex ...

What is the best way to save high-resolution images created with HTML5 canvas?

Currently, there is a JavaScript script being used to load and manipulate images using the fabricjs library. The canvas dimensions are set to 600x350 pixels. When smaller images are uploaded onto the canvas and saved as a file on disk, everything works c ...

Determining a value that increases to yield a fresh sum

I'm currently developing a character generator that determines your score based on the experience points you allocate to it. The scoring system is such that 1 XP gives you a score of 1, 3 XP gives you a score of 2, 6 XP gives you a score of 3, 10 XP g ...

What is the best way to apply styling to a kendo-grid-column?

Utilizing the kendo-grid in my project serves multiple purposes. Within one of the cells, I aim to incorporate an input TextBox like so: <kendo-grid-column field="value" title="{{l('Value')}}" width="200"></kendo-grid-column> It is ...

Unable to transfer the properties of reactjs to react-chartist

How can I pass the state from the parent component "main.js" into the child component "bar.js"? //main.js import React, { Component } from 'react'; import BarChart from './Bar-chart'; class Hero extends Component { cons ...