What makes the use of partials fail in this Angular 14 app? - javascript

I am working on a with nested form groups in Angular 14.
There are form controls common to multiple forms so I have added them in a partial.
In form.component.ts I have:
import { Component } from '#angular/core';
import { FormGroup, FormControl, Validators, FormArray } from '#angular/forms';
#Component({
selector: 'my-app',
templateUrl: './form.component.html',
styleUrls: ['./form.component.css'],
})
export class FormComponent {
public form: FormGroup = new FormGroup({
first_name: new FormControl('', Validators.required),
last_name: new FormControl('', Validators.required),
email: new FormControl('', [Validators.required, Validators.email]),
phone: new FormControl('', Validators.required),
});
constructor() {}
ngOnInit(): void {}
public sendFormData() {
console.log(this.form.value);
}
}
In form.component.html I include the partial <app-additional></app-additional>:
<form [formGroup]="form">
<mat-form-field appearance="outline" floatLabel="always">
<mat-label class="mat-label">Fast name:</mat-label>
<input class="mat-input" matInput formControlName="first_name" />
</mat-form-field>
<mat-form-field appearance="outline" floatLabel="always">
<mat-label class="mat-label">Last name:</mat-label>
<input class="mat-input" matInput formControlName="last_name" />
</mat-form-field>
<app-additional></app-additional>
<div class="center">
<button
(click)="sendFormData()"
mat-raised-button
color="primary"
[disabled]="!form.valid"
>
Submit
</button>
</div>
</form>
See Stackblitz HERE.
The problem
Instead of rendering the partial, the app throws the error:
Error: formControlName must be used with a parent formGroup directive.
Questions:
What causes this problem?
What is the most reliable way to fix it?

You need to pass your form as an input to partial and provide the same form group name over there to make it work.
In form.component.html:
<app-additional [form]="form"></app-additional>
In additional.component.html:
<div class="more-controls" [formGroup]="form">
In additional.component.ts:
#Input() form: FormGroup;

The problem is caused because the form control inside the partial component () is not bound to a parent FormGroup directive.
To fix this problem, you can pass the form FormGroup from the parent component to the partial component using property binding ([form]="form") and bind the form control inside the partial component to the parent FormGroup using formControlName and formGroup directive.
just like this :
<form [formGroup]="form">
<!-- ... -->
<app-additional [form]="form"></app-additional>
<!-- ... -->
</form>

Related

How to Pass Form Group Data to Submit Button in Another Component (Angular 7)

I'm having issues passing my form group data to the saveDialog() function which updates the form data on a submit button.
How would I do this in Angular 7? I'm trying to have all my components for each form group seperated, and submitted/updated together using one button?
modify-view-action.component.html
<form [formGroup]="modifyActionForm" (ngSubmit)="saveDialog()">
<div class="container" fxLayout="row" fxLayoutGap="25px">
<div class="column1" fxLayout="column">
<mat-form-field>
<mat-label>Name</mat-label>
<input matInput>
<mat-icon matSuffix>sentiment_very_satisfied</mat-icon>
</mat-form-field>
<mat-form-field>
<mat-label>Keyword</mat-label>
<input matInput>
<mat-icon matSuffix>sentiment_very_satisfied</mat-icon>
</mat-form-field>
<mat-form-field>
<mat-label>Description</mat-label>
<input matInput>
<mat-icon matSuffix>sentiment_very_satisfied</mat-icon>
</mat-form-field>
<mat-form-field>
<mat-label>Icon</mat-label>
<input matInput>
<mat-icon matSuffix>sentiment_very_satisfied</mat-icon>
</mat-form-field>
<mat-form-field>
<mat-label>Priority</mat-label>
<input matInput>
<mat-icon matSuffix>sentiment_very_satisfied</mat-icon>
</mat-form-field>
</div>
</form>
modify-view-action.component.ts
export class ModifyViewActionComponent implements OnInit {
modifyActionForm: FormGroup;
dbrAction: any;
constructor() { }
ngOnInit() {
this.initData();
}
initData() {
this.dbrAction = JSON.parse(localStorage.getItem('DbrAction'));
}
}
First in order to get data from a FormGroup you need to add formControlName on each input you want data from. Like that :
<mat-form-field>
<mat-label>Name</mat-label>
<input matInput formControlName="name">
<mat-icon matSuffix>sentiment_very_satisfied</mat-icon>
</mat-form-field>
You also need to declare in your .ts file, the FormGroup with each controllers. Like that :
modifyActionForm = new FormGroup({
name : new FormControl(),
keyword: new FormControl(),
description: new FormControl(),
// And that ⬆ for each input in your form
})
In order to get the data from this FormGroup you need to do this :
this.modifyActionForm.value
You will get an Object with your inputs' data.
Your question is not quite clear but if you want to pass data like for example your FormGroup from a component to another one, many techniques exist.
I recommend you to read this great article from Jeff Delaney explaining the different way to sharing Data between Angular Components (Fireship.io - Sharing Data between Angular Components) and this one Fireship.io - Angular Reactive Forms Basics Guide explaining how works reactive forms and how to use them.
Good day !

