[innerHTML]
property and creating dynamic Reactive Forms to bind to the inputs. However, that method fails due to technical limitations of using the innerHTML property. Once the HTML is rendered in the browser all properties are forced to lowercase text so any Angular directives or properties then fail. Such as *ngIf, *ngFor, [formGroup], formControlName
, etc... Angular uses camelCase for just about everything and therefor once it is forced to lowercase text it all is ignored and this method is no longer a viable solution.
[innerHTML]
property, once again rendering this method useless (as described in my first attempt).
By using this method the dynamic components HTML is displayed, but I get a new error:
"ERROR Error: formGroup expects a FormGroup instance. Please pass one in."
StackBlitz with error scenario
Working StackBlitz with solution
The solution is to create the Reactive Form in the parent component. Then use Angulars dependency injection and inject the parent component into the Dynamic Component.
By injecting the parent component into the dynamic component you will have access to all of the parents components public properties including the reactive form. This solution demonstrates being able to create and use a Reactive Form to bind to the input in a dynamically generated component.
Full code below
import {
Component, ViewChild, OnDestroy,
AfterContentInit, ComponentFactoryResolver,
Input, Compiler, ViewContainerRef, NgModule,
NgModuleRef, Injector, Injectable
} from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import {
ReactiveFormsModule, FormBuilder,
FormGroup, FormControl, Validators
} from '@angular/forms';
@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: 'app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements AfterContentInit, OnDestroy {
@ViewChild('dynamicComponent', { read: ViewContainerRef }) _container: ViewContainerRef;
public ackStringForm: FormGroup;
public ctlClass: DynamicControlClass[];
public formErrors: any = {};
public group: any = {};
public submitted: boolean = false;
private cmpRef;
constructor(
private fb: FormBuilder,
private componentFactoryResolver: ComponentFactoryResolver,
private compiler: Compiler,
private _injector: Injector,
private _m: NgModuleRef<any>) {
this.ctlClass = [
new DynamicControlClass('formTextField', true, 5, 0, '', 'Please enter a value', 'Must be Minimum of 5 Characters', '', 'textbox')]
}
ngOnDestroy() {
//Always destroy the dynamic component
//when the parent component gets destroyed
if (this.cmpRef) {
this.cmpRef.destroy();
}
}
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 {
this.group[dyclass.Key] = new FormControl(dyclass.defaultValue || '');
}
});
this.ackStringForm = new FormGroup(this.group);
this.ackStringForm.valueChanges.subscribe(data => this.onValueChanged(data));
this.onValueChanged();
this.addComponent();
}
private addComponent() {
let template = ` <div style="border: solid; border-color:green;">
<p>This is a dynamic component with an input using a reactive form </p>
<form [formGroup]="_parent.ackStringForm" class="form-row">
<input type="text" formControlName="formTextField" required>
<div *ngIf="_parent.formErrors.formTextField" class="alert alert-danger">
{{ _parent.formErrors.formTextField }}</div>
</form><br>
<button (click)="_parent.submitForm()"> Submit</button>
<br>
</div>
<br>
`;
@Component({
template: template,
styleUrls: ['./dynamic.component.css']
})
class DynamicComponent {
constructor(public _parent: AppComponent) {}
}
@NgModule({
imports: [
ReactiveFormsModule,
BrowserModule
],
declarations: [DynamicComponent]
})
class DynamicComponentModule { }
const mod = this.compiler.compileModuleAndAllComponentsSync(DynamicComponentModule);
const factory = mod.componentFactories.find((comp) =>
comp.componentType === DynamicComponent
);
const component = this._container.createComponent(factory);
}
private onValueChanged(data?: any) {
if (!this.ackStringForm) { return; }
const form = this.ackStringForm;
for (const field in this.formErrors) {
// clear previous error message (if any)
this.formErrors[field] = '';
const control = form.get(field);
if ((control && control.dirty && !control.valid) || (this.submitted)) {
let objClass: any;
this.ctlClass.forEach(dyclass => {
if (dyclass.Key === field) {
objClass = dyclass;
}
});
for (const key in control.errors) {
if (key === 'required') {
this.formErrors[field] += objClass.requiredErrorString + ' ';
}
else if (key === 'minlength') {
this.formErrors[field] += objClass.minLengthString + ' ';
}
else if (key === 'maxLengthString') {
this.formErrors[field] += objClass.minLengthString + ' ';
}
}
}
}
}
public submitForm(){
let value = this.ackStringForm.value.formTextField;
alert(value);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With