I have a form with input boxes. The input boxes are in both type text and numbers. And I have to validate them and I followed this tutorial and tried to validate them.
According to that if I have to validate a string then I can use the control group as follows.
constructor(fb: FormBuilder){
this.complexForm = fb.group({
'firstName' : [null, Validators.required],
'lastName': [null, Validators.compose([Validators.required, Validators.minLength(5), Validators.maxLength(10)])]
})
And the HTML code for this as follows...
<form [formGroup]="complexForm" (ngSubmit)="submitForm(complexForm.value)">
<div class="form-group">
<label>First Name:</label>
<input class="form-control" type="text" placeholder="John" [formControl]="complexForm.controls['firstName']">
</div>
<div class="form-group">
<label>Last Name</label>
<input class="form-control" type="text" placeholder="Doe" [formControl]="complexForm.controls['lastName']">
</div>
<div class="form-group">
<button type="submit" class="btn btn-default">Submit</button>
</div>
</form>
But I have to validate a number type input box also as the following example.
<form [formGroup]="complexForm" (ngSubmit)="submitForm(complexForm.value)">
<div class="form-group">
<label>Age:</label>
<input class="form-control" type="number" [formControl]="complexForm.controls['age']">
</div>
<div class="form-group">
<button type="submit" class="btn btn-default">Submit</button>
</div>
</form>
But Problem is there is no option for Validators to select what is minimum value and maximum value for the input.
are there someone has a solution for this issue?
Thanks.
The current version of angular 4 now has min and max validators
So it is as simple as writing
this.complexForm = fb.group({
age:[null,[Validators.required, Validators.min(5),Validators.max(90)]],
})
Remember it is on the #angular/forms repo
$: npm uninstall #angular/forms --save
$: npm install #angular/forms --save
There's no built-in Validator for min/max currently you can checkout the source for Validator to see whats available.
What you can do is create a custom validator function following the tutorials in the official docs
Example:
export function maxValue(max: Number): ValidatorFn {
return (control: AbstractControl): {[key: string]: any} => {
const input = control.value,
isValid = input > max;
if(isValid)
return { 'maxValue': {max} }
else
return null;
};
}
With this you can update your code to
constructor(fb: FormBuilder){
this.complexForm = fb.group({
'firstName' : [null, Validators.required],
'lastName': [null, Validators.compose([Validators.required, Validators.minLength(5), Validators.maxLength(10)])],
'age': [null, maxValue(60)]
})
If I get your issue right, try to include your validators' requirements also in HTML so your code will look as follows:
<form [formGroup]="complexForm" (ngSubmit)="submitForm(complexForm.value)">
<div class="form-group">
<label>Age:</label>
<input class="form-control" type="number" required minlength="5" maxlength="10" [formControl]="complexForm.controls['age']">
</div>
<div class="form-group">
<button type="submit" class="btn btn-default">Submit</button>
</div>
</form>
not sure if this is what you are looking for but HTML validators are always an option
<input type="number">
This won't allow users to put in anything but numbers and you don't have to parse it as it will be set as a number not a string.
Related
The problem
I use a form on a webpage where users fill in all sorts of details. There are 3 fields which generate the input for another field. That field gets generated like this: Firstname + Lastname + Date of birth. However, when a validation error is thrown on the form and the page reloads, the generated input isn't the expected format anymore. Only the Date of birth is then in that input.
It looks like it isn't initializing the Firstname + Lastname field anymore after a validation error is thrown on the page. Any suggestions on how to make it so that the fields gets initialized constantly? Or is there maybe a better way to handle this?
This is the code I use for the generated input
window.onload = function() {
let studentNoField = document.getElementById('input_7_9');
let enteredDetails = {
name: '',
lastname: '',
date: ''
};
/* set value in the third input: Studentnummer */
function generateInput() {
let studentNumber = Object.values(enteredDetails).join('').toLowerCase();
studentNoField.value = studentNumber;
}
/* event listener for first input: Voornaam */
document.getElementById('input_7_1').addEventListener('input', function(event) {
enteredDetails.name = event.target.value.replace(/\s/g, '').slice(0, 8);
generateInput();
});
/* event listener for second input: Achternaam */
document.getElementById('input_7_25').addEventListener('input', function(event) {
enteredDetails.lastname = event.target.value.replace(/\s/g, '').slice(0, 8);
generateInput();
});
/* event listener for second input: Date */
document.getElementById('input_7_3').addEventListener('input', function(event) {
enteredDetails.date = event.target.value.replace(/-/g, '').slice(0, 4);
generateInput();
});
/* Get selected training and format it properly for the PDF */
jQuery('#input_7_23').change(function(e) {
var optionChange = jQuery('#input_7_23 option:selected').text().toUpperCase();
jQuery('#input_7_58').val(optionChange);
});
}
<html>
<body>
<form method="post" enctype="multipart/form-data" id="gform_7" action="/budget/" _lpchecked="1">
<div>
<div id="gform_fields_7">
<div id="field_7_9">
<label for="input_7_9">Studentnummer
<input name="input_9" id="input_7_9" type="text" value="" maxlength="20" aria-required="true" aria-invalid="false">
</div>
</div>
<div id="field_7_1">
<label for="input_7_1">Voornaam</label>
<div><input name="input_1" id="input_7_1" type="text" value="" aria-required="true" aria-invalid="false"> </div>
</div>
<div id="field_7_25">
<label for="input_7_25">Achternaam</label>
<div><input name="input_25" id="input_7_25" type="text" value="" aria-required="true" aria-invalid="false"> </div>
</div>
<div id="field_7_3">
<label for="input_7_3">Geboortedatum</label>
<div>
<input name="input_3" id="input_7_3" type="text" value="" placeholder="dd-mm-yyyy" aria-describedby="input_7_3_date_format" aria-invalid="false" aria-required="true">
<span id="input_7_3_date_format">DD dash MM dash JJJJ</span>
</div>
</div>
</div>
</div>
<div>
<input type="submit" id="gform_submit_button_7" value="Versturen" onclick="if(window["gf_submitting_7"]){return false;} window["gf_submitting_7"]=true; " onkeypress="if( event.keyCode == 13 ){ if(window["gf_submitting_7"]){return false;} window["gf_submitting_7"]=true; jQuery("#gform_7").trigger("submit",[true]); }">
</div>
</form>
</body>
</html>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Any help or suggestions is appreciated.
There were a few non-existing ids referenced in your code. In the following snippet I have tried to "correct" these errors, but I also went further: I removed all repetitions, thereby following the DRY principle "Don't repeat yourself". The "input"-event listener now works for all elements of the inps array. There is, however one differentiation: the first two elements are limited to 8 characters while the date is limited to 4: .slice(0,i<2?8:4).
const [stNr, ...inps]=[9, 1, 25, 3].map(n=> document.getElementById(`input_7_${n}`));
inps.forEach(inp=>inp.addEventListener("input",()=>
stNr.value=inps.map((el,i)=>
el.value.replace(/[\s-]/g,"").slice(0,i<2?8:4).toLowerCase()
).join(""))
)
<form method="post" enctype="multipart/form-data" id="gform_7" action="/budget/" _lpchecked="1">
<div>
<div id="gform_fields_7">
<div id="field_7_9">
<label for="input_7_9">Studentnummer</label>
<input name="input_9" id="input_7_9" type="text" value="" maxlength="20" aria-required="true" aria-invalid="false">
</div>
</div>
<div id="field_7_1">
<label for="input_7_1">Voornaam</label>
<div><input name="input_1" id="input_7_1" type="text" value="" aria-required="true" aria-invalid="false"> </div>
</div>
<div id="field_7_25">
<label for="input_7_25">Achternaam</label>
<div><input name="input_25" id="input_7_25" type="text" value="" aria-required="true" aria-invalid="false"> </div>
</div>
<div id="field_7_3">
<label for="input_7_3">Geboortedatum</label>
<div>
<input name="input_3" id="input_7_3" type="text" value="" placeholder="dd-mm-yyyy" aria-describedby="input_7_3_date_format" aria-invalid="false" aria-required="true">
<span id="input_7_3_date_format">DD dash MM dash JJJJ</span>
</div>
</div>
</div>
</div>
<div>
<input type="submit" id="gform_submit_button_7" value="Versturen">
</div>
</form>
I removed your jQuery statements at the end of your script, as they referred to non-existent ids. These statements can definitely also be re-written in Vanilla JS, if necessary.
And, as #CherryDT already mentioned: there is no validation code visible here. If it happens on the server then it is the server's responsibility to produce a suitable response that allows the client to render the page with the previously (possibly annotated) content.
I am trying to populate the city based on zip code field. I am able to do the aforementioned using normal html tag with the (keyup) event binding, but with css I have to use (keyup.enter) which is able to invoke the function which is bind with event call but it is not letting the patchValue() function work properly. Here's a snippet of my code :-
editor.component.ts
profileForm = this.fb.group({
name: ['', Validators.required],
address: this.fb.group({
zip: ['', Validators.required],
city: ['', Validators.required]})
});
func(event: any){
this.profileForm.patchValue({
address:{
city: this.getCity(event.value)
}
})
}
getCity = (theCurrentZip: any) => {
return Object.keys(this. zipCode).filter(z => {
return this.zipCode[z].includes(theCurrentZip)
})[0]
}
zipCode: any = {
"A": ["1", "2" ],
"B":[ "3", "4",]
};
editor.component.html
<div>
<form (ngSubmit)="onSubmit()" #myForm="ngForm" class="profileForm">
<mat-form-field required>
<input matInput id="name" placeholder="Contact Name" name="name" [(ngModel)]="name">
</mat-form-field >
<div formGroupName="address">
<mat-form-field >
<input matInput id="zip" (keyup)="func($event.target)" placeholder="Zip" name="zip" [(ngModel)]="zip" >
</mat-form-field >
<mat-form-field required>
<input matInput id="city" placeholder="City" name="city" [(ngModel)]="city" >
</mat-form-field >
</div>
<p>
<button type="submit" mat-raised-button color = "primary">Submit</button>
</p>
</form>
</div>
As far as I can see you're currently mixing the reactive and template-driven approach of Angular forms.
The reactive approach is used in your editor.component.ts and the template-driven one in your editor.component.html.
Since your HTML is template-driven it is not necessary to call patchValue because the form and the form controls are not bound to the HTML. So, to update your HTML you need to adjust your implementation of func like following
func(event) {
this.city = this.getCity(event.target.value);
}
My suggestion for you would be that you choose one of those approaches and don't mix them (if it isn't absolutely necessary). Also, you should use more meaningful function names than func ;)
I have this very basic form, that I am trying to enable/disable a button on if the form is/isn't filled out. When using this form, the button is always enabled even when I add/remove data in the input fields.
<form #activationForm="ngForm">
<input type="email" ([ngModel])="email" placeholder="{{'activation.email' | translate}}" required>
<input type="text" ([ngModel])="code" placeholder="{{'activation.code' | translate}}" required>
<button [disabled]="activationForm.form.invalid" class="button button--success" translate="activation.activate" (click)="activate()"></button>
</form>
#Component({
selector: 'app-activation',
templateUrl: './activation.component.html',
styleUrls: ['./activation.component.scss']
})
export class ActivationComponent {
public email: string = '';
public code: string = '';
}
Why is the button always enabled?
I would suggest using Reactive Forms. There's a cleaner way to do what you want.
Here's an example:
App Module:
import { ReactiveFormsModule } from '#angular/forms';
imports: [
...
ReactiveFormsModule
]
Component TypeScript:
this.activationForm = new FormGroup({
email: new FormControl('', [Validators.required, Validators.email]),
code: new FormControl('', [Validators.required])
});
Component Template:
<form [formGroup]="activationForm" (ngSubmit)="onSubmit($event)">
<input formControlName="email" />
<input formControlName="code" />
<button [disabled]="activationForm.invalid">Submit</button>
</form>
If you're using Angular Material, you can then leverage mat-hint or mat-error to assist with validation messages/hints. You can also make your own, of course.
The bonus with Reactive Forms is that everything is accessible. For example, you can do 'dynamic stuff' with your form by accessing their FormControl with this.activationForm.get('email'). The same goes for the form itself (ex: this.activationForm.setValue(...)). It's pretty powerful and in my opinion, much more preferable than NgForm.
to set two way data binding with ngModel it like this [(ngModel)]="email" also you have to provide name attribute
<form #activationForm="ngForm">
<input type="email" name="email" [(ngModel)]="email" placeholder="{{'activation.email' }}" required>
<input type="text" name="code" [(ngModel)]="code" placeholder="{{'activation.code' }}" required>
<button [disabled]="activationForm.form.invalid" class="button button--success" translate="activation.form.invalid" (click)="activate()">create</button>
</form>
demo 🚀
So I have implemented a custom form validation for changing password. It works good, but I'm getting an error message, when the new password is typed, but the confirmPassword is not touched yet, because the two differ. I want to use validation.dirty and touched, to prevent this behaviour, but implementing it as it follows, Im getting the following error:
There is no directive with "exportAs" set to "ngModel" ("mGroup]="password" type="password" class="form-control" id="confirmPassword" name="confirmPassword" [ERROR ->]#confirmPassword="ngModel">
<form (ngSubmit)="changePassword()" [formGroup]="password">
<div class="form-group">
<label for="oldPassword">Old Password</label>
<input type="password" class="form-control" id="oldPassword" name="oldPassword">
</div>
<div class="form-group">
<label for="password">New Password</label>
<input formControlName="password" [formGroup]="password" type="password" class="form-control" id="password" name="password">
</div>
<div class="form-group">
<label for="confirmPassword">Confirm Password</label>
<input formControlName="confirmPassword" [formGroup]="password" type="password" class="form-control" id="confirmPassword" name="confirmPassword" #confirmPassword="ngModel">
<div class="alert alert-danger" *ngIf="password.controls.confirmPassword.errors?.MatchPassword && (confirmPassword.touched || confirmPassword.dirty)">Password not match</div>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
I tought that #confirmPassword="ngModel" will solve this, on the confirmPassword input, but that's what causing the error.
What am I doing wrong here?
Here is a working StackBlitz using "ReactiveForms" approach.
The first thing you are not doing right is mixing different form approaches.
If you want to use model driven forms (using FormsModule), then you need to get rid of the ReactiveFormsModule directives: formControlName, formGroup, etc.
Also, you missed the [(ngModel)]="myValue" in your code, which is required for #ref="ngModel" to work.
if you want to stick to ReactiveFormsModule, you need to get rid of ngModel code. Also, since you are using formGroup on the parent form, you DON'T need to specify it on the children:
<form [formGroup]="parent>
<input formControlName="child">
</form>
would bind this form to:
parent = new FormGroup({
child: new FormControl(null),
});
I also took the liberty to suggest a validation approach, where the "future" password field has MinLength and other validators, while the whole password FormGroup is responsible for making sure that your passwords match.
Use a Custom Validator instead
example:
ngOnInit() {
this.myForm = new FormGroup({
password1: new FormControl(),
password2: new FormControl('',this.comparePassword.bind(this)),
})
comparePassword(control: FormControl): { [key: string]: boolean } {
if (control.parent){//
const password1 = control.parent.value['password1'];
const password2 = control.value;
if(password1 === password2){
return {passwordMismatch:true}
}
}
return null;
}
HTML
<form [formGroup]="myForm">
<div class="form-group">
<label for="password1">password1</label>
<input type="password"
class="form-control" formControlName="password1">
</div>
<div class="form-group">
<label for="password2">password2</label>
<input type="password" class="form-control"
formControlName="password2">
</div>
<div *ngIf="myForm.get('password2').errors && myForm?.get('password2')?.errors?.passwordMismatch">
Password Mismatch
</div>
</form>
Live Demo
I have a template form, which I wrote from guides and they don't really work though.
I have several of models:
export class User {
constructor(public userId?: UserId,
public firstName?: String,
public lastName?: String,
public address?: Address) {
}
}
export class Address {
constructor(
public street?: String,
public city?: String,
public zipCode?: String) {
}
}
I have component:
Component({
templateUrl: 'user.html'
})
export class MyComponent implements OnInit, OnDestroy{
user: User;
userForm: NgForm;
ngOnInit(): void {
}
And page itself:
<form novalidate #userForm="ngForm">
<div class="form-group">
<input required minlength="4" type="text"
id="firstName"
[(ngModel)]="user.firstName" name="firstName">
<small *ngIf="!firstName.valid">Not valid!</small>
</div>
<div class="form-group">
<input required ng-minlength="4" type="text"
id="lastName"
[(ngModel)]="user.lastName" name="lastName">
</div>
<div ngModelGroup="user.address">
<div class="form-group">
<input required ng-minlength="4"
type="text"
id="address-house"
[(ngModel)]="user.address.address1" name="address.address1">
</div>
<div class="form-group">
<div class="form-group">
<input required ng-minlength="4"
type="text"
id="zipCode"
[(ngModel)]="user.address.zipCode" name="address.zipCode">
</div>
<div>
<input required ng-minlength="4"
type="text"
lass="form-control input-lg"
id="city"
[(ngModel)]="user.address.city" name="address.city">
</div>
</div>
</div>
<button type="button" (click)="checkAndProceed()">Continue</button>
</div>
</form>
The only thing I want to do is to add validation - that's all. None of the guides helped. Can we do in-html validation or ts validation? It would be nice to call validation when clicking 'Continue' button and making it valid if it is so.
In this case of validation I additionally get console error:
Cannot read property 'valid' of undefined
There are lots of attributes on input elements. We have name, Id, and template reference variable. Your code is missing the template reference variable. It is the template reference variable that holds onto the reference to the element and has the valid, dirty, and other flags associated with it.
For example, change your code to include a template reference variable like this:
<div class="form-group">
<input required minlength="4" type="text"
id="firstName"
[(ngModel)]="user.firstName" name="firstName"
#firstNameVar="ngModel">
<small *ngIf="!firstNameVar.valid">Not valid!</small>
</div>
Notice the #firstNameVar. That is the template reference variable. It can be named the same thing as your Id and name attributes. I just named it something different so it could be readily distinguished between the other two attributes.
Notice also that the *ngIf is then changed to use firstNameVar.valid
For more information on template reference variables, see this: https://angular.io/guide/template-syntax#ref-vars