Angular 9: disabled attribute different behavior in chrome and IE 11

I had a simple template with a fromGroup and bunch of form controls, and I'm trying to enable a button if the form is valid.
.ts:
import {Component, OnInit} from '#angular/core';
import {FormBuilder, FormControl, FormGroup, Validators} from '#angular/forms';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
formGroup: FormGroup;
constructor(public formBuilder: FormBuilder) {
}
ngOnInit(): void {
this.formGroup = this.formBuilder.group({
firstName: ['', Validators.minLength(3)],
lastName: ['', Validators.minLength(5)]
});
}
}
.html:
<form [formGroup]="formGroup">
<div>
<span>Name</span>
<input type="text" formControlName="firstName">
</div>
<div>
<span>Name</span>
<input type="text" formControlName="lastName">
</div>
<div>
<span>Test</span>
<input [disabled]="!formGroup.valid" type="text">
</div>
</form>
from the above example, the expectation is by default the button should be disabled and once the form is valid I should be able to see the button enabled, and in chrome it is working as expected but not in IE 11 (no errors in the console), looked at few solutions but that hasn't fixed my error, any suggestions ??
Found the solution ChangeDetectorRef.detectChanges(); fixed my issue.
this.formGroup.valueChanges.subscribe(() => {
this.changeRef.detectChanges();
});

Angular Reactive Forms: Use .controls property with formGroupName

