How to disable mat-autocomplete on formBuilder? - javascript

I am trying to create a form in which once an option is selected in the first field, this selection gives the options of the second field, which is a mat-autocomplete, also when value from the mat-autocomplete is selected, it is filled with mat-chips.
The problem is that I am not able to make the second field disabled until the first field is filled.
I have tried both different ways, like the indicated by the console warning:
"It looks like you're using the disabled attribute with a reactive form directive. If you set disabled to true
when you set up this control in your component class, the disabled attribute will actually be set in the DOM for
you. We recommend using this approach to avoid 'changed after checked' errors.
Example:
// Specify the `disabled` property at control creation time:
form = new FormGroup({
first: new FormControl({value: 'Nancy', disabled: true}, Validators.required),
last: new FormControl('Drew', Validators.required)
});
// Controls can also be enabled/disabled after creation:
form.get('first')?.enable();
form.get('last')?.disable();"
I tried the response of this question too:
Angular mat-autocomplete disable input FormControl not working
But in the end I had to do it like this (that give me the warning):
HTML
<div class="modal-descriptors" [formGroup]="filterValueForm">
<mat-form-field appearance="outline" class="form-field">
<mat-label>tag</mat-label>
<mat-select (selectionChange)="onFilterChange($event)" formControlName="filterSelectCtrl" required>
<mat-option *ngFor="let filter of filters" [value]="filter.code">{{ filter.code }}</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline" class="modal-descriptors">
<mat-label>condition</mat-label>
<mat-select formControlName="filterConditionCtrl" required>
<mat-option value="true">{{ apply$ | async }}</mat-option>
<mat-option value="false">{{ notApply$ | async }}</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline" class="descriptors">
<mat-label>{{ values }}</mat-label>
<mat-chip-list #toChipList required>
<mat-chip
class="descriptors_label"
*ngFor="let filterValue of selectedFiltersValue"
[selectable]="selectable"
[removable]="removable"
(removed)="removeSelectedFilterValue(filterValue)">
{{ filterValue.value }}
<mat-icon class="iconicon" matChipRemove *ngIf="removable">close</mat-icon>
</mat-chip>
<input type="text"
matInput
#filterValueInput
formControlName="filterValueCtrl"
[matAutocomplete]="autoTo"
[matChipInputFor]="toChipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[matChipInputAddOnBlur]="addOnBlur"
(matChipInputTokenEnd)="add($event)"
[disabled] = "isFilterValueSelected"
>
</mat-chip-list>
<mat-autocomplete #autoTo="matAutocomplete" (optionSelected)="selected($event)">
<mat-option *ngFor="let filterValue of filteredValues" [value]="filterValue">{{ filterValue.value }}</mat-option>
</mat-autocomplete>
</mat-form-field>
TS
ngOnInit() {
this.filterValueForm = this.fb.group({
filterSelectCtrl: [null, Validators.required],
filterConditionCtrl: [null, Validators.required],
filterValueCtrl: [{value: '', disabled: true}, Validators.required], /*this object inside array do nothing*/
})
};
get isFilterValueSelected() {
return !this.filterValueForm.get('filterSelectCtrl').value ? true : false;
}
Does anyone know what is wrong or knows a better way to do it?

Disable the second field on the formGroup declaration.
this.filterValueForm = this.fb.group({
firstField: ['', Validators.required],
secondField: [{value: '', disabled: true}, Validators.required],
})
Then, when you select an option from the autocomplete, you are calling the selected($event) function from your ts. In this function, you can enable the second field like this:
selected() {
\*Do whatever logic you want to do when an autocomplete element is selected*\
this.filterValueForm.controls['secondField'].enable();
}
Edit:
You are overriding the ts configuration on the html with the line
<input type="text"
matInput
#filterValueInput
formControlName="filterValueCtrl"
[matAutocomplete]="autoTo"
[matChipInputFor]="toChipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[matChipInputAddOnBlur]="addOnBlur"
(matChipInputTokenEnd)="add($event)"
[disabled] = "isFilterValueSelected" <---
>

