Angular 8 Material mat-error shows up on load by default - angular9

I am having this weird scenario. my mat-error shows up by default, without even touching the input field. Basically, when I load the page the error message is shown without any user interaction on the input field. I don't have any error messages on the browser console. Can anyone point out what did I miss?
This is my .ts
frmStep3: FormGroup;
matcher = new MyErrorStateMatcher();
// omited unrelated code
this.frmStep3 = this.fb.group({
courseDurationNum: [, [Validators.required, Validators.maxLength(3), Validators.minLength(1)]]
});
return {
frmStep3: this.frmStep3
};
This is my HTML
<form class="form-group-parent p-0" [formGroup]="frmStep3" (ngSubmit)="stepForward()">
<div class="form-group d-inline-block">
<label class="w-100">Choose your course duration<sup>*</sup></label>
<input [errorStateMatcher]="matcher" [maxLength]="3" type="text" class="float-left" matInput
formControlName="courseDurationNum" placeholder="Ex. 1 - 365 Day(s)" />
<mat-error class="w-100 float-left"
*ngIf="frmStep3.get('courseDurationNum').hasError('required') || frmStep3.get('courseDurationNum').invalid">
<strong>Duration</strong> must be between <strong>1 - 365</strong> day(s).
</mat-error>
</div>
</form>

I figured out, I missed wrapping them in from controls in <mat-form-field> tag. Correct code below.
<form class="form-group-parent p-0" [formGroup]="frmStep3" (ngSubmit)="stepForward()">
<div class="form-group d-inline-block">
<mat-form-field>
<label class="w-100">Choose your course duration<sup>*</sup></label>
<input [errorStateMatcher]="matcher" [maxLength]="3" type="text" class="float-left" matInput
formControlName="courseDurationNum" placeholder="Ex. 1 - 365 Day(s)" />
<mat-error class="w-100 float-left"
*ngIf="frmStep3.get('courseDurationNum').hasError('required') || frmStep3.get('courseDurationNum').invalid">
<strong>Duration</strong> must be between <strong>1 - 365</strong> day(s).
</mat-error>
</mat-form-field>
</div>
</form>

Related

HTML & Angular Material disable save button from outside div when form fields are empty

I have the following Angular Material form with the Close and Save buttons in an outside div for design reasons:
<h1 mat-dialog-title>Product Page</h1>
<div mat-dialog-content>
<form [formGroup]="productForm">
<mat-form-field appearance="outline">
<mat-label>Name</mat-label>
<input formControlName="name" required matInput placeholder="Name" style="text-transform:uppercase">
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Weight</mat-label>
<input type="number" required formControlName="weight" matInput placeholder="weight">
</mat-form-field>
</form>
</div>
<div mat-dialog-actions [align]="'end'">
<button mat-raised-button color="warn" mat-dialog-close>Close</button>
<button mat-raised-button color="primary" (click)="saveProduct()">Save</button>
</div>
I want to disable the Save button when the form fields are empty but the required parameter isn't working since the buttons are in an outside div. Is there a way to correlate the buttons with the form such that the Save button is disabled until the required fields are filled in?
You can bind it manually :
<button (click)="submit()" [disabled]="!form.valid">Submit</button>
Or you can cheat your way in the modal by providing the form as the main container (yes, it works)
<form [formGroup]="form">
<h1 mat-dialog-title>...</h1>
<div mat-dialog-content>...</div>
<div mat-dialog-actions align="end">
<button type="submit" [disabled]="!form.valid">Submit</button>
</div>
</form>
You could add a validation to your formGroup and use the formGroup to set the button disabled:
on your component:
constructor(fb:FormBuilder
(...)
) {
this.productForm = fb.group({
name: [null, Validators.required]
(...)
})
}
then on your template, adjust the button:
<button mat-raised-button color="primary" [disabled]="productForm.invalid" (click)="saveProduct()">Save</button>
You might as well remove the 'required' from you input (as angular will warn you about this in the console):
<input formControlName="name" matInput placeholder="Name" style="text-transform:uppercase">

Angular template inside reactive form

