Uncovering the mystery of retrieving form values from dynamic HTML in Angular 2

As a beginner in Angular 2, I am facing challenges extracting values from Dynamic HTML. My task involves having some Form Inputs and injecting Dynamic HTML within them that contain additional inputs.

I followed the example by @Rene Hamburger to create the Dynamic Form Input.

The example consists of 3 Inputs - 2 in the Template (Name, LastName). I then inject the address using 'addcomponent'.

While I use Form Builder to construct all 3 controls, upon submitting, only the values of Name & Last Name are displayed, while the address values remain elusive.

I am unsure how to retrieve these address values and seek assistance from the community experts.

http://plnkr.co/edit/fcS1hdfLErjgChcFsRiX?p=preview

app/app.component.ts

import {AfterViewInit,OnInit, Compiler, Component, NgModule, ViewChild,
  ViewContainerRef} from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'
import { FormGroup, FormControl, FormArray, FormBuilder, Validators } from '@angular/forms';
import { DomSanitizer, SafeHtml } from "@angular/platform-browser";

@Component({
  selector: 'my-app',
  template: `
    <h1>Dynamic template:</h1>

    <form [formGroup]="myForm" (ngSubmit)="onSubmit()">
     <div  class="form-row">
      <label for="">Name</label>
      <input type="text" class="form-control" formControlName="name">
            <small [hidden]="myForm.controls.name.valid || (myForm.controls.name.pristine && !submitted)" class="text-danger">
            Name is required (minimum 5 characters).
          </small>
    </div>

        <div  class="form-row">
      <label for="">Last Name</label>
      <input type="text" class="form-control" formControlName="lastname">
            <small [hidden]="myForm.controls.name.valid || (myForm.controls.name.pristine && !submitted)" class="text-danger">
            Name is required (minimum 5 characters).
          </small>
    </div>

       <div #container></div>

      <div class="form-row">
      <button type="submit">Submit</button>
      </div>
       <div *ngIf="payLoad" class="form-row">
            <strong>Saved the following values</strong><br>{{payLoad}}
        </div>


    </form>
  `,
})
export class AppComponent implements OnInit , AfterViewInit {
  @ViewChild('container', { read: ViewContainerRef }) container: ViewContainerRef;
  public myForm: FormGroup; // our model driven form
  public payLoad: string;

    public onSubmit() {
        this.payLoad = JSON.stringify(this.myForm.value);
    }

  constructor(private compiler: Compiler,private formBuilder: FormBuilder,private sanitizer: DomSanitizer) {}

ngOnInit() {
      this.myForm = this.formBuilder.group({
            name: ['', [<any>Validators.required, <any>Validators.minLength(5)]],
            lastname: ['', [<any>Validators.required, <any>Validators.minLength(5)]],
            address: ['', [<any>Validators.required, <any>Validators.minLength(5)]]
            });
}
  ngAfterViewInit() {

    this.addComponent('<div  class="form-row"> <label for="">Address</label> <input type="text" class="form-control" formControlName="address">  </div>');
  }

  private addComponent(template: string) {
    @Component({template: template})
    class TemplateComponent {}

    @NgModule({declarations: [TemplateComponent]})
    class TemplateModule {}

    const mod = this.compiler.compileModuleAndAllComponentsSync(TemplateModule);
    const factory = mod.componentFactories.find((comp) =>
      comp.componentType === TemplateComponent
    );
    const component = this.container.createComponent(factory);
  }
}

Since the Plunker link is not functional, I have added the example on stackblitz.

https://stackblitz.com/edit/angular-t3mmg6

This dynamic Form controls example in the 'addcomponent' method allows you to fetch Form controls from the server. By examining the 'addcomponent' method, you can observe the Form Controls being utilized. While this example does not involve angular material, it still functions effectively. It targets Angular 6 but also works seamlessly in previous versions.

JITComplierFactory needs to be added for Angular Version 5 and above.

Thank you,

Vijay

Answer №1

The issue arises when you include the group address in the formbuilder groups of the parent component, but the HTML is actually added as a child component, causing your formgroup values to not update properly.

One way to solve this problem using a parent-child approach is by transmitting the value changes from the child component to the parent component and manually setting the form group value. You can explore different methods of communication between parent-child components here.

Instead of adding child components, it may be more efficient to utilize ngFor or ngIf directives to manage your dynamic form. Check out an example of how to implement this approach here.

Answer №2

My colleague, Justin, assisted me in successfully accessing Form Values from Dynamic HTML without relying on services. While @Hagner's approach using services is effective, the method outlined below offers a simpler way to achieve the same outcome. For those facing similar situations, I wanted to share this solution.