I found a solution.
Dont know why but when creating the formGroup like this dont work:
filterValueCtrl: [{value: '', disabled: true}, Validators.required]
And after try some other solutions I found that one:
this.filterValueForm.controls.filterValueCtrl.disable();
But dont know why dont work if its used after create the formGroup that its inside ngOnInit()
So I made method that its triggered when the mouse is over the <mat-form-field>
TS:
enableOrDisableField(){
this.filterValueForm.get('filterSelectCtrl').value ? this.filterValueForm.controls.filterValueCtrl.enable() : this.filterValueForm.controls.filterValueCtrl.disable();
}
HTML:
<mat-form-field appearance="outline" (mouseenter)="enableOrDisableField()">
...
...
<mat-form-field>
Maybe its not the best solution but its working and no warning at console...so I think its fine for me.
Hope can help someone, and tanks to all who have tryed to help.

Related

Angular forms require one of two form groups to be valid

I am trying to implement a reactive angular form where either A or B has to be entered. A is a unique id and B is a set of values which identify the id. Now I try to validate a Form that is valid if either A is entered or B is entered including all the required values. I found several solutions that implement this behavior based on FormFields but was not able to get it working with the group of values.
<form class="container" [formGroup]="myForm" (ngSubmit)="onSubmit()">
<mat-form-field class="w-1/2">
<mat-label>ID</mat-label>
<input matInput type="number" formControlName="id">
</mat-form-field>
<div class="grid grid-cols-3 gap-4" formGroupName="innerGroup">
<mat-form-field>
<mat-label>First Name</mat-label>
<input matInput type="number" formControlName="firstName">
</mat-form-field>
<mat-form-field>
<mat-label>Last Name</mat-label>
<input matInput type="number" formControlName="lastName">
</mat-form-field>
</div>
</form>
My first idea was to override the default validator for the form but I could not figure out how to do that. Not even sure if it would be possible. I was trying to adjust https://stackoverflow.com/a/48714721 to work in my scenario but I had no idea how to get it to work because of the additional complexity with the inner form group.
Using angular 14 I was able to produce a similar result to what you are describing I am not sure it will 100% solve your issue however it might help.
Basically what I did was create a validator function that is to be applied at the group level. This validator will check the valid state of any given controls be they a FormGroup or a FormControl. However, this alone will not solve the problem as if you have a form group angular will see that any underling control or group that is invalid will also invalidate the parent. So what I did was call .disable() on any control that was being checked by the validator function. This will disable the UI element and disable validation checking by angular allowing the parent to be considered valid when one of the children is valid but the other is invalid effectively creating a one and only one validator.
My specific example I was trying to get the OnlyOneValidator to work for a MatStepper.
Validator
export function onlyOneValidator(controlKeys: string[]) {
return (control: AbstractControl): ValidationErrors | null => {
let countOfValidControls = 0;
for (let key of controlKeys) {
const controlToCheck = control.get(key);
if (controlToCheck === null || controlToCheck === undefined) {
throw new Error(`Error: Invalid control key specified key was ${key}`);
}
countOfValidControls += controlToCheck?.valid ? 1 : 0;
}
if (countOfValidControls !== 1) {
// the count is not exactly one
return {
onlyOneValid: {
actualValidCount: countOfValidControls,
expectedValidCount: 1
}
};
}
return null;
};
}
Controller
#Component({
selector: "app-equipment-creation-page",
templateUrl: "./equipment-creation-page.component.html",
styleUrls: ["./equipment-creation-page.component.scss"],
})
export class EquipmentCreationPageComponent implements OnInit, OnDestroy {
public categories = [null, "Tools", "Vehicles"];
constructor(private _formBuilder: FormBuilder) {}
public categoryInformationGroup = this._formBuilder.group({
existingCategory: this._formBuilder.group({
category: new FormControl(null, [ Validators.required ])
}),
newCategory: this._formBuilder.group({
name: new FormControl("", [Validators.required]),
description: new FormControl("", [Validators.required])
})
}, {
validators: [
onlyOneValidator(["existingCategory", "newCategory"])
],
});
public ngOnDestroy(): void {
this.subscriptions.forEach(sub => {
sub.unsubscribe();
});
}
private subscriptions: Subscription[] = [];
public ngOnInit(): void {
this.subscriptions.push(this.categoryInformationGroup.controls.existingCategory.statusChanges.pipe(
tap((status: string) => {
if (status === "VALID") {
this.categoryInformationGroup.controls.newCategory.disable();
} else {
this.categoryInformationGroup.controls.newCategory.enable();
}
})
).subscribe());
this.subscriptions.push(this.categoryInformationGroup.controls.newCategory.statusChanges.pipe(
tap((status: string) => {
if (status === "VALID") {
this.categoryInformationGroup.controls.existingCategory.disable();
} else {
this.categoryInformationGroup.controls.existingCategory.enable();
}
})
).subscribe());
}
}
Template
<form [formGroup]="categoryInformationGroup.controls.existingCategory">
<mat-form-field>
<mat-label>Apply to existing category?</mat-label>
<mat-select formControlName="category">
<mat-option *ngFor="let category of categories" [value]="category">
{{ category ?? "None" }}
</mat-option>
</mat-select>
</mat-form-field>
</form>
OR
<form [formGroup]="categoryInformationGroup.controls.newCategory">
<mat-form-field>
<mat-label>Create New Category</mat-label>
<input matInput formControlName="name" placeholder="Name">
<mat-error *ngIf="categoryInformationGroup.controls.newCategory.controls.name.hasError('required')">This field
is required
</mat-error>
</mat-form-field>
<mat-form-field>
<mat-label>Create New Category</mat-label>
<input matInput formControlName="description" placeholder="Description">
<mat-error *ngIf="categoryInformationGroup.controls.newCategory.controls.description.hasError('required')">
This field is required
</mat-error>
</mat-form-field>
</form>
Hopefully this helps or at least gives you some ideas about how to approach this. If anyone else has any thoughts on this please let me know I would love to find a better way to do this.