In our application I noticed lots of duplicate code in the HTML of our forms, most input elements have the same structure so I want to create a generic input component that we can use instead to keep the files clean.
The problem I currently have is that there is 1 form that has 2 nested formGroups inside, example:
this.addressForm = this.fb.group({
postalAddress: this.fb.group({
street: ["", [Validators.required]],
}),
visitorAddress: this.fb.group({
street: [""],
})
});
This leads to my new component's HTML to also have duplicate code due to some forms requiring a formGroupName.
<div [formGroup]="form" *ngIf="form && controlPath && controlName">
<div *ngIf="groupName" [formGroupName]="groupName">
<mat-form-field class="w-full">
<mat-label>{{ label }}</mat-label>
<input type="text" matInput [formControlName]="controlName" *ngIf="type === 'text'">
<input type="number" matInput [formControlName]="controlName" *ngIf="type === 'number'">
<mat-error *ngFor="let message of form.get(controlPath)?.errors?.['messages']">{{ message | i18n }}</mat-error>
</mat-form-field>
</div>
<div *ngIf="!groupName">
<mat-form-field class="w-full">
<mat-label>{{ label }}</mat-label>
<input type="text" matInput [formControlName]="controlName" *ngIf="type === 'text'">
<input type="number" matInput [formControlName]="controlName" *ngIf="type === 'number'">
<mat-error *ngFor="let message of form.get(controlPath)?.errors?.['messages']">{{ message | i18n }}</mat-error>
</mat-form-field>
</div>
The code above works fine but as mentioned, I would like to get rid of the duplicate code. I figured this would be a good case for a ng-template but it appears when using a template the nested controls can no longer find the surrounding FormGroup.
Example
<div [formGroup]="form" *ngIf="form && controlPath && controlName">
<div *ngIf="groupName" [formGroupName]="groupName">
<ng-content *ngTemplateOutlet="content"></ng-content>
</div>
<div *ngIf="!groupName">
<ng-content *ngTemplateOutlet="content"></ng-content>
</div>
<ng-template #content>
<mat-form-field class="w-full">
<mat-label>{{ label }}</mat-label>
<input type="text" matInput [formControlName]="controlName" *ngIf="type === 'text'">
<input type="number" matInput [formControlName]="controlName" *ngIf="type === 'number'">
<mat-error *ngFor="let message of form.get(controlPath)?.errors?.['messages']">{{ message | i18n }}</mat-error>
</mat-form-field>
</ng-template>
Error:
Has anyone encountered such a situation and if so, what would be a good way to go about this?
UPDATE
After Garbage Collectors answer I refactored my code to the following:
<div [formGroup]="form" *ngIf="form && controlPath && controlName">
<div *ngIf="groupName" [formGroupName]="groupName">
<ng-container [ngTemplateOutlet]="inputTemplate" [ngTemplateOutletContext]="{ templateForm: form, templateFormGroup: groupName, templateControlName: controlName }"></ng-container>
</div>
<div *ngIf="!groupName">
<ng-container [ngTemplateOutlet]="inputTemplate" [ngTemplateOutletContext]="{ templateForm: form, templateControlName: controlName }"></ng-container>
</div>
<ng-template #inputTemplate let-form="templateForm" let-groupName="templateFormGroup" let-controlName="templateControlName">
<div [formGroup]="form" [formGroupName]="groupName">
<mat-form-field class="w-full">
<mat-label>{{ label }}</mat-label>
<input type="text" matInput [formControlName]="controlName" *ngIf="type === 'text'">
<input type="number" matInput [formControlName]="controlName" *ngIf="type === 'number'">
<mat-error *ngIf="form.get(controlPath)?.errors?.['required']">{{ 'error.required' | i18n }}</mat-error>
<mat-error *ngIf="form.get(controlPath)?.errors?.['email']">{{ 'error.email' | i18n }}</mat-error>
<mat-error *ngIf="form.get(controlPath)?.errors?.['invalid']">{{ 'error.form.invalid' | i18n }}</mat-error>
<mat-error *ngIf="form.get(controlPath)?.errors?.['minlength']">{{ minLengthError }}</mat-error>
<mat-error *ngIf="form.get(controlPath)?.errors?.['pattern']">{{ patternError }}</mat-error>
<mat-error *ngFor="let message of form.get(controlPath)?.errors?.['messages']">{{ message | i18n }}</mat-error>
</mat-form-field>
</div>
</ng-template>
Though the template now has all variables correctly, it cannot find the control path for non-nested forms. I outputted controlName in the template as a test and it is correct. I expect the error occurring due to the formGroupName being null for non-nested forms.
Error:
As far as I remember, every isolated piece of code displaying form controls should have a reference to the form where those controls were defined.
Actions
Use ng-container instead of ng-content to include your templates
Pass form as a parameter to your template
Assign form parameter to [formGroup] attribute inside your template
Template
<ng-template #demoTemplate let-form="demoForm">
<div [formGroup]="form">
<!-- Insert the rest of your template here -->
</div>
</ng-template>
Main component using template
<div [formGroup]="form" *ngIf="form">
<ng-container
[ngTemplateOutlet]="demoTemplate"
[ngTemplateOutletContext]="{ demoForm: form }">
</ng-container>
</div>
Notice that attribute formGroup is added in both places, in the template and in the parent.
Use [formControl] instead of formControlName
You can pick nested variables via the get command, for instance form.get('Address.Street')
More explanations: https://stackoverflow.com/a/74668699/6019056

