I have a problem with the form in angular. My goal is to make a form that is filled with default values that can be changed. After validating the form, it sends the data to the MySQL database.
This is component.html code:
<form #adopt="ngForm" (ngSubmit)="success()">
<label for="email">Email:</label>
<input type="email" name="email" [(ngModel)]="adoptions.email" #email="ngModel">
<label for="animal">Twój wybór to:</label>
<input type="text" name="animal" [(ngModel)]="adoptions.animal" #email="ngModel">
<button [disabled]="adopt.form.invalid" type="submit">Adoptuj</button>
<button (click)="getAnimal('')" class="disable">Odznacz swój wybór</button>
</form>
This is typeScript code:
export class AdoptpageComponent implements OnInit {
adoptions = new Adoptions();
sessionValue
animal
value
msg='';
constructor(private userService: UserService, private shared: SharedService, private _service
: AdoptService, private _router : Router) {
}
ngOnInit(): void {
this.getUsers();
this.sessionValue = sessionStorage.getItem('email');
}
getAnimal(arg) {
this.animal = arg;
}
success() {
this._service.create(this.adoptions).subscribe(
data => {
console.log("dziala");
this._router.navigate(['/adopt'])
},
error => {
console.log("nie dziala");
this.msg = error.error;
}
)
}
}
The code I posted above works, but only when I enter the value into the form from the keyboard. I want the value from sessionValue to be retrieved automatically in the first form and animal in the second. I managed to achieve it when instead of ngModel I entered:
<input type="email" name="email" [value]="sessionValue" #email="ngModel">
But then the form does not work (it does not send data to the database). Unfortunately, when both are used, [value] = "sessionValue" does not work
<input type="email" name="email" [value]="sessionValue" [(ngModel)]="adoptions.email" #email="ngModel">
do you have an idea what to do to be able to submit the form with the default value?
first: you are binding adoptions.email and adoptions.animal to ngModel, but they are empty(or even worse - null or undefined) when ngOnInit is fired, that is why your inputs are empty. They get value once you introduce text in inputs, that is why you are able to successfully execute this._service.create
second: you are causing a value binding conflict. The source of ngModel is different from the source of value. Once ngOnInit is fired value tries to load the value of sessionValue in input and ngModel tries to load nothing, as its source is empty
There is no need to use value if you have ngModel. You just have to set up start point value for its source variable. like below example
ngOnInit(): void {
adoptions.email = somehowGetEmail()
adoptions.animal= somehowGetAnimal()
}
And that's all you have to do, if you insist on using ngModels.
But in general this looks like a lot of unnccessary bindings. Since these are a form inputs you should be using form's preimplemented features for value bindings. Check docs for angular FormBuilder, formControl
I have a mat input field in which I bind two-way data with ngModel, I would also like to incorporate mat-error with validation using a formControl.
<mat-form-field [formGroup]="myForm">
<input matInput formControlName="myFormName" autocomplete="off"
(ngModelChange)="dataChanged(myValue)" [ngModel]="myValue">
<mat-error>
Error! Value outside <strong>{{minVal}}</strong>
and <strong>{{maxVal}}</strong>.
</mat-error>
</mat-form-field>
However this gives me an issue where ngModelChange fires multiple times when the component is initialized and the ngModel value myValue is populated with data through HTTP calls in a service. Something about mixing template driven and reactive forms?
Essentially, what I am trying to achieve is two way bind data to an input while also having mat-errors without having ngModelChange fire unnecessarily just because I populate the input with data on init.
You would have a form that looks something like this:
import { Component } from "#angular/core";
import {
FormGroup,
FormControl,
Validators
} from "#angular/forms";
/** #title Input with a custom ErrorStateMatcher */
#Component({
selector: "input-error-state-matcher-example",
templateUrl: "./input-error-state-matcher-example.html",
styleUrls: ["./input-error-state-matcher-example.css"]
})
export class InputErrorStateMatcherExample {
minVal = 8;
maxVal = 20;
myForm = new FormGroup({
myFormName: new FormControl("", [
Validators.minLength(this.minVal),
Validators.maxLength(this.maxVal)
])
});
}
Here, you've defined your minLength and maxLength validators.
You've also binded the form with your template using [formGroup] and formControlName directives.
So everything will be taken care of by Angular for you.
You just have to access the errors property on the myForm.controls['myFormName'] control. Something like this:
<mat-form-field [formGroup]="myForm">
<input matInput formControlName="myFormName" autocomplete="off">
<mat-error *ngIf="myForm.controls['myFormName'].errors">
Error! Value outside <strong>{{minVal}}</strong>
and <strong>{{maxVal}}</strong>.
</mat-error>
</mat-form-field>
Here's a Working Demo for your ref.
I have a form on my page and when I call FormGroup.reset() it sets the forms class to ng-pristine ng-untouched but FormControl.hasError(...) still returns truthy. What am I doing wrong here?
Template
<form [formGroup]="myForm" (ngSubmit)="submitForm(myForm)">
<mat-form-field>
<input matInput formControlName="email" />
<mat-error *ngIf="email.hasError('required')">
Email is a required feild
</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput type="password" formControlName="password" />
<mat-error *ngIf="password.hasError('required')">
Password is a required feild
</mat-error>
</mat-form-field>
<button type="submit">Login</button>
</form>
Component
export class MyComponent {
private myForm: FormGroup;
private email: FormControl = new FormContorl('', Validators.required);
private password: FormControl = new FormControl('', Validators.required);
constructor(
private formBuilder: FormBuilder
) {
this.myForm = formBuilder.group({
email: this.email,
password: this.password
});
}
private submitForm(formData: any): void {
this.myForm.reset();
}
}
Plunker
https://embed.plnkr.co/Hlivn4/
It (FormGroup) behaves correctly. Your form requires username and password, thus when you reset the form it should be invalid (i.e. form with no username/password is not valid).
If I understand correctly, your issue here is why the red errors are not there at the first time you load the page (where the form is ALSO invalid) but pop up when you click the button. This issue is particularly prominent when you're using Material.
AFAIK, <mat-error> check the validity of FormGroupDirective, not FormGroup, and resetting FormGroup does not reset FormGroupDirective. It's a bit inconvenient, but to clear <mat-error> you would need to reset FormGroupDirective as well.
To do that, in your template, define a variable as such:
<form [formGroup]="myForm" #formDirective="ngForm"
(ngSubmit)="submitForm(myForm, formDirective)">
And in your component class, call formDirective.resetForm():
private submitForm(formData: any, formDirective: FormGroupDirective): void {
formDirective.resetForm();
this.myForm.reset();
}
GitHub issue: https://github.com/angular/material2/issues/4190
In addition to Harry Ninh's solution, if you'd like to access the formDirective in your component without having to select a form button, then:
Template:
<form
...
#formDirective="ngForm"
>
Component:
import { ViewChild, ... } from '#angular/core';
import { NgForm, ... } from '#angular/forms';
export class MyComponent {
...
#ViewChild('formDirective') private formDirective: NgForm;
constructor(... )
private someFunction(): void {
...
formDirective.resetForm();
}
}
After reading the comments this is the correct approach
// you can put this method in a module and reuse it as needed
resetForm(form: FormGroup) {
form.reset();
Object.keys(form.controls).forEach(key => {
form.get(key).setErrors(null) ;
});
}
There was no need to call form.clearValidators()
Add the property -
#ViewChild(FormGroupDirective) formGroupDirective: FormGroupDirective;
and use this instead of this.myForm.reset();
this.formGroupDirective.resetForm();
This will reset the error display and also do the job of form.reset(). But the form, along with the fields, will still show ng-invalid class
Check this answer for more details - https://stackoverflow.com/a/56518781/9262627
The below solution works for me when trying to reset specific form controller in form group -
this.myForm.get('formCtrlName').reset();
this.myForm.get('formCtrlName').setValidators([Validators.required, Validators.maxLength(45), Validators.minLength(4), Validators.pattern(environment.USER_NAME_REGEX)]);
this.myForm.get('formCtrlName').updateValueAndValidity();
form.reset() won't work on custom form control like Angular Material that's why the function is not working as expected.
My workaround for this is something like this
this.form.reset();
for (let control in this.form.controls) {
this.form.controls[control].setErrors(null);
}
this.form.reset() the issue with this is that it will reset your formcontrol values but not the errors so you need to reset them individually by this line of code
for (let control in this.form.controls) {
this.form.controls[control].setErrors(null);
}
With this you don't need to use FormGroupDirective which is a cleaner solution for me.
Github issue: https://github.com/angular/angular/issues/15741
I found that after calling resetForm() and reset(), submitted was not being reset and remained as true, causing error messages to display. This solution worked for me. I found it while looking for a solution to calling select() and focus() on an input tag, which also wasn't working as expected. Just wrap your lines in a setTimeout(). I think setTimeout is forcing Angular to detect changes, but I could be wrong. It's a bit of a hack, but does the trick.
<form [formGroup]="myFormGroup" #myForm="ngForm">
…
<button mat-raised-button (click)="submitForm()">
</form>
submitForm() {
…
setTimeout(() => {
this.myForm.resetForm();
this.myFormGroup.reset();
}, 0);
}
resetForm() {
this.myFormGroup.reset();
this.myFormGroup.controls.food.setErrors(null);
this.myFormGroup.updateValueAndValidity();
}
UPDATE FROM 2021 - ANGULAR 11.2
The fact to use a [formGroup]="form and a #formDirective="ngForm" directly into the HTML function is not a good practise. Or maybe you would prefer to use #ViewChild, and do it directly from your .ts. Actually, the problem don't come from Angular, but Material.
If you take a look at their GitHub, you will see this :
/** Provider that defines how form controls behave with regards to displaying error messages. */
#Injectable({providedIn: 'root'})
export class ErrorStateMatcher {
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
return !!(control && control.invalid && (control.touched || (form && form.submitted)));
}
}
The form will keep its submitted state. So you just have to delete the last part of the function.
Here is my solution (tested and working). I have a Material Module, into I've implemented this :
export class ShowOnInvalidTouchedErrorStateMatcher implements ErrorStateMatcher {
isErrorState(control: FormControl): boolean {
return !!(control && control.invalid && control.touched);
}
}
#NgModule({
providers: [
{
provide: ErrorStateMatcher, useClass: ShowOnInvalidTouchedErrorStateMatcher
}
],
exports: [
MatSnackBarModule,
MatTabsModule,
...
]
});
If you want to use this ErrorStateMatcher on only one form, it's possible. Please see this Material example. This is the same principle.
I had no luck with resetting the form directive. But You can also change the input state to pending to do that as well.
this.myForm.get("email").reset();
this.myForm.get("password").reset();
To anyone whom this may help, I am running Angular 9.1.9 and I didn't want to reset the form/controls just the overall validity of the form so I just ran:
this.registerForm.setErrors(null);
...where registerForm: FormGroup and that reset the form errors, leading to:
this.registerForm.valid
...returning true.
The same can be done for controls:
this.registerForm.get('email').setErrors(null)
As soon as the form is touched, these errors are re-evaluated anyway so if that's not good enough, you may need to have a boolean flag to further pin-down exactly when you want to start showing/hiding error UI.
I did not need to touch the directive in my case.
I was also having the same set of problems. My problem was that i was using mat-form-field and formGroup. After resetting the form submitted flag was not resetting.
So, the solution that worked for me is, putting a directive of ngForm along with formGroup and passing onSubmit(form). Added
#ViewChild('form') form;
in component and then I used
this.form.resetForm();
Nothing from above worked for me (Angular 7.2, Angular Material 7.3.7).
Try to pass with submit method an event on view:
<form [formGroup]="group" (ngSubmit)="onSubmit($event)">
<!-- your form here -->
</form>
Then use it to reset currentTarget and your form afterwards:
public onSubmit(event): void {
// your code here
event.currentTarget.reset()
this.group.reset()
}
Simple fix: use button with type="reset" and function submitForm() together
<form [formGroup]="MyForm" (ngSubmit)="submitForm()">
<input formControlName="Name">
<mat-error>
<span *ngIf="!tunersForm.get('Name').value && tunersForm.get('Name').touched"></span>
</mat-error>
<button type="reset" [disabled]="!MyForm.valid" (click)="submitForm()">Save</button>
</form>
I have a working form taking the following HTML markup. No errors or warnings.
<div class="input-element">
<div class="input-caption">Title</div>
<input type="text"
formControlName="targetField"
class="form-control">
</div>
I transformed it into a custom component, which also works, as shown below.
<app-input-text [info]="'Title'"
formControlName="targetField"
ngDefaultControl></app-input-text>
In my next view, I need to use FormArray as follows - still working code.
<div formArrayName="stuff">
<div *ngFor="let thing of form.controls.stuff.controls; let i = index;"
[formGroupName]=i>
<div class="input-element">
<div class="input-caption">Title</div>
<input type="text"
formControlName="targetField"
class="form-control">
</div>
</div>
</div>
Now, I expected that combining both (i.e. being able to use custom input component and being able to form array for components) would post no problem. However, the sample below doesn't work.
<div formArrayName="stuff">
<div *ngFor="let thing of form.controls.stuff.controls; let i = index;"
[formGroupName]=i>
<app-input-text [info]="'Title'"
formControlName="targetField"
class="col-sm-6"></app-input-text>
</div>
</div>
It generates the following error.
No value accessor for form control with path: 'stuff -> 0 -> targetField'
The custom component is design like this (although given that it works in the explicit markup example, I'm not sure if it's relevant information). The only (wild) guess I have might be that value isn't jacked into the form array field somehow.
export class InputTextComponent implements OnInit {
constructor() { this.value = new EventEmitter<string>(); }
#Input() info: string;
#Output() value: EventEmitter<string>;
onEdit(value: any): void { this.value.emit(value); }
}
The group and array creating in the current view is done like this (not sure if this is of any relevance neither, as it works for the explicit HTML markup case).
this.form = builder.group({
id: "",
stuff: builder.array([
builder.group({ targetField: "aaa" }),
builder.group({ targetField: "bbbb" }),
builder.group({ targetField: "cc" })
])
});
Is there a limitation in Angular in this regard that I'm not aware of? I'm rather sure there's not and that I'm just doing something fairly clever simply missing a tiny detail.
I do understand the error but I can't see how it relates to the code. The form can't find the 0th element in the array or that element has no field of that name. Since I do get to see a few rows, I know there must be a 0th element. Since I specified the name of the field, I know there is indeed such. What else am I missing?
I am very new to angular2 and i was wondering if there is a shorter way to write the keypress and paste events so that the html code will be more readable (i am using Type Script):
<textarea rows="1" class="txt" (keypress)="c()" (paste)="c()" [(ngModel)]="LeftText"></textarea>
When HTML template becomes cluttered with Angular logic, this means that logic should be moved to directive/component classes.
In this case this can be a directive:
#Directive({ selector: '[modify]' })
class ModifyDirective {
#Input() modify;
#HostListener('paste', ['$event.target'])
#HostListener('keypress', ['$event.target'])
onModify(e) {
if (this.modify) {
this.modify(e);
}
}
}
Which is used like
<textarea [modify]="c">
Notice that c is passed to the directive as a callback, this means that a method should be bound to the context in order to keep proper this.