Form validation is not working in angular? - javascript

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>

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.

How to bind to an Angular form from users selected option

OK it's a bit more complicated than the headline..
This form I am working on is a form group. It has a few fields ( supplement name, description and tags) the supplement name one is what I need help with as I have not worked on a complicated form like this and want to get it right and not just offer a messy patch job.
Here is the expected logical order of what happens
user adds a new supplement by clicking on the field and begins typing "creatine" for example
there is a query sent out that fetches products based on the entry into the input and
returns a JSON that are offered as suggestions
user clicks the suggestion "creatine"
field is populated and binds
we add another entry through the "add option" and repeat for X amount of products we want to
add.
What actually happens
user adds new supplement by clicking the field and types "creatine" suggestion request is
sent off and populates the dropdown
user clicks on the suggestion "creatine" the field takes that value
value is actually blank
user adds another supplement but the previous selection is in the field
user clears it and types again
value is blank
What needs to happen is the user can add X amount of supplements and able to grab whatever option from the dropdown recommendation and it is added to the form group array and does not interfere with the other form group array values.
I know this is not the right way to bind the form and I don't think it's right the way i'm binding the mat input field to trigger the query and this is the reason why I'm asking the question again, to not offer a patch job.
Component code
import { Subscription } from 'rxjs/Subscription';
import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '#angular/forms';
import { UtilitiesService } from '../../utilities/utilities.service';
import { GetSupplementsService } from './get-supplements.service';
#Component({
selector: 'app-supplements',
templateUrl: './supplements.component.html',
styleUrls: ['./supplements.component.css'],
providers: [GetSupplementsService],
})
export class SupplementsComponent implements OnInit {
supplementForm: FormGroup;
queryField: FormControl = new FormControl();
private supplementInventoryResults: Array<ISupplementInventoryResponse>;
private eventForm: FormGroup;
private searchResults: any;
private searchSubscription: Subscription;
private addSupplementSubscription: Subscription;
subcription: Subscription;
constructor (
private bottomSheet: MatBottomSheet,
private _fb: FormBuilder,
private ref: ChangeDetectorRef,
private _utils: UtilitiesService,
private getSupplements: GetSupplementsService,
private router: Router
) { }
public ngOnInit(): void {
this.browsingStackHistory = false;
this.loading = true;
this.supplementForm = this._fb.group({ // the form in question
entryArray: this._fb.array([
this.getUnit()
])
});
this.searchSubscription =
this.queryField.valueChanges
.debounceTime(600)
.distinctUntilChanged()
.switchMap((query) => this.getSupplements.search_supplement_by_category(query))
.subscribe((result) => {
this.searchResults = result;
});
}
public ngOnDestroy(): void {
this.subcription.unsubscribe();
}
private getUnit(): FormGroup {
return this._fb.group({
supplementName: [''],
description: [''],
tags: ['']
});
}
private addUnit(): void {
const control = <FormArray>this.supplementForm.controls['entryArray'];
control.push(this.getUnit());
}
private removeUnit(i: number): void {
const control = <FormArray>this.supplementForm.controls['entryArray'];
control.removeAt(i);
}
private addSupplement(): void { // this will do the post to the service
const supplementObject = {
start: this._utils.get_upload_time(),
data: this.supplementForm.value,
rating: 0
};
}
}
Template
[![<mat-tab label="Add Stack (Test)">
<div style="padding:8px;">
<div fxLayout="row wrap">
<div fxFlex.gt-sm="50%" fxFlex="100">
<h1>Add New Supplements Stack</h1>
<form \[formGroup\]="supplementForm" class="log-workout">
<!-- Start form units array with first row must and dynamically add more -->
<div fxLayout="column" fxLayoutAlign="center center" class="row-height">
<div formArrayName="entryArray">
<mat-divider></mat-divider>
<!-- loop throught units -->
<div *ngFor="let reps of supplementForm.controls.entryArray.controls; let i=index">
<!-- row divider show for every nex row exclude if first row -->
<mat-divider *ngIf="supplementForm.controls.entryArray.controls.length > 1 && i > 0"></mat-divider>
<br>
<!-- group name in this case row index -->
<div \[formGroupName\]="i">
<!-- unit name input field -->
<div class="row">
<mat-form-field class="example-form">
<input matInput placeholder="Supplement Name" \[formControl\]="addSupplementField"
formControlName="supplementName" \[matAutocomplete\]="auto">
<mat-autocomplete #auto="matAutocomplete">
<mat-option *ngFor="let product of supplementResults" \[value\]="product?.product_name">
<img class="example-option-img" aria-hidden \[src\]="product?.product_image" height="25">
{{product?.product_name}}
</mat-option>
</mat-autocomplete>
</mat-form-field>
<mat-form-field class="example-form">
<input matInput placeholder="Description" formControlName="description" required>
</mat-form-field>
<mat-form-field class="example-form">
<input matInput placeholder="Tags" formControlName="tags" required>
</mat-form-field>
</div>
<!-- row delete button, hidden if there is just one row -->
<button mat-mini-fab color="warn" *ngIf="supplementForm.controls.entryArray.controls.length > 1"
(click)="removeUnit(i)">
<mat-icon>delete forever</mat-icon>
</button>
</div>
</div>
<!-- New unit button -->
<mat-divider></mat-divider>
<mat-card-actions>
<button mat-raised-button (click)="addUnit()">
<mat-icon>add box</mat-icon>
Add Other Product
</button>
</mat-card-actions>
<button mat-raised-button (click)="addSupplement()">
<mat-icon>add box</mat-icon>
Add Supplement
</button>
</div>
</div>
<!-- End form units array -->
</form>
</div>
</div>
</div>][1]][1]
Having the below when the getUnit() function is called apparently binds it in the sense it will operate independently and without conflicts.
private getUnit(): FormGroup {
const formGroup = this._fb.group({
supplementName: [''],
review: [''],
rating: [''],
notes: [''],
tags: ['']
});
formGroup.get('supplementName').valueChanges
.debounceTime(300)
.distinctUntilChanged()
.switchMap((search) => this.getSupplements.search_supplement_by_category(search))
.subscribe((products) => {
this.supplementResults = products;
});
return formGroup;
}

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>

