Reset Angular Formly Form Validations on click event - angular6

I am implementing dynamic forms using the angular formly module and It's working fine. What functionality I need is Initially there should be a select box which contains multiple options, Based on selection different form fields should be displayed. As I explained I have Implemented and it's working, here what my problem is If I select option 1 and If I submit the form without filling fields, form displaying validation errors that is also cool. But when I select option 2 form fields are changing, but by default, all required fields are showing errors. How can I resist this? Please suggest me.
html
<div class="row">
<mat-form-field class="col-lg-2">
<mat-select placeholder="Form For" (selectionChange)="getSelectedFormName($event)">
<mat-option value="uf001">UF001</mat-option>
<mat-option value="uf002">UF002</mat-option>
<mat-option value="uf003">UF003</mat-option>
</mat-select>
</mat-form-field>
<div class="col-lg-4">
<button type="button" class="btn btn-default btn-one" (click)="getDynamicForm()">GET FORM</button>
</div>
</div>
<form [formGroup]="form" (ngSubmit)="submit(model)" >
<formly-form [model]="model" [fields]="fields" [form]="form" *ngIf="isFormTypeSelected" >
</formly-form>
<button type="submit" class="btn btn-success">Submit</button>
</form>
ts file
getSelectedFormName(eve) {
this.isFormSaved = false;
this.form = new FormGroup({});
this.fields=[];
this.model = {};
this.parentFormName = eve.value;
}
getDynamicForm() {
this.isFormSaved = false;
this.savedFields=[];
this.getDynamicFormBasedOnSelection(this.parentFormName);
//fields getting from api call
}
getDynamicFormBasedOnSelection(formName: string) {
this.auth.getDynamicFormBasedOnSelction(formName, this.userAgencyCode).subscribe(
(result) => {
const str = JSON.stringify(result);
this.fields = JSON.parse(str);
this.isFormTypeSelected = true;
this.addEvents(this.fields);
});
}
Here I'm providing my screens which are for better understanding