-- app/app.component.ts

    import {
  AfterContentInit, AfterViewInit, AfterViewChecked, OnInit, Compiler, Component, NgModule, ViewChild,
  ViewContainerRef, forwardRef, Injectable, ChangeDetectorRef
} from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { ReactiveFormsModule, FormGroup, FormControl, FormsModule, FormArray, FormBuilder, Validators } from '@angular/forms';
import { DomSanitizer, SafeHtml } from "@angular/platform-browser";

@Injectable()
export class DynamicControlClass {
  constructor(public Key: string,
    public Validator: boolean,
    public minLength: number,
    public maxLength: number,
    public defaultValue: string,
    public requiredErrorString: string,
    public minLengthString: string,
    public maxLengthString: string,
    public ControlType: string
  ) { }
}

@Component({
  selector: 'my-app',
  template: `
    <h1>Dynamic template:</h1>

<div class="container">
    <form [formGroup]="myForm" (ngSubmit)="onSubmit()" novalidate>



     <div  class="form-row">
      <label for="">First Name</label>
      <input type="text" class="form-control" formControlName="firstname" required>
              <div *ngIf="formErrors.firstname" class="alert alert-danger">
                {{ formErrors.firstname }}
              </div>

    </div>

        <div  class="form-row">
      <label for="">Last Name</label>
      <input type="text" class="form-control" formControlName="lastname"  required>

                            <div *ngIf="formErrors.lastname" class="alert alert-danger">
                {{ formErrors.lastname }}
              </div>
      </div>

       <div #container></div>
<!--
<div  class="form-row">

    <input type="radio" formControlName="Medical_Flu_Concent_Decline_medical_flu_concent_decline_rule1" value="1"> <b>Concent Template </b>
    <br>
    <input type="radio" formControlName="Medical_Flu_Concent_Decline_medical_flu_concent_decline_rule1" value="2"> <b>Decline  Template</b>
</div>
-->

       <br>
       <!--
            <button type="submit" class="btn btn-default"
             [disabled]="!myForm.valid">Submit</button>
       -->

                   <button type="submit" class="btn btn-default" >Submit</button>

       <div *ngIf="payLoad" class="form-row">
            <strong>Saved the following values</strong><br>{{payLoad}}
        </div>

        <div> Is Form Valid : {{myForm.valid}}</div>
    </form>

    </div>
  `
  ,
  styles: ['h1 {color: #369;font-family: Arial, Helvetica, sans-serif;font-size: 250%;} input[required]:valid {border-left: 5px solid #42A948; /* green */ } input[required]:invalid {border-left: 5px solid #a94442; /* red */ } .radioValidation input:invalid{outline: 2px solid  #a94442;} .radioValidation input:valid{outline: 2px solid #42A948;}'],

})
export class AppComponent implements AfterContentInit {
  @ViewChild('container', { read: ViewContainerRef }) container: ViewContainerRef;
  public myForm: FormGroup; // our model driven form
  public payLoad: string;
  public controlData: [string, boolean, number];
  public ctlClass: DynamicControlClass[];
  public formErrors: any = {};
  public group: any = {};
  public submitted: boolean = false;
  public setValidatorValue: boolean = false;

  public onSubmit() {

    this.submitted = true;
    this.setValidatorValue = false;
    this.onValueChanged();

    if (this.myForm.valid) {

      const form = this.myForm

      const control = form.get('Medical_Flu_Concent_Decline_medical_flu_concent_decline');

      if (control) {
        if (control.value === '1') {
          const controlreset = form.get('Medical_Flu_Decline_Details_medical_flu_decline_details');

          if ((controlreset) && (controlreset.value)) {
            this.myForm.patchValue({ Medical_Flu_Decline_Details_medical_flu_decline_details: null });
          }
        }
      }
      this.payLoad = JSON.stringify(this.myForm.value);
    }

  }

  constructor(private compiler: Compiler, private formBuilder: FormBuilder, private sanitizer: DomSanitizer) {

    this.ctlClass = [
      new DynamicControlClass('firstname', true, 5, 0, '', 'Please enter First Name', 'First Name must be Minimum of 5 Characters', '', 'textbox'),
      new DynamicControlClass('lastname', true, 5, 0, '', 'Please enter LastName', 'Last Name must be Minimum of 5 Characters', '', 'textbox'),
      new DynamicControlClass('address', true, 5, 0, 'Default Address', 'Please enter Address', 'Address must be Minimum of 5 Characters', '', 'textbox'),
      new DynamicControlClass('Medical_Flu_Concent_Decline_medical_flu_concent_decline', true, 0, 0, null, 'Please Select one of the Radio option', '', '', 'radio'),
      new DynamicControlClass('Medical_Flu_Decline_Details_medical_flu_decline_details', false, 0, 0, null, 'Please Select one of the Decline option', '', '', 'radio'),
      new DynamicControlClass('city', true, 5, 0, 'Enter City', 'Please enter City', 'City must be Minimum of 5 Characters', '', 'textbox')]
  };