I'm curious if it's possible to use the myForm.controls property in an Angular Reactive form instead of defining an AbstractControl for every instance?
This method is suggested in NG-Book but it's not used with nested forms and FormGroupName.
Here is my code.
HTML:
<form [formGroup]="myForm"
(ngSubmit)="onSubmit(myForm.value)">
<div>
<input type="text"
id="nameInput"
placeholder="Product"
[formControl]="myForm.controls['name']">
<div *ngIf="myForm.controls['name'].hasError('required')">Name is
required</div>
</div>
<div formGroupName="address">
<input type="text"
id="streetInput"
placeholder="Enter Street"
[formControl]="myForm.controls['street']">
<div *ngIf="myForm.controls['street'].hasError('required')">Street is required</div>
</div>
<button type="submit" class="ui button">Submit</button>
JS:
export class App {
myForm: FormGroup;
constructor(fb: FormBuilder) {
this.myForm = fb.group({
'name': ['', Validators.required],
'address': fb.group({
'street': ['', Validators.required],
});
}
}
}
I realize I can set formControlName="street" on the input but then I think I would have to use AbstractControl to determine it's validation state. Where as in my non-nested formGroup, I can just use myForm.controls['name'].hasError('required').
I don't know, i understand your question well.
Do you want to get validation about nested from control?
Then, you can get myForm.get('address').control['street'].hasError('required') or myForm.get('address').get('street').hasError('required')
And you can change hasError('required') to errors.required.
These link is description about form validation.
https://angular.io/api/forms/FormGroup#example-4
https://angular.io/guide/form-validation#reactive-form-validation
If I misunderstand your question, comment.

Angular 4 reactive form not working when nested in another component with jquery-steps plugin [duplicate]

Please see the following examples. I have loaded jquery and jquery-steps into the project already and it is working. However after rendering the view, changing the data in the input boxes doesn't update the values in the form group mainForm. I believe the reason is that jquery-steps dynamically removed and reconstructed the html for the form, and so the form group doesn't link to the DOMs anymore.
Is there any way to re-bind FormGroup mainForm to the DOMs after jquery-steps reconstructed the html?
I read about ComponentResolver and ViewContainerRef, is it where it should use those? Could you give me an example how to use those in this situation?
Thank you!
<pre>{{ mainForm.value | json }}</pre>
<form [formGroup]="mainForm" id="mainForm" action="#">
<h1>Account</h1>
<div>
<label for="userName">User name *</label>
<input formControlName="userName" type="text" class="required">
<label for="password">Password *</label>
<input formControlName="password" type="text" class="required">
<label for="confirm">Confirm Password *</label>
<input formControlName="confirm" type="text" class="required">
<p>(*) Mandatory</p>
</div>
<h1>Finish</h1>
< div>
<input formControlName="acceptTerms" type="checkbox" class="required">
<label for="acceptTerms">I agree with the Terms and Conditions.</label>
</div>
</form>
import {Component, AfterContentInit} from "#angular/core";
import {FormBuilder, Validators, FormGroup} from "#angular/forms";
#Component({
templateUrl: 'main-view.template.html'
})
export class MainViewComponent implements AfterContentInit {
private mainForm: FormGroup;
constructor(private formBuilder: FormBuilder) {
this.mainForm = this.formBuilder.group({
userName: ['', Validators.required],
password: ['', Validators.required],
confirm: ['', Validators.required],
acceptTerms: ['', Validators.required],
});
}
ngAfterContentInit() {
$("#mainForm").steps();
}
}
The main reason why that is not working is that jquery-steps plugin removes your html markup.
Using jquery in angular2 is bad idea but if you want to get it working i can offer you slightly modify the library
jquery.steps.js
function render(wizard, options, state) {
+ var contentWrapper = $('<{0} class=\"{1}\"></{0}>'.format(options.contentContainerTag, "content " + options.clearFixCssClass));
+ contentWrapper.append(wizard.children());
// Create a content wrapper and copy HTML from the intial wizard structure
var wrapperTemplate = "<{0} class=\"{1}\">{2}</{0}>",
orientation = getValidEnumValue(stepsOrientation, options.stepsOrientation),
verticalCssClass = (orientation === stepsOrientation.vertical) ? " vertical" : "",
- //contentWrapper = $(wrapperTemplate.format(options.contentContainerTag, "content " + options.clearFixCssClass, wizard.html())),
See also Plunker Example

Angular 2 get input validation status

I'm creating an input text component with angular2. I need to add a class at this control if is valid and if it's required. This is the component:
import { Component, Input } from "#angular/core";
import { NgForm } from '#angular/forms';
#Component({
selector: "input-control",
template: `
<div [class.has-success]="required" class="form-group form-md-line-input form-md-floating-label">
<input [class.edited]="model[property]"
[(ngModel)]="model[property]"
[attr.ngControl]="property"
[name]="property"
type="text"
class="form-control"
id="{{property}}"
value=""
[attr.required]="required">
<label [attr.for]="property">{{label}}</label>
<span class="help-block">{{description}}</span>
</div>
`
})
export class InputControlComponent {
#Input()
model: any;
#Input()
property: string;
#Input()
label: string;
#Input()
description: string;
#Input()
required: boolean;
#Input()
form: NgForm;
}
In the first row of the template I set the "has-success" class if the input is required but I need to set it if it's valid too. Somethig like this:
[class.has-success]="required && form.controls[property].valid"
The html is this:
<form role="form" *ngIf="active" (ngSubmit)="onSubmit(databaseForm)" #databaseForm="ngForm">
<div class="form-body">
<div class="row">
<div class="col-md-6">
<input-control [model]="model" [property]="'code'" [form]="databaseForm" [label]="'#Localizer["Code"]'" [description]="'#Localizer["InsertCode"]'" [required]="true"></input-control>
</div>
<div class="col-md-6">
<input-control [model]="model" [property]="'description'" [form]="databaseForm" [label]="'#Localizer["Description"]'" [description]="'#Localizer["InsertDescription"]'"></input-control>
</div>
</div>
</div>
</form>
I think that you can't use the template-driven form a sub-component and make it be part of a form of the parent component without implementing a custom value accessor with Angular2 prior to version RC2.
See this question:
Angular 2 custom form input
With version RC2+, I think that it's possible out of the box like this:
<form #databaseForm="ngForm">
<input-control name="code" [ngModelOptions]="{name: 'code'}"
[(ngModel)]="model.code"/>
</form>

Categories

Resources