How can i pass the id while editing in angular?

I want to pass the id when only user edit the document .here is my html and component.ts
Html
<h1 mat-dialog-title>Hi {{data.name}}</h1>
<form [formGroup]="addTaskForm" (ngSubmit)="save()" >
<mat-form-field>
<mat-select formControlName="name" placeholder="Element Name">
<mat-option *ngFor="let element of Elements" [value]="element.name">
{{ element.name }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field>
<mat-select formControlName="symbol" placeholder="Element symbol">
<mat-option *ngFor="let element of Elements" [value]="element.symbol">
{{ element.symbol }}
</mat-option>
</mat-select>
</mat-form-field>
<div mat-dialog-actions>
<button type="button" mat-button (click)="onNoClick()">Cancel</button>
<button type="submit" mat-button cdkFocusInitial>Add</button>
</div>
</form>
component.ts
export class DialogOverviewExampleDialog {
Elements = ELEMENT_DATA;
addTaskForm: FormGroup;
symbol = new FormControl('', Validators.required);
name = new FormControl('', Validators.required);
id = new FormControl('', Validators.required);
constructor(
public dialogRef: MatDialogRef<DialogOverviewExampleDialog>,
#Inject(MAT_DIALOG_DATA) public data: any,
private formBuilder: FormBuilder) {
if(data.element){
console.log(data.element.name);
this.name = data.element.name;
this.symbol = data.element.symbol;
this.id = data.element.id;
}
this.addTaskForm = this.formBuilder.group({
name: this.name,
symbol: this.symbol,
id: this.id
});
}
onNoClick(): void {
this.dialogRef.close();
}
save(Element) {
console.log('working');
console.log(this.addTaskForm.value);
}
}
when user click add popup will open with two dropdowns and add a button that time I don't want id but when user click edit button popup window will open with particular row details that time if user click add button I should get the particular id , name, symbol in component
demo
Just set the id: this.id while you are adding
this.addTaskForm = this.formBuilder.group({
name: this.name,
symbol: this.symbol,
id: this.id
});
also pass the id with your modal,
if(data.element){
console.log(data.element.name);
this.name = data.element.name;
this.symbol = data.element.symbol;
this.id = data.element.id;
}
DEMO

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