  ngAfterContentInit() {




    this.ctlClass.forEach(dyclass => {

      let minValue: number = dyclass.minLength;
      let maxValue: number = dyclass.maxLength;

      if (dyclass.Validator) {

        this.formErrors[dyclass.Key] = '';

        if ((dyclass.ControlType === 'radio') || (dyclass.ControlType === 'checkbox')) {
          this.group[dyclass.Key] = new FormControl(dyclass.defaultValue || null, [Validators.required]);
        }
        else {

          if ((minValue > 0) && (maxValue > 0)) {
            this.group[dyclass.Key] = new FormControl(dyclass.defaultValue || '', [Validators.required, <any>Validators.minLength(minValue), <any>Validators.maxLength(maxValue)]);
          }
          else if ((minValue > 0) && (maxValue === 0)) {
            this.group[dyclass.Key] = new FormControl(dyclass.defaultValue || '', [Validators.required, <any>Validators.minLength(minValue)]);
          }
          else if ((minValue === 0) && (maxValue > 0)) {
            this.group[dyclass.Key] = new FormControl(dyclass.defaultValue || '', [Validators.required, <any>Validators.maxLength(maxValue)]);
          }
          else if ((minValue === 0) && (maxValue === 0)) {
            this.group[dyclass.Key] = new FormControl(dyclass.defaultValue || '', [Validators.required]);
          }
        }
      }
      else {

        if (dyclass.Key === 'Medical_Flu_Decline_Details_medical_flu_decline_details') {
          this.formErrors[dyclass.Key] = 'null';
          this.group[dyclass.Key] = new FormControl(dyclass.defaultValue);
        }
        else {
          this.group[dyclass.Key] = new FormControl(dyclass.defaultValue || '');
        }


      }



    });

    this.myForm = new FormGroup(this.group);

    this.myForm.valueChanges.subscribe(data => this.onValueChanged(data));

    this.onValueChanged(); // (re)set validation messages now


    this.addComponent('<div [formGroup]="_parent.myForm" class="form-row">  <label for="">Address</label> <input type="text" class="form-control" formControlName="address"  required> <div *ngIf="_parent.formErrors.address" class="alert alert-danger">{{ _parent.formErrors.address }}</div><\div><div [formGroup]="_parent.myForm" class="form-row">   <label for="">City</label> <input type="text" class="form-control" formControlName="city"  required> <div *ngIf="_parent.formErrors.city" class="alert alert-danger">{{ _parent.formErrors.city }}</div><\div>&...';</answer2>
<exanswer2><div class="answer accepted" i="41152783" l="3.0" c="1481741294" m="1485967696" v="1" a="VmlqYXkgQW5hbmQgS2FubmFu" ai="7197101">
<p>My Colleague (Justin) helped me on how to access the Form Values from the Dynamic HTML without the need for services like with Hagner's answer (<a href="http://plnkr.co/edit/DeYGuZSOYvxT76YI8SRU?p=preview" rel="nofollow noreferrer">http://plnkr.co/edit/DeYGuZSOYvxT76YI8SRU?p=preview</a>). This different approach serves as an alternative to utilizing services and presents a more straightforward solution for reaching the desired outcome. I wanted to provide this option for individuals who may face similar challenges.</p>

<pre><code>-- app/app.component.ts



-- index.html

<!DOCTYPE html>
<html>

  <head>


      System.import('app').catch(function(err){ console.error(err); });
    </script>     
  </head>

  <body>
    <my-app>Loading...</my-app>
  </body>

</html>

http://plnkr.co/edit/rELaWPJ2cDJyCB55deTF?p=preview

All credits go to Justin for his support throughout the process.

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

In Angular 4, I encountered an issue where adjusting the height of the navigation bar caused the toggle button to no longer expand the menu in Bootstrap

In the process of developing an angular application, I encountered a situation where I needed to adjust the height of the nav bar using style properties. After making the necessary changes, everything was working fine. However, a problem arose when I mini ...

Combining two datasets within a single subscription either renders the set as read-only or allows for modifications to be made to the set in Angular

Hey there! I've been encountering some issues with a service I'm using. It takes a long time to return its result, and to make matters worse, I have to call it twice. The data I receive forms two grids, with one being editable and triggering ev ...

AngularJS ng-model not refreshing

One of the features in my application is a Font Awesome icon picker that allows employees to easily access different icons without having to search for their codes online. However, I am facing an issue where clicking on an icon does not update the ng-mode ...

Stop the upload progress in Angular 6 by unsubscribing from the upload observable, without halting the actual

When attempting to cancel an upload by unsubscribing, the unsubscribe action only stops the progress of the upload from being displayed, but the actual upload process continues and files are still uploaded to the server. This issue is present in the upload ...

Increase by 30 minutes from the minimum value and decrease by 30 minutes from the maximum value in Ionic date time

Looking to add a customized Ionic date time picker with min and max capabilities. If the minimum time is selected, it should automatically add 30 minutes, while selecting the maximum time should subtract 30 minutes. Any suggestions on how to achieve this? ...

Type of object is unidentified in Vuejs with Typescript error code ts(2571)

Currently, I am facing a challenge with storing JSON data in a variable within my Vue project. Below is the code snippet used to upload and store the file: <input type="file" id="file" ref="fileSelect" class="custom- ...

Issues with loading Kendo ScrollView dynamically within an Angular ng-template

In an effort to add dynamism to this sample, I am working on pulling items[] from a database. You can view the progress on this link. To achieve this, I have initiated a service call to load the items: public isFormReady: boolean = false; public items: ...

Utilizing Business Logic in a MEAN Stack Environment

When developing an Angular application, where should the calculations take place - on the front end or back end? Considering that the outputs need to update with each input change, is it practical to send a request to the back end every time there is an ...

When sending an HTTP POST request to a Nodejs service with an uploaded file, the request fails after 8-15 seconds on Firefox and 25 seconds on Chrome

My web app is built on Angular 7. I am facing an issue while trying to send larger files to a Node.js service. Smaller files, around 3mb, are being sent successfully but when attempting to send bigger files like 20mb, the request gets cut off. In Chrome, I ...

What strategies can I employ to manage browser compatibility issues while utilizing contemporary JS and CSS frameworks?

I have been working on a project that utilizes the most recent versions of Angular (version 5) and Bootstrap (4). However, after a few weeks of development, I noticed that some features are not functioning properly on Safari, and I am uncertain if all fea ...

Display a div above the Kendo for Angular Dialog

Looking at this StackBlitz example, there is a Kendo for Angular Dialog that contains a div with the CSS property position:absolute. The goal is to display this div on top of the dialog itself, rather than inside it, while ensuring that the dialog does n ...

Every time I hit the refresh button, I receive dual responses

In my angular 6 application, there's a sidebar component sidebar.ts ngOnInit() { if (!Array.isArray(this.menu) || !this.menu.length) { const data = JSON.parse(localStorage.getItem('data')); this.item = data.response; ...

Loop within a promise results in undefined

Within my application, there is a function that returns a promise. Inside this function, I wait for an image in the DOM to become available and then extract that element to generate base64 data from it. getCodesOfOneMerchant(merchantDataEntry: MerchantData ...

A Typescript object that matches types and eventually returns a string when called upon

In the process of overengineering a type that can match either a string or an object whose valueOf() function, when evaluated recursively, ultimately returns a string. type Stringable = string | StringableObject; interface StringableObject { valueOf() ...

Be cautious, warning.indexOf function is not supported in this version of rollup.js

Encountering the following error message when using the rollup.config.js file warning.indexOf is not a function rollup.config.js import nodeResolve from 'rollup-plugin-node-resolve' import commonjs from 'rollup-plugin-commonjs&apos ...

Should the checkbox be marked, set to 1?

I am looking for a way to call a method when the user checks or unchecks multiple checkboxes. Do you have any suggestions on how I can achieve this? Here is what I have tried so far: <input type="checkbox" [ngModel]="checked == 1 ? true : checked == 0 ...

Utilizing an Angular Service within a method, embedded in a class, nested inside a module

module Helper { export class ListController { static handleBatchDelete(data) { // Implementing $http functionality within Angular ... $http.post(data) } } } // Trigger on button click Helper.ListController. ...

Alter the entity type when passing it as a parameter

Trying to alter the Entity type, I am invoking a function that requires two parameters - one being the entity and the other a location. However, when trying to assign a Type for the first entity, it throws an error: Error: Argument of type 'Node<En ...

Unable to open modal window in Angular 14 micro-frontend application

I've been working on a micro front end project and running into some issues with opening modal popup windows. I've tried both the Angular material and bootstrap approaches, but ran into problems with both. With Angular material, the popup window ...

What is the best way to prevent the output folder from appearing in the import statements for users of my package?

I have a project written in Typescript that consists of multiple .d.ts files. I would like to package this project as an npm module and utilize it in another project. In the second project, my goal is to be able to import modules like so: import {Foo} fr ...