Angular MaterializeCSS FormArray Selector

I'm having problems with a form that working on.
I'm using MaterializeCSS with Angular and in order to make sure the select will be properly initiated i'm using the following method:
ngAfterViewInit() {
const selArray = this.select.toArray();
selArray.forEach(el => {
M.FormSelect.init(el.nativeElement);
});
}
That's working fine, my problem is that I'm using FormArray and when I create a new FormControl dynamically, the select doesn't work. It's like it's not initialized.
I added the code above inside my addResident() method and it doesn't work but if I have to add again, then it works.
Here is video of the error:
https://youtu.be/v1CHkmJtzCo
Here is the code:
#ViewChildren('select') select: QueryList<ElementRef>;
ngAfterViewInit() {
const selArray = this.select.toArray();
selArray.forEach(el => {
M.FormSelect.init(el.nativeElement);
});
const dpArray = this.datePicker.toArray();
dpArray.forEach(element => {
M.Datepicker.init(element.nativeElement);
});
}
addResident() {
(this.hostForm.get('residents') as FormArray).push(
new FormGroup({
firstName: new FormControl(''),
lastName: new FormControl(''),
birthDate: new FormControl(''),
action: new FormControl('Insert'),
residentID: new FormControl(0),
relationship: new FormControl('')
})
);
const selArray = this.select.toArray();
selArray.forEach(el => {
M.FormSelect.init(el.nativeElement);
});
}
I would suggest to use Angular Material to avoid problems with MaterializeCSS.
https://material.angular.io/
Material Design components for Angular
Angular Material: Forms
Angular Material: Form-Field
Have a look at the Form-Field examples:
<div class="example-container">
<mat-form-field>
<input matInput placeholder="Input">
</mat-form-field>
<mat-form-field>
<textarea matInput placeholder="Textarea"></textarea>
</mat-form-field>
<mat-form-field>
<mat-select placeholder="Select">
<mat-option value="option">Option</mat-option>
</mat-select>
</mat-form-field>
</div>