Template Driven Form Mark All as touched - Angular 7

I am using template driven forms for validations. And I would like to Mark all fields as touched when the user blur on the last required field. currently I am only able to do this by passing the form and individually doing each field. From research I see there is a way to do MarkAllAsTocuhed but it's throwing an error. Is there a better/correct way to do this with Angular 7. I also tried looping through the controls but since it's an object that also does not work.
.HTML
<form #myForm="ngForm">
<mat-form-field class="input-field">
<input #field1="ngModel" name="name1"
[(ngModel)]="fieldOne" type="text" matInput placeholder="Field 1" required>
</mat-form-field>
<mat-form-field class="input-field">
<input #field2="ngModel" name="name2"
[(ngModel)]="fieldTwo" type="text" matInput placeholder="Field 2" required>
</mat-form-field>
<mat-form-field class="input-field">
<input #field2="ngModel" name="name3"
[(ngModel)]="fieldThree" type="text" matInput placeholder="Field 3" required>
</mat-form-field>
<mat-form-field class="input-field">
<input #field3="ngModel" name="name4"
[(ngModel)]="fieldFour" type="text" matInput placeholder="Field 4"
(blur)="touchAllFields(myForm.form)" required>
</mat-form-field>
</form>
.TS
touchAllFields(form){
//Working Version
form.controls.name1.markAsTouched();
form.controls.name2.markAsTouched();
form.controls.name3.markAsTouched();
form.controls.name4.markAsTouched();
form.controls.name5.markAsTouched();
form.controls.name6.markAsTouched();
form.controls.name7.markAsTouched();
form.controls.name8.markAsTouched();
//Failed Attempt
form.controls.markAllAsTouched(); //typeError: form.controls.markAllAsTouched is not a function
//Failed Attempt
for(var i = 0; i < form.controls.length; i++){
form.controls[i].markAllAsTouched(); //Failed since its an object and not an array
}
}
ngForm itself has no markAllAsTouched method since it's not a FormGroup or FormArray. However, NgForm has an instance of a formGroup accessible via .form or .control - source.
This has been available since Angular2 until the current latest version (Angular10)
So the correct answer to your questions should be as follows:
form.form.markAllAsTouched();
or
form.control.markAllAsTouched();
You can try this:
Found out that Object.keys can handle this.
Object.keys(this.form.controls).forEach(key => {
this.form.get(key).markAsTouched();
});
For Angular 8+, you can use:
Object.keys(this.form.controls).forEach(key => {
this.form.controls[key].markAsTouched();
});

form submit not working on mobile device (match the requested format)