Actually form.reset() just reset the form values. You need to reset the form directive too. for eg.
<form [formGroup]='authForm' (submit)='submitForm(formDirective)' #formDirective="ngForm" class="is-mat-form">
<mat-form-field>
<input matInput placeholder="Email ID" formControlName='login'>
<mat-error *ngIf="authForm.controls.login.hasError('required')">
Email is required
</mat-error>
<mat-error *ngIf="authForm.controls.login.hasError('email')">
Please enter a valid email address
</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput type="password" formControlName='password' placeholder="Password">
<mat-error *ngIf="authForm.controls.password.hasError('required')">
Password is required
</mat-error>
<mat-error *ngIf="authForm.controls.password.hasError('minlength')">
Password must be minimum 6 digit long.
</mat-error>
</mat-form-field>
and .ts file is
submitForm(formDirective: FormGroupDirective){
if (this.authForm.invalid) {
console.log('form submitted')
this.authForm.reset()
return;
}
this will reset the form values only, to reset the form error we need to reset the formdirective as well.
submitForm(formDirective: FormGroupDirective){
if (this.authForm.invalid) {
console.log('form submitted')
this.authForm.reset()
formDirective.resetForm();
return;
}

Related

patchValue not working when using [[ngModel]]

So when I open an item editor dialog, I want to be able to put some default values that I retrieve from the specific item I selected. This all worked up until I added [[ngModel]] in each of the fields. In fact, it still works when I remove it. I want to be able to do it again without removing the ngModel as I heavily depend on it in my code.
This is my HTML
<div mat-dialog-content class="formCentered">
<form [formGroup]="form">
<mat-form-field class="inputBox">
<mat-label>Scanner</mat-label>
<input matInput formControlName="scannerName" [(ngModel)]="data.scanName" maxlength="50">
</mat-form-field>
<mat-form-field class="inputBox">
<mat-label>Calibration Price</mat-label>
<input matInput formControlName="calibrationPrice" [(ngModel)]="data.caliPrice" maxlength="50">
</mat-form-field>
<mat-form-field class="inputBox">
<mat-label>Calibration Description</mat-label>
<input matInput formControlName="calibrationDescription" [(ngModel)]="data.caliDescription" maxlength="50">
</mat-form-field>
</form>
</div>
<div mat-dialog-actions class="alignRight">
<button mat-button class="confirmationButton" (click)="onNoClick()" class="confirmationButton">Cancel</button>
<button mat-button class="confirmationButton" [mat-dialog-close]="data" cdkFocusInitial class="confirmationButton">Save</button>
</div>
And this is my part of the ts for this
dialogRef.componentInstance.form.patchValue({
scannerName: cal.scannerName, // scannerName is the old value (unedited)
calibrationPrice: cal.calibrationPrice, // calibrationPrice is the old value (unedited)
calibrationDescription: cal.calibrationDescription // calibrationDescription is the old value (unedited)
});
Any idea on how I can achieve that?
It's not a good practice merge reactive forms with ngModel.
You can remove all of the [(ngModel)] in the html, and subscribe to the form changes. Then, when some field of the form change, the subscription would change your varaibles as well.
Add in your ts code something like this:
// after you have inicialized your form
this.form.get('scannerName').valueChanges
.subscribe((val: any) => {
//this.data.scanName = this.form.get('scannerName').value;
this.data.scanName = val
});
this.form.get('calibrationPrice').valueChanges
.subscribe((val: any) => {
//this.data.calibrationPrice= this.form.get('calibrationPrice').value;
this.data.calibrationPrice= val
});
this.form.get('calibrationDescription').valueChanges
.subscribe((val: any) => {
//this.data.calibrationDescription= this.form.get('calibrationDescription').value;
this.data.calibrationDescription= val
});
You can't add NgModel in formGroup.
You have to define the formControl
example:
form = new FormGroup({
scannerName: new FormControl(''),
calibrationPrice: new FormControl(''),
calibrationDescription: new FormControl(''),
})
If you want set default value, you have to set value in new FormControl like this:
form = new FormGroup({
scannerName: new FormControl('test'),
calibrationPrice: new FormControl('value'),
calibrationDescription: new FormControl(someValue),
})
it's seems that you want change data variable, you could do this:
this.data = this.form.value

The value [value] is not assigned in input

I have a little problem using [value] in my inputs.
This is my component .html
<div class="form-group">
<mat-form-field class="example-full-width" style="padding-left: 15px;">
<input matInput [value]='value_2_' [(ngModel)]="form.name_1" placeholder="name_1"
name="name_pro_1" required>
</mat-form-field>
</div>
Everything looks good, but when I run the program, the value is shown in the corresponding input, however, being a value required by the input, it is still in red, it is solved until I add a letter to the value or remove something from the input.
I managed to solve the error by deleting
name = "name_pro_1"
but since I use NgModel I cannot remove the name because I get other errors. Like:
core.js:4352 ERROR Error: If ngModel is used within a form tag, either the name attribute must be set or the form
control must be defined as 'standalone' in ngModelOptions.
Example 1: <input [(ngModel)]="person.firstName" name="first">
Example 2: <input [(ngModel)]="person.firstName" [ngModelOptions]="{standalone: true}">
I think the problem is [(ngModel)]="form.name_1". Try to replace that with
[(ngModel)]="testValue" and define a variable in the class with the same name and it will work.
I can't replicate the problem, but using [(ngModel)] doesn't make using [value] unnecessary? You can set the form.name_1 to the value you want on the component.ts file.
Finally i resolved the issue, that it's my solution:
First thing first, i delete the [(ngModel)] in the input of component.html, then i used (change) to call the function when user select a SKU
<div class="form-group input-sku" >
<mat-form-field class="example-full-width">
<mat-label>SKU</mat-label>
<input type="text" placeholder="Search.." id="sku_select" aria-label="number"
matInput [formControl]="myControl" [matAutocomplete]="auto"
[(ngModel)]="selectedValue" (change)="changedata_1($event)" >
<mat-autocomplete autoActiveFirstOption #auto="matAutocomplete">
<mat-option *ngFor="let data of filter_products | async" [value]="data.sku">{{data.sku}}</mat-option>
</mat-autocomplete>
</mat-form-field>
</div>
Input who recived the value:
<div class="form-group inputs-name">
<mat-form-field class="example-full-width">
<mat-label>Name</mat-label>
<input type="text" matInput id="nombre_1" required [value]="name_1" name="name_1" placeholder="Name" >
</mat-form-field>
</div>
And in my component.ts
changedata_1(e) {
this.nombre_1 = e.target.value;
this.DataToSave.nombre_1 = this.nombre_1;}
private _filter(value: string): Products_BD[] {
const filterValue = value.toLowerCase();
const data_ = this.DatasProducts.filter(DataProducts => DataProducts.sku.toLowerCase().includes(filterValue));
for(let element of data_){
const sku1= elemento.sku;
const name1 = element.name;
this.name_1 = name1;
this.DataToSave.sku_1 = sku1;
this.DataToSave.name_1 = name1;
}
return this.DataProducts.filter(DataProducts => DataProducts.sku.toLowerCase().includes(filterValue));}
this.filter_products = this.myControl.valueChanges.pipe(
startWith(''),
map(value => this._filter(value))
);
I hope this solution helps! :)

Routing via MatAutocomplette Angular

I am trying to implement the autocomplete component of Angular Material:
It works fine, it at least displays and I see the data from my json. Is there a way to route on click of the event, that is when something is selected from the autocomplete to a page?
My HTML currently looks like this:
<div class="example-form">
<div class="container">
<form [formGroup]="schoolForm">
<mat-form-field class="example-form">
<input type="text" matInput placeholder="Gib den Namen deiner Schule ein" formControlName="schoolGroup" required
[matAutocomplete]="autoGroup">
<mat-autocomplete #autoGroup="matAutocomplete">
<mat-optgroup *ngFor="let group of schoolGroupOptions | async" [label]="group.letter">
<mat-option *ngFor="let name of group.names" [value]="name">
{{name}}
</mat-option>
</mat-optgroup>
</mat-autocomplete>
</mat-form-field>
</form>
<form [formGroup]="schoolForm">
<mat-form-field class="example-form1">
<input type="text" matInput placeholder="Gib den Nachnamen deines Lehrers ein">
</mat-form-field>
</form>
</div>
<button mat-raised-button color="primary" routerLink="/teachers">Ergebnisse anzeigen</button>
</div>
how should the TS. look like?
Thx for your help
Looking at the documentation we see that MatAutoComplete exposes an event when an option is selected.
#Output() optionSelected
Reading further, we need to add [matAutocomplete] as a directive onto our text input, and pass it a reference to the auto-complete in our template.
To do this, I create a mat-autocomplete and give it the template literal name of #navigable which we pass into the [matAutocomplete] directive.
This gives us the following html template
<mat-form-field>
<input type="text" matInput [matAutocomplete]="navigable">
<mat-autocomplete #navigable (optionSelected)="chooseOption($event)">
<mat-option *ngFor="let group of groups" [value]="group.route">{{group.name}}</mat-option>
</mat-autocomplete>
</mat-form-field>
Note we can bind to (optionSelected) on the mat-autocomplete as it is the event stated in the API to be called when the value is selected for our attached input.
in our .ts file
#Component()
export class MyExampleComponent {
// group dummy object, hard-coded for demonstration purposes
// I've chosen to store the route directly on the group.
// You should choose whichever strategy suits your needs
groups = [
{
name: "One",
route: 'one'
},
{
name: "Two",
route: 'two'
},
{
name: "Three",
route: 'three'
}
];
// inject router for navigation
constructor(private router: Router) {}
chooseOption(event: MatAutocompleteSelectedEvent) {
// destructure the value and navigate.
const {option: {value}} = event
this.router.navigate([value]);
}
}
and that is it. We will now navigate as soon as an option is selected in the dropdown list.

Set initial value to a Toggle of a form in Angular Material within HTML

I am working with a simple form:
HTML
<mat-card>
<form (submit)="onAddBet(betForm)" #betForm="ngForm">
<mat-form-field>
<textarea #description="ngModel" matInput rows="6" placeholder="Description" name="description" ngModel
required></textarea>
<mat-error *ngIf="description.invalid">Please enter a Bet Description</mat-error>
</mat-form-field>
<mat-slide-toggle #privacy="ngModel" name="privacy" ngModel>Private</mat-slide-toggle>
<mat-slide-toggle #comments="ngModel" name="comments" ngModel>Allow comments</mat-slide-toggle>
<button mat-raised-button color="primary" type="submit">
CREATE
</button>
</form>
</mat-card>
If I press submit without touching any field of the form I get all the fields empty as it should be expected but I would like to get instead of the status of the form, meaning all the fields as "" but in the fields "privacy" and "comments" as false (boolean) (the default appearance of the toggles is as not marked).
I know that this could be easily done by Typescript from the component following the method:
Typescript
onAddBet(form: NgForm) {
if (!form.value.privacy) {
form.value.privacy = false;
console.log(form.value);
} else console.log(form.value);
if (form.invalid) return;
form.resetForm();
}
But I was wondering if there is any HTML attribute to run the same code avoiding to use Typescript
How could I do it?
You can initialize your ngModel in your HTML by using ngModel with a one-way binding
<mat-slide-toggle #privacy="ngModel" name="privacy" [ngModel]="false">Private</mat-slide-toggle>
<mat-slide-toggle #comments="ngModel" name="comments" [ngModel]="false">Allow comments</mat-slide-toggle>
Then in your component
onAddBet(form: NgForm) {
console.log(form.value);
if (form.invalid) return;
form.resetForm();
}
As per the documentation of slide-toggle you can use [checked]="checked" in HTML.

Password is not matched with pattern in Angular CLI

I have a problem with password validation, I am using a regex such as;
'(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])'
and my ng-form field is;
password: ['', [Validators.pattern('(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])'), Validators.required]]
also in HTML, I get input as;
<div class="form-group">
<label for="password">Password</label>
<input type="password" formControlName="password" placeholder="******" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.password.errors }" />
<div *ngIf="submitted && f.password.errors" class="invalid-feedback">
<div *ngIf="f.password.errors">Invalid Password</div>
</div>
</div>
where f is function such as;
get f() {
return this.userForm.controls;
}
When I entered a password as: Harun123, I get invalid password error. Why this happens?
This question could be solved with a combination of these two answers:
So first of all, you would need a custom validator for checking the passwords, that could look like this
checkPasswords(group: FormGroup) { // here we have the 'passwords' group
let pass = group.controls.password.value;
let confirmPass = group.controls.confirmPass.value;
return pass === confirmPass ? null : { notSame: true }
}
and you would create a formgroup for your fields, instead of just two form controls, then mark that custom validator for your form group:
this.myForm = this.fb.group({
password: ['', [Validators.required]],
confirmPassword: ['']
}, {validator: this.checkPasswords })
and then as mentioned in other answer, the mat-error only shows if a FormControl is invalid, so you need an error state matcher:
export class MyErrorStateMatcher implements ErrorStateMatcher {
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm |
null): boolean {
const invalidCtrl = !!(control && control.invalid && control.parent.dirty);
const invalidParent = !!(control && control.parent && control.parent.invalid
&& control.parent.dirty);
return (invalidCtrl || invalidParent);
}
}
in the above you can tweak when to show error message. I would only show message when the password field is touched. Also I would like above, remove the required validator from the confirmPassword field, since the form is not valid anyway if passwords do not match.
Then in component, create a new ErrorStateMatcher:
matcher = new MyErrorStateMatcher();
Finally, the template would look like this:
<form [formGroup]="myForm">
<mat-form-field>
<input matInput placeholder="New password" formControlName="password"
required>
<mat-error *ngIf="myForm.hasError('required', 'password')">
Please enter your new password
</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput placeholder="Confirm password"
formControlName="confirmPassword" [errorStateMatcher]="matcher">
<mat-error *ngIf="myForm.hasError('notSame')">
Passwords do not match
</mat-error>
</mat-form-field>
</form>
Here's a demo for you with the above code: stackblitz