Password Confirm Angular Material

I'm working to authenticate a user with Angular Material. I'm currently trying to get the proper mat-error to display when the confirmation password doesn't match the first entered pw.
Here is my html:
<mat-form-field hintLabel="Minimum 8 Characters" class="">
<mat-label>Password</mat-label>
<input
matInput
#input
type="password"
formControlName="password">
<mat-hint align="end">{{input.value?.length || 0}}/8</mat-hint>
</mat-form-field>
<mat-form-field>
<mat-label>Confirm</mat-label>
<input
matInput
required
type="password"
#confirm
formControlName="confirmPassword">
<mat-error *ngIf="form.get('confirmPassword').invalid || confirmPasswordMismatch">Password does not match</mat-error>
</mat-form-field>
The error displays once the user has focused on password confirmation and unfocuses without entering anything. Once the user enters anything, the error goes away even though the confirmation doesn't match the password.
Here is my TS file:
public get confirmPasswordMismatch() {
return (this.form.get('password').dirty || this.form.get('confirmPassword').dirty) && this.form.hasError('confirmedDoesNotMatch');
}
this.form = new FormGroup({
userName: new FormControl(null, [Validators.required]),
fullName: new FormControl(null, [Validators.required]),
email: new FormControl(null, [Validators.required, Validators.pattern(this.EMAIL_REGEX)]),
password: new FormControl(null),
confirmPassword: new FormControl(null, ),
}, (form: FormGroup) => passwordValidator.validate(form));
The desired effect is that the error shows when the user has entered text into pw input when confirm pw is empty and to show an error when both have text but confirm doesn't match pw.
I solved it like this:
Template:
<mat-form-field>
<input matInput type="password" placeholder="Password" formControlName="password" (input)="onPasswordInput()">
<mat-error *ngIf="password.hasError('required')">Password is required</mat-error>
<mat-error *ngIf="password.hasError('minlength')">Password must have at least {{minPw}} characters</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput type="password" placeholder="Confirm password" formControlName="password2" (input)="onPasswordInput()">
<mat-error *ngIf="password2.hasError('required')">Please confirm your password</mat-error>
<mat-error *ngIf="password2.invalid && !password2.hasError('required')">Passwords don't match</mat-error>
</mat-form-field>
Component:
export class SignUpFormComponent implements OnInit {
minPw = 8;
formGroup: FormGroup;
constructor(private formBuilder: FormBuilder) { }
ngOnInit() {
this.formGroup = this.formBuilder.group({
password: ['', [Validators.required, Validators.minLength(this.minPw)]],
password2: ['', [Validators.required]]
}, {validator: passwordMatchValidator});
}
/* Shorthands for form controls (used from within template) */
get password() { return this.formGroup.get('password'); }
get password2() { return this.formGroup.get('password2'); }
/* Called on each input in either password field */
onPasswordInput() {
if (this.formGroup.hasError('passwordMismatch'))
this.password2.setErrors([{'passwordMismatch': true}]);
else
this.password2.setErrors(null);
}
}
Validator:
export const passwordMatchValidator: ValidatorFn = (formGroup: FormGroup): ValidationErrors | null => {
if (formGroup.get('password').value === formGroup.get('password2').value)
return null;
else
return {passwordMismatch: true};
};
Notes:
Thanks to onPasswordInput() being called from either password field, editing the first password field (and thus invalidating the password confirmation) also causes the mismatch error to be displayed in the password confirmation field.
The *ngIf="password2.invalid && !password2.hasError('required')" test for the password confirmation field ensures that never both error messages ("mismatch" and "required") are displayed at the same time.
Based on your code, it doesn't look like you added any validation for the confirmPassword field: confirmPassword: new FormControl(null, ) so the only validation happening is via the required attribute. Also, the mat-error will only be displayed by the form field if the form control is invalid. That means you can't force an error to be displayed just by using ngIf. Since you only have required validation on that field, it makes sense that you only have an error when the field is empty. To solve this problem, you need to create a validator for mismatch checking and add it to the confirmPassword form control. As an alternative, you can manually add errors to the form control using setErrors() when the field changes by adding an input listener - for example (this is just from memory - may need fixing):
<mat-form-field>
<mat-label>Confirm</mat-label>
<input matInput required type="password" #confirm formControlName="confirmPassword"
(input)="onInput($event.target.value)">
<mat-error *ngIf="form.get('confirmPassword').invalid>
Password does not match
</mat-error>
</mat-form-field>
onInput(value) {
if (this.form.hasError('confirmedDoesNotMatch')) { // or some other test of the value
this.form.get('confirmPassword').setErrors([{'confirmedDoesNotMatch': true}]);
} else {
this.form.get('confirmPassword').setErrors(null);
}
}
you are essentially validating how 2 fields in a form interact with each other ("password" and "confirm password" fields). This is known as "cross-field validation"
that means, your custom validator cannot be assigned to just 1 field. The custom validator needs to be assigned to the common parent, the form.
Here is the official documented best practice, for validating how 2 fields in a form interact with each other
https://angular.io/guide/form-validation#cross-field-validation
this code snippet worked for me
template:
<form method="post" [formGroup]="newPasswordForm">
<input type="password" formControlName="newPassword" />
<input type="password" formControlName="newPasswordConfirm" />
<div class="err-msg" *ngIf="newPasswordForm.errors?.passwordMismatch && (newPasswordForm.touched || newPasswordForm.dirty)">
confirm-password does not match password
</div>
</form>
component.ts:
export class Component implements OnInit {
this.newPasswordForm = new FormGroup({
'newPassword': new FormControl('', [
Validators.required,
]),
'newPasswordConfirm': new FormControl('', [
Validators.required
])
}, { validators: passwordMatchValidator });
}
export const passwordMatchValidator: ValidatorFn = (formGroup: FormGroup): ValidationErrors | null => {
return formGroup.get('newPassword').value === formGroup.get('newPasswordConfirm').value ?
null : { 'passwordMismatch': true };
}
Note that for passwordMatchValidator, it is outside the component class. It is NOT inside the class
Henry's answer was very helpful (and I'm surprised it doesn't have more votes), but I've made a tweak to it so that it plays well with Angular Material controls.
The tweak relies on the fact that the FormGroup class has a parent property. Using this property, you can tie the validation message to the password confirmation field, and then refer up the chain in the validator.
public signUpFormGroup: FormGroup = this.formBuilder.group({
email: ['', [Validators.required, Validators.pattern(validation.patterns.email)]],
newPassword: this.formBuilder.group({
password: ['', [
Validators.required,
Validators.minLength(validation.passwordLength.min),
Validators.maxLength(validation.passwordLength.max)]],
confirmPassword: ['', [Validators.required, passwordMatchValidator]]
})
});
The validator looks like this:
export const passwordMatchValidator: ValidatorFn = (formGroup: FormGroup): ValidationErrors | null => {
const parent = formGroup.parent as FormGroup;
if (!parent) return null;
return parent.get('password').value === parent.get('confirmPassword').value ?
null : { 'mismatch': true };
}
and the form then looks like this:
<div formGroupName="newPassword" class="full-width new-password">
<mat-form-field class="full-width sign-up-password">
<mat-label>{{ 'sign-up.password' | translate }}</mat-label>
<input matInput [type]="toggleSignUpPass.type" [maxlength]="max" [minlength]="min" formControlName="password" required/>
<mat-pass-toggle-visibility #toggleSignUpPass matSuffix></mat-pass-toggle-visibility>
<mat-icon matSuffix [color]="color$ | async">lock</mat-icon>
<mat-hint aria-live="polite">{{ signUpFormGroup.get('newPassword').value.password.length }}
/ {{ max }} </mat-hint>
<mat-error *ngIf="signUpFormGroup?.get('newPassword')?.controls?.password?.hasError('required')">
{{ 'sign-up.password-error.required' | translate }}
</mat-error>
<mat-error class="password-too-short" *ngIf="signUpFormGroup?.get('newPassword')?.controls?.password?.hasError('minlength')">
{{ 'sign-up.password-error.min-length' | translate:passwordRestriction.minLength }}
</mat-error>
<mat-error *ngIf="signUpFormGroup?.get('newPassword')?.controls?.password?.hasError('maxlength')">
{{ 'sign-up.password-error.max-length' | translate:passwordRestriction.maxLength }}
</mat-error>
</mat-form-field>
<mat-form-field class="full-width sign-up-confirm-password">
<mat-label>{{ 'sign-up.confirm-password' | translate }}</mat-label>
<input matInput [type]="toggleSignUpPassConf.type" formControlName="confirmPassword" required />
<mat-pass-toggle-visibility #toggleSignUpPassConf matSuffix></mat-pass-toggle-visibility>
<mat-icon matSuffix [color]="color$ | async">lock</mat-icon>
<mat-error *ngIf="signUpFormGroup?.get('newPassword')?.controls?.confirmPassword?.hasError('required')">
{{ 'sign-up.confirm-password-error.required' | translate }}
</mat-error>
<mat-error class="password-mismatch" *ngIf="signUpFormGroup?.get('newPassword')?.controls?.confirmPassword?.hasError('mismatch')">
{{ 'sign-up.password-error.mismatch' | translate }}
</mat-error>
</mat-form-field>
</div>