I have a small angular app I have made that works fine on my computer, but does not work properly on any mobile device. The problem is the form submit button. The button will not submit form and hitting the return key on my ipad on the last form field directs back to the card number form field and says "Match the requested format" ... I am using Stripe by the way.
I have tried ngSubmit on the form and changed the button around to input type and type button and etc. I have also got rid of ngSubmit and used (click)="buy()" which also works on a desktop, but not on a mobile device. I have even added (tap) from hammerjs just in case it was a touch issue. I tried using a label outside of the form linked to the submit button. None of which work. I added an alert to just see if the button was even being clicked, and it is, the mobile device will show the alert, but wont submit the form. So, please ignore the the code at the bottom where I have multiple buttons a labels, it was for testing purposes (it all works on desktop though as is, just not on any mobile device).
Here are two pics of the problem:
https://drive.google.com/file/d/18grWfXyQN9gvcuJRuUqIoS_yjMsU1vii/view?usp=sharing)
https://drive.google.com/file/d/1ezhyAtTg1Z1OomLd9pv47Bif-0ILOR5b/view?usp=sharing
<app-navbar></app-navbar>
<div class="container">
<form class="example-form" action="#" [formGroup]="stripeTest">
<table class="example-full-width" cellspacing="0">
<tr>
<td>
<mat-form-field class="example-full-width media-width">
<input matInput type="text" placeholder="Full Name"
formControlName="name" id="firstName">
<mat-error *ngIf="hasError('name', 'required')">First Name is
required
</mat-error>
</mat-form-field>
</td>
</table>
<table class="example-full-width" cellspacing="0">
<td>
<mat-form-field class="example-full-width">
<input matInput type="text" placeholder="Phone "
formControlName="phone" id="phone">
<mat-error *ngIf="hasError('phone', 'required')">Zip is
required
</mat-error>
</mat-form-field>
</td>
<td>
<mat-form-field class="example-full-width">
<input matInput type="text" placeholder="Email"
formControlName="email" id="email">
<mat-error *ngIf="hasError('email', 'required')">Zip is
required
</mat-error>
</mat-form-field>
</td>
</table>
<p>
<mat-form-field class="example-full-width addr-media-width">
<textarea matInput type="text" placeholder="Address"
formControlName="address_line1" id="address"></textarea>
<mat-error *ngIf="hasError('address_line1', 'required')">Address
is required
</mat-error>
</mat-form-field>
</p>
<table class="example-full-width" cellspacing="0">
<tr>
<td>
<mat-form-field class="example-full-width">
<input matInput type="text" placeholder="City"
formControlName="address_city" id="city">
<mat-error *ngIf="hasError('address_city', 'required')">City is
required
</mat-error>
</mat-form-field>
</td>
<td>
<mat-form-field class="example-full-width state-width">
<input matInput type="text" placeholder="State"
formControlName="address_state" id="state">
<mat-error *ngIf="hasError('address_state', 'required')">State is
required
</mat-error>
</mat-form-field>
</td>
<td>
<mat-form-field class="half-width">
<input matInput type="text" placeholder="Zip"
formControlName="address_zip" id="zip">
<mat-error *ngIf="hasError('address_zip', 'required')">Zip is
required
</mat-error>
</mat-form-field>
</td>
</tr>
</table>
<div class="col-lg-12">
<div id="card-element" class="field"></div>
</div>
<div class="col-lg-12">
<input type="submit" id="submit-form" [disabled]="!stripeTest.valid"
class="btn btn-danger" style="visibility: hidden" (click)="buy()">
<button type="button" (tap)="buy()">Buy</button>
<button class="btn btn-danger" (click)="cancel()">Cancel</button>
</div>
</form>
<label for="submit-form" tabindex="0">Submit</label>
buy function
buy(){
const name = this.stripeTest.get('name').value;
const phone = this.stripeTest.get('phone').value;
const email = this.stripeTest.get('email').value;
const address_state = this.stripeTest.get('address_state').value;
const address_city = this.stripeTest.get('address_city').value;
const address_line1 = this.stripeTest.get('address_line1').value;
const address_zip = this.stripeTest.get('address_zip').value;
let newCustomer: Customer = {
name: name,
phone: phone,
email: email
};
this.dataService.addCustomer(newCustomer)
.subscribe(() => {
console.log('Added');
})
this.stripeService
.createToken(this.card, {
name, address_state, address_city, address_line1,
address_zip
})
.subscribe(obj => {
if (obj) {
console.log('Token is --> ', obj.token.id);
// tslint:disable-next-line: no-unused-expression
this.http.post('http://localhost:3000/payme', {
token: obj.token.id,
receipt_email: email
}).subscribe(
(res) => {
console.log('The response from server is ', res);
console.log('Payment Done');
this.router.navigateByUrl('/thank-you');
alert(obj.token.id + obj + Customer + name + res);
},
(err) => {
console.log('The error is ', err);
alert(err);
})
} else {
// Error creating the token
console.log('Error comes ');
}
});
}
Oh wow, I feel like a moron. Jeez, shoot me haha. So, the this.http.post('http://localhost:3000/payme') needed to be just this.http.post(/payme) and needed to make changes in the data service as well, getting rid of the localhost:3000 part and just leaving the /add-customer. Wow, I can not believe I did not even see that! Wow. But it still worked on my computer even when deploying to Heroku. I guess because it was on my computer?

model driven form: validation not working as expected in Angular 2

