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

Using Twitter Bootstrap 3 and 2 alongside Angular 2 for web development

Our current Main application is developed using Angular 1.4 and incorporates Twitter Bootstrap 2.3.2. This application includes various components built with Angular 1.4 by different teams, We are now looking to create new components using Angular 2, and ...

The system encountered an issue: Unable to access the filter property of an undefined property

Here is my form code in TypeScript: this.editForm = this.fb.group({ 'dropoffs': this.fb.array(this.createDropOffFacility(data.dropoffs.filter(picks => picks.drop_facility !== ''))), }); createDropOffFacility(facilities) { ...

How to properly create a Dockerfile using Node.js as the backend

Currently, I am developing a straightforward Angular application that relies on Node.js as the backend for file uploading. The structure of my project folder is shown below: To execute this setup in Angular, I follow these steps: Run 'ng ...

After calling the PHP API, the http.get method in Ionic 2 is returning a value of isTr

Even though a similar question has been posted before, I am still puzzled about why I am receiving: 'isTrusted': true every time I call a PHP API from Ionic 2. The code I am using is as follows: getProduct(id: string){ if(this._product){ con ...

Troubleshooting Angular 2: (keyup) event not functioning on Tour of Heroes Tutorial

I am currently working on a tutorial for the tour of heroes, and I have encountered an issue with the (keyup) event in my project. What seems to be happening is that when I input the first letter, Angular sends a request, but subsequent key presses do not ...

Interface circular dependency is a phenomenon where two or more interfaces

In my MongoDB setup, I have created an interface that defines a schema using mongoose as an ODM. import mongoose, { model, Schema, Model, Document } from "mongoose"; import { IUser } from "./user"; import { IPost } from "./posts&q ...

Struggling with Angular 8: Attempting to utilize form data for string formatting in a service, but encountering persistent page reloading and failure to reassign variables from form values

My goal is to extract the zip code from a form, create a URL based on that zip code, make an API call using that URL, and then display the JSON data on the screen. I have successfully generated the URL and retrieved the necessary data. However, I am strug ...

Discovering the most recent 10 date elements in a JSON object within a React application

I have an array of objects containing a date element. My goal is to identify the 10 most recent dates from this element and display them in a table format. When I attempt to render these dates using a mapping technique that targets each data with data.date ...

Decrease initial loading time for Ionic 3

I have encountered an issue with my Ionic 3 Android application where the startup time is longer than desired, around 4-5 seconds. While this may not be excessive, some users have raised concerns about it. I am confident that there are ways to improve the ...

Is it possible to prevent casting to any by improving type definitions?

My query can be best elucidated with the following code snippet. The comments within the code aim to provide clarity on the nature of the question. type MediaFormats = "video" | "audio"; type IMediaContent<TType extends MediaFormats, TValue> = { ...

Enhance your coding experience with Angular Apollo Codegen providing intelligent suggestions for anonymous objects

Currently, I am exploring the integration of GraphQL with Angular. So far, I have been able to scaffold the schema successfully using the @graphql-codegen package. The services generated are functional in querying the database. However, I've noticed ...

Exploring the world of shaders through the lens of Typescript and React three fiber

Looking to implement shaders in React-three-fiber using Typescript. Shader file: import { ShaderMaterial } from "three" import { extend } from "react-three-fiber" class CustomMaterial extends ShaderMaterial { constructor() { supe ...

The Order ID field in the Serenity-Platform's Order Details tab is not registering orders

I've been working on replicating the functionality of Orders-Order detail in my own project. https://i.stack.imgur.com/Bt47B.png My custom module is called Contract and Contract Line item, which I'm using to achieve this. https://i.stack.imgur ...

What is the most effective way to share data among components in React?

I recently delved into learning about react and find myself puzzled on how to pass data between two components. Presently, I have set up 2 functions in the following manner: First, there's topbar.tsx which displays information for the top bar, inclu ...

Developing an array in Angular on an Android device is proving to be a sluggish

I'm facing an issue with my simple array that collects rows from a database and uses a distance column as a key. let output = {}; for (let dataRow of sqllite.rows) { output[dataRow.distance] = dataRow; } When testing in Chrome browser on my PC, ...

Is it possible that Typescript does not use type-guard to check for undefined when verifying the truthiness of a variable?

class Base {} function log(arg: number) { console.log(arg); } function fn<T extends typeof Base>( instance: Partial<InstanceType<T>>, key: keyof InstanceType<T>, ) { const val = instance[key]; if (val) { ...

What is the best way to transform an Observable<Observable<HttpEvent<any>>> into an Observable<HttpEvent<any>> within the Angular AuthInterceptor service?

export class AuthInterceptor implements HttpInterceptor { constructor(private usersService: UsersService){} intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { return this.usersService ...

Angular components are persisting instead of being destroyed

When navigating to a new page in my Angular application, I've noticed that the component from the previous page remains in memory instead of being destroyed. This results in a new instance being created when I navigate back to that page. The applicat ...

Executing multiple instances of the cascading dropdown fill method in Angular 7

I am currently working on an Angular app that includes cascading comboboxes for country and state selection. However, I have noticed that the get states() method in my state.component.ts file is taking a long time to run. What could be causing this issue? ...

What is the best way to retrieve JSON data from a raw.github URL and save it into a variable?

Suppose there is a JSON file named data.json on Github. The raw view of the file can be accessed through a URL like this: https://raw.githubusercontent.com/data.json (Please note that this URL is fictional and not real). Assume that the URL contains JSON ...