Form validation is not working in angular?

I want to check whether the dropdown is empty.
Need to show the required message and
If not empty, enable the submit button.
If empty, disable the submit button. Below is my html
Below is my html
<form [formGroup]="myForm" (ngSubmit)="save()" >
<mat-form-field>
<mat-select formControlName="name" placeholder="Element List" (selectionChange)="elementSelectionChange($event)" required>
<mat-option *ngFor="let element of Elements" [value]="element.name">
{{ element.name }}
</mat-option>
</mat-select>
<mat-error *ngIf="myForm.hasError('required', 'name')">Please choose an name</mat-error>
</mat-form-field>
<mat-form-field>
<mat-select formControlName="symbol" placeholder="Symbol List" required>
<mat-option *ngFor="let element of selectedElementSymbol" [value]="element.symbol">
{{ element.symbol }}
</mat-option>
</mat-select>
<mat-error *ngIf="myForm.hasError('required', 'symbol')">Please choose an symbol</mat-error>
</mat-form-field>
<div mat-dialog-actions>
<button mat-button (click)="onNoClick()">Cancel</button>
<button type="submit" mat-button cdkFocusInitial>Add</button>
</div>
</form>
below is my component
export class DialogOverviewExampleDialog {
myForm: FormGroup;
symbol = new FormControl('', Validators.required);
name = new FormControl('', Validators.required);
constructor(
public dialogRef: MatDialogRef<DialogOverviewExampleDialog>,
#Inject(MAT_DIALOG_DATA) public data: any,
private formBuilder: FormBuilder) {
this.myForm = this.formBuilder.group({
name: [this.name],
symbol: [this.symbol],
});
}
save() {
console.log(this.myForm.value);
}
}
updated demo here
You are currently assigning formcontrols to your formcontrol, whereas you want to assign value to your form controls. Below you are assigning form control name to formcontrol name:
WRONG:
name = new FormControl('', Validators.required);
this.myForm = this.formBuilder.group({
'name': [this.name, Validators.required],
// ...
});
so instead, just declare name, do what you want with the value, then assign that value to your form control...
CORRECT:
name: string;
this.myForm = this.formBuilder.group({
'name': [this.name, Validators.required],
// ...
});
Then just add [disabled]="!myForm.valid" on your submit button.
As for the other question, by default Angular material doesn't show error message unless the field has been touched, so you need to have a custom error state matcher that shows the error even when field is not touched (if that is what you want):
import {ErrorStateMatcher} from '#angular/material/core';
export class MyErrorStateMatcher implements ErrorStateMatcher {
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
return !!(control.invalid);
}
}
and in your TS, declare a error state matcher:
matcher = new MyErrorStateMatcher();
and use in template:
<mat-select formControlName="name" ... [errorStateMatcher]="matcher">
Here's your
StackBlitz
To make the submit button disabled (link)
<button type="submit" [disabled]="!myForm.valid" mat-button cdkFocusInitial>Add</button>
In order to check whether the dropdown is empty or not, you need to make the form fields required like
this.myForm = this.formBuilder.group({
'name': [this.name, Validators.required],
'symbol': [this.symbol, [Validators.required]]
});
Inorder to show the error highlight you need to add an ngClass in the templete like.
[ngClass]="{'error': myForm.controls.name.valid == false}"
You have to insert the validator into the form builder object.
Have a quick look at:
https://angular.io/guide/reactive-forms#validatorsrequired
this.heroForm = this.fb.group({
name: ['', [Validators.required] ],
});
As for the button:
<button type="submit" [disabled]="!form.valid" mat-button cdkFocusInitial>Add</button>