I am using the model driven form like this .
Just like normal validations , i want that i show an error message if username and password are missing.
And Submit button should be disabled as long as username and password are not valid.
<div class="login">
<form #f="ngForm" (ngSubmit)="dologin(f)">
<div class="form-group">
<label for="username">Username</label>
<input id="username" type="text" class="form-control" name ="username" ngModel #username="ngModel">
<div [hidden]="username.valid" class="alert alert-danger"> Username is required.</div>
</div>
<div class="form-group">
<label for="password">Password</label>
<input id="password" type="password" class="form-control" name ="password" ngModel #password="ngModel">
<div [hidden]="password.valid" class="alert alert-danger"> Password is required.</div>
</div>
<button type="submit" [disabled]="username.length<0 || password.length<0" class="btn btn-primary" type="submit">Login</button>
</form>
</div>
i am seeing quite strange behaviour from validation div. Sometimes
it is showing "Password is required" and sometimes not.
i want to disable the submit button, until the form is valid .i tried
[disabled]="!f.valid"
but as i print it out f is always valid even
though i have not entered any data in username and password.
Component:
constructor(private router: Router,private authenticationService : AuthenticationService,private httpService:HttpService,private formBuilder:FormBuilder) {
this.form=formBuilder.group({
username:['',Validators.required],
password:['',Validators.required]
});
}
UPDATE
Can't bind to 'formGroup' since it isn't a known property of 'form'.
(" ][formGroup]="form"
(ngSubmit)="dologin(form.value)">
][formControl]="form.controls['password']">
[ERROR ->]
Username
[ERROR ->]
"): LoginComponent#4:8 No provider for NgControl ("
Password
[ERROR ->] ; Task: Promise.then ; Value:
Error: Template parse errors:(…) Error: Template parse errors: Can't
bind to 'formGroup' since it isn't a known property of 'form'. ("
][formGroup]="form"
(ngSubmit)="dologin(form.value)">
][formControl]="form.controls['password']">
[ERROR ->]
Username
[ERROR ->]
Thanks.
The way you have set up your HTML template is missing some key bits that actually ensure you have wired up the front end to the back end for a reactive form. What you have appears to be more in line with a template driven form mixed with model driven. In fact, the template you have posted will not even compile if you remove your FormsModule import.
To begin with remove your FormsModule import which is letting you mix the two different form types together. This will take us down a path where a strict Reactive Forms (aka model driven) implementation is required.
<form #f="ngForm" (ngSubmit)="dologin(f)"> will be changed to <form [formGroup]="form" (ngSubmit="dologin(form.value)"
Each of your inputs and warning divs will change from
<input id="username" type="text" class="form-control" name="username" ngModel #username="ngModel">
<div [hidden]="username.valid" class="alert alert-danger"> Username is required.</div>
To
<input id="username" type="text" class="form-control" name="username" formControlName="username">
The changes are because the ngModel attribute and #[name]="ngModel" are not supported in the model driven form, so instead you will use either formControlName or [formControl] syntax.
<div [hidden]="form.controls['username'].valid || form.controls['username'].pristine"
class="alert alert-danger"> Username is required.</div>
Finally, your submit button changes, note that you have type="submit" twice, from <button type="submit" [disabled]="username.length<0 || password.length<0" class="btn btn-primary" type="submit">Login</button>
To
<button type="submit" [disabled]="!form.valid" class="btn btn-primary">Login</button>
since we have successfully wired up the rest of the form the validation on the form group will now be correct
And here is a working plunker that you can play around with: https://plnkr.co/edit/Mu9vEYGB35SwUr9TEsPI?p=preview
Implementation without form builder
<form #loginForm="ngForm" (ngSubmit)="login()">
<md-input required type="email"
pattern="[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*#(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?"
placeholder="Email Address" #email="ngModel" name="email"
[(ngModel)]="loginModel.email"></md-input>
<div *ngIf="email.dirty && !email.valid && email.touched && email.errors" class="error-message">
<div *ngIf="email.errors.required">Email is required</div>
<div *ngIf="!email.errors.required && email.errors.pattern">This is not a valid email</div>
</div>
<md-input required type="password" placeholder="Password" #password="ngModel" name="password" [(ngModel)]="loginModel.password"></md-input>
<div *ngIf="password.dirty && !password.valid && password.touched && password.errors" class="error-message">
<div *ngIf="password.errors.required">Password is required</div>
</div>
<button ma-raised-button [disabled]="!loginForm.valid">
Login
</button>
</form>
Component:
ngOnInit() {
this.loginModel = {email: '', password: ''};
}
login() {
console.log(this.loginModel['email']);
console.log(this.loginModel['password']);
}