Add inputs dynamically when click on button in angular 4

I would create a form with the possibility to add inputs dynamically
I found a question about the same problem in angular 2 but I can't make it working in my exemple
Here's my component ts file :
export class AjoutProjetComponent implements OnInit {
isLinear = false;
firstFormGroup: FormGroup;
secondFormGroup: FormGroup;
constructor(private _formBuilder: FormBuilder) {}
ngOnInit() {
this.secondFormGroup = this._formBuilder.group({
pers: [this._formBuilder.array([this.createItem()])]
});
}
createItem(): FormGroup {
return this._formBuilder.group({
name: ['', Validators.required]
poste: ['', Validators.required],
});
}
addItem(): void {
const control = < FormArray > this.secondFormGroup.controls['pers'];
control.push(this.createItem());
}
}
then HTML file
<mat-step [stepControl]="secondFormGroup">
<form [formGroup]="secondFormGroup">
<ng-template matStepLabel>Constituez votre équipe</ng-template>
<div formArrayName="pers">
<mat-form-field *ngFor="let control of secondFormGroup.controls.pers.controls; let i= index">
<input matInput placeholder="Nom collaborateur" formControlName="name" required>
</mat-form-field>
</div>
</form>
</mat-step>
<div>{{secondFormGroup.value | json}}</div>
When I click in my favorite icon I get this error :
ERROR TypeError: control.push is not a function at AjoutProjetComponent.addItem
How can I make adding dynamically inputs working ?
UPDATE
I have updated my html code so that I could print two inputs but when I run my code I get this error now
ERROR Error: Cannot find control with path: 'pers -> name'
You did not declare your FormArray properly. You use arrays only to initialize simple FormControls, not FormGroups or FormControls, change to :
this.secondFormGroup = this._formBuilder.group({
pers: this._formBuilder.array([this.createItem()]) // remove opening and closing brackets
});
To see the inputs added dynamically to the html, you need to use an ngFor loop. I think you somewhat misunderstood the usage of formArrayName, which only adds context to the template to use with FormArrays. Try this:
<ng-container formArrayName="pers">
<input placeholder="Address"
*ngFor="let control of secondFormGroup.controls.pers.controls"
[formControl]="control.controls.name" required />
</ng-container>
And read more about FormArrayName directive here

Categories

Resources