I'm using Aurelia and Typescript to build a web page. I have a simple login form and I'd like to validate the user email and password.
I am using Aurelia validation and by default it validates the content of my input each time it changes, which can be annoying. (ex: getting an error message saying that the email is not valid when you're not even done typing it). So I'd like to do the validation onBlur instead (when the focus on the input is lost) and when the user clicks on the Login button.
Here's my code:
login.html
<template>
<section>
<div class="container col-lg-12">
<div class="col-md-4 col-md-offset-4 centered">
<h2 t="signin_sign_in"></h2>
<form role="form" submit.delegate="login()" validate.bind="validation">
<br if.bind="errors" />
<div if.bind="errors" repeat.for="error of errors" class="alert alert-danger">
<h4 repeat.for="message of error">${message}</h4>
</div>
<div class="form-group">
<label class="control-label" t="signin_email_address"></label>
<input type="text" class="form-control" value.bind="email">
</div>
<div class="form-group">
<label class="control-label" t="signin_password"></label>
<input type="password" class="form-control" value.bind="password">
</div>
<button type="submit" class="btn btn-primary" t="signin_sign_in"></button>
</form>
</div>
</div>
</section>
</template>
login.ts
#autoinject()
export class Login {
email: string;
password: string;
router: Router;
application: ApplicationState;
accountService: AccountService;
errors;
validation;
i18n: I18N;
constructor(router: Router, application: ApplicationState, accountService: AccountService, validation: Validation, i18n: I18N) {
this.router = router;
this.application = application;
this.accountService = accountService;
this.i18n = i18n;
this.errors = [];
this.validation = validation.on(this)
.ensure('email')
.isNotEmpty()
.isEmail()
.ensure('password')
.isNotEmpty()
.hasLengthBetween(8, 100);
}
navigateToHome(): void {
this.router.navigate("/welcome");
}
login(): void {
var __this = this;
this.validation.validate()
.then(() => this.accountService.signin(this.email, this.password, this.rememberMe)
.then(result => {
// Do stuff
})
.catch(error => {
// Handle error
}
}));
}
}
My first thought was to add
& updateTrigger:'blur':'paste'
to my binding in the HTML, but it doesn't work. The binding is updated correctly when focus is lost but the validation stops working. There's no error in the Chrome debug console either.
Any idea on how to do this? Is is possible at all?
There's different binding behaviours you can use to tell when the validation should trigger. You can read more about them in the Aurelia docs on validation.
From the docs;
The validate binding behavior obeys the associated controller's
validateTrigger (blur, change, changeOrBlur, manual). If you'd like to
use a different validateTrigger in a particular binding use one of the
following binding behaviors in place of & validate:
& validateOnBlur: the DOM blur event triggers validation.
& validateOnChange: data entry that changes the model triggers validation.
& validateOnChangeOrBlur: DOM blur or data entry triggers validation.
& validateManually: the binding is not validated
automatically when the associated element is blurred or changed by the
user.
I recently encountered this exact use case and I solved it using Aurelia's built in debounce feature.
<input type="text" class="form-control" id="email" placeholder="Email"
value.bind="email & validateOnChangeOrBlur & debounce:600 ">
600ms is an arbitrary value, but you can always play around with it as needed.
Related
I have a [formGroup] in Angular version8 app.
I am trying to validate the form based on these simple rules:
if a file is uploaded (includes just choosing any file with the button) then the Reason input is NOT required, else
if a file has not been chosen (ie 'choose file' has no value) then a Reason must be provided for form validation to pass.
File upload is optional if a Reason text is provided.
How do I check inside the Reason textArea that a file has been chosen? I simply can't seem to write a validation statement using that ngClass
template:
<form [formGroup]="dialogForm">
<div class="row">
<div class="lbl-col">
<label for="reason">Reason<span class="required-star">*</span></label>
</div>
<div class="col">
<textarea
id="reasonForChange"
formControlName="reasonForChange"
name="reasonForChange"
type="text"
[(ngModel)]="selectedReasonText"
[ngClass]="{invalid: dialogForm.get('File').invalid}"
>
</textarea>
</div>
</div>
<div class="row">
<div class="lbl-col"></div>
<div class="col">
<input
type="file"
id="reasonFile"
formControlName="File"
(change)="onFileChange($event)"
/>
</div>
</div>
</form>
[...]
// submit button
<button
class="primary-button"
label="update status"
(click)="update()" // a POST nothing more.
[disabled]="dialogForm.invalid"
></button>
and in my controller:
constructor(
private fb: FormBuilder
) {
this.dialogForm = this.fb.group({
chooseStatus: [null, Validators.required],
reasonForChange: [null, Validators.required],
File: [null],
});
Using [ngClass]="{invalid: dialogForm.get('File').invalid}" or variations of it, it doesn't appear to check formControlName="File" but instead checks itself, 'reasonforChange'.
I have also tried the crossValidation template from Ang docs.
Extend the Formbuilder:
constructor(
private fb: FormBuilder
) {
this.dialogForm = this.fb.group({
chooseStatus: [null],
reasonForChange: [null],
File: [null],
}, {validators: crossFormValidator}
);
and provide
export const crossFormValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
const reasonForChange = control.get('reasonForChange');
const file= control.get('File');
return file && reasonForChange && attachment.value !== '' ? { crossValid: true } : null;
};
but that errors on this line in my template
<div *ngIf="dialogForm.errors?.['crossValid'] && (dialogForm.dirty)" class="form-validation-warning">
<span class="warning-label">* Required</span>
</div>
at Parser Error: Unexpected token [, expected identifier or keyword at column 20 in [dialogForm.errors?.['crossValid'] && (dialogForm.dirty)]
Even though that is taken almost verbatiim from the angular docs at https://angular.io/guide/form-validation#adding-cross-validation-to-template-driven-forms
Well, there is a simple approach for that, to validate different fields in angular.
Create a getter function in .ts file as follow:
get If() {
return this.dialogForm.controls;
}
This function is returning the controls of each field in the form.
Now, apply this function on the form fields as follow:
<form [formGroup]="dialogForm">
<div class="row">
<div class="lbl-col">
<label for="reason">Reason<span class="required-star">*</span></label>
</div>
<div class="col" *ngIf="If.File.valid || !If.File.untouched">
<textarea
id="reasonForChange"
formControlName="reasonForChange"
name="reasonForChange"
type="text"
[(ngModel)]="selectedReasonText"
[ngClass]="{invalid: dialogForm.get('File').invalid}"
>
</textarea>
</div>
</div>
<div class="row">
<div class="lbl-col"></div>
<div class="col">
<input
type="file"
id="reasonFile"
formControlName="File"
(change)="onFileChange($event)"
/>
</div>
</div>
// submit button
<button
class="primary-button"
label="update status"
(click)="update()" // a POST nothing more.
[disabled]="dialogForm.invalid"
></button>
</form>
This way, it is going to work as expected
I have my chat and I dont want people to send empty message so I would like that my input become required. Thanks for your help.
I tried to put "required='required'" in the input line, I also tried veeValidate but it broke my chat when I use it, I also tried to put "Required = true" in Props and data but without a good result
This is ChatForm.vue
<template>
<div class="input-group" >
<input id="btn-input" type="text" name="message" class="form-control input-sm" placeholder="Ecrire..." v-model="newMessage" #keyup.enter="sendMessage">
<span class="input-group-btn">
<button class="btn btn-primary btn-sm" id="btn-chat" #click="sendMessage">
✓
</button>
</span>
</div>
</template>
<script>
export default {
props: ['user'],
data() {
return {
newMessage: '',
}
},
methods: {
sendMessage() {
this.$emit('messagesent', {
user: this.user,
message: this.newMessage
});
setTimeout(function() {
const messages = document.getElementById('mess_cont');
messages.scrollTop = messages.scrollHeight;
}, 200);
this.newMessage = '';
}
}
}
</script>
And this is my form in the app.blade.php
<div id="app" class="container-chat">
<div class="row">
<div class="col-md-12 col-md-offset-2">
<div class="col-md-12 col-md-offset-2">
<div class="panel-body panel-content" id="mess_cont">
<chat-messages id="mess" :messages="messages" :currentuserid="{{Auth::user()->id}}"></chat-messages>
</div>
<div class="panel-footer">
<chat-form
v-on:messagesent="addMessage"
:user="{{ Auth::user() }}"
></chat-form>
</div>
</div>
</div>
</div>
</div>
Try to change your ChatForm.vue like this:
<template>
<form #submit.prevent="sendMessage">
<div class="input-group" >
<input id="btn-input" type="text" name="message" class="form-control input-sm" placeholder="Ecrire..." v-model="newMessage" required>
<span class="input-group-btn">
<button class="btn btn-primary btn-sm" type="submit" id="btn-chat">
✓
</button>
</span>
</div>
</template>
You are not treating the input in the correct way, the input which is required needs to be inside a form and the required keyword will prevent the form submission if the input field is empty.
There are a few things I would do differently.
1/ Wrap your chat form in a tag, and execute the sendMessage() method on submit. This will give your users a nicer experience, as they can just to submit the message.
2/ Convert the button into a submit button so it triggers the form.submit event.
3/ You can easily disable the button by checking whether newMessage has contents. I don't think you need vee validate or anything else to achieve this; for something as simple as a chat form, your user doesn't need much more feedback than seeing a disabled button to realise (s)he needs to write something first.
4/ in the addMessage method you can just check the contents of newMessage and not do anything when it's empty. This is perfectly fine because you already hinted the user by disabling the button too.
I think this is a subtle way where you guide your user, but don't overdo it.
Please add name attributes to all of your form elements. Some of the element in my form had name attribute and some didn't. Element which had name attributes worked correctly but the one's which didn't had name failed.
I have a component with a reactive form in it like so...
form component.html
<form [formGroup]="form" class="do-form">
<div formGroupName="dot">
<div class="do-form__container">
<div class="do-form__group">
<label for="day">Day</label>
<input id="day" type="number" placeholder="XX" class="do-form__group__control" formControlName="day" />
</div>
<div class="do-form__group">
<label for="month">Month</label>
<input id="month" type="number" placeholder="XX" class="do-form__group__control" formControlName="month" />
</div>
<div class="do-form__group">
<label for="year">Year</label>
<input id="year" type="number" placeholder="XXXX" class="do-form__group__control" formControlName="year" />
</div>
</div>
<div class="do-form__errors" *ngIf="isValid()">
<p>Please enter a valid date</p>
</div>
</div>
</form>
and in my form.component.ts
form = this.fb.group({
dot: this.fb.group({
day: ['',
[
Validators.required,
Validators.min(1),
Validators.max(31)
]],
month: ['',
[
Validators.required,
Validators.min(1),
Validators.max(12)
]],
year: ['2018',
[
Validators.required,
Validators.min(1918),
Validators.max(2018)
]]
})
});
isValid() {
return (
!this.form.valid &&
this.form.get('dot.day').touched &&
this.form.get('dot.month').touched &&
this.form.get('dot.year').touched
);
}
Now I have a separate page (app.component.html) like so
<app-form #formTool></app-form>
<button class="full-width-btn" (click)="next(); form.sendResult();">SEND</button>
app.component.ts
import { formComponent } from '../form-component/form-component.ts'
export ...
#ViewChild(formComponent) form;
Now basically I want to disable the send button until the form in the app form component is valid.
I'm not entirely sure how to do this. I thought about storing a valid event on a shared server but I'm not sure how I can store a valid event in a service. I saw that with non-reactive forms you can just have a button that uses the same ngModel but once again not sure if that would work in this case.
Any help would be appreciated!
EDIT
I have tried [disabled]="form.invalid" and [disabled]="!isValid()" but I am still able to click the button
I have also tried using [disabled]=!form.isValid() and [disabled]=!form.form.isValid()
Thanks
You are really close. Here is the only thing you need to add:
<app-form #formTool></app-form>
<button class="full-width-btn" [disabled]="!isValid()" (click)="next(); form.sendResult();">SEND</button>
In form component you could define an event #Output formValid = new EventEmitter().
Then you can listen to changes in input fields (on keypress or so) and on any change check the validity and if the form is valid, emit an event: formValid.emit().
In the app component define formIsValid = false, and on the app-form element you can listen to the formValid event:
< app-form (formValid)="formIsValid = true">
(or some function in app.component.ts instead of the inline code).
And finally on button
< button [disabled]="!formIsValid">
I am new to angular let say, i have reactive form like follow
ngOnInit() {
this.registerFormGroup = this.formBuilder.group({
email: [ '', Validators.compose([Validators.required, Validators.email])],
password: this.formBuilder.group({
first: [ '', Validators.required ],
second: [ '', Validators.required, ]
})
});
}
and my angular template looks like follow
<div class="container">
<form [formGroup]="registerFormGroup"
(ngFormSubmit)="registerUser(registerFormGroup.value)" novalidate>
<div class="form-group" >
<label for="email">Email</label>
<input type="email" formControlName="email" placeholder="Enter Email"
class="form-control">
</div>
<div *ngIf="!registerFormGroup.get('password').get('first').valid"
class="alert alert-danger">
</div>
<div class="form-group text-center">
<button type="submit" class="btn btn-success btn-lg"
[disabled]="!registerFormGroup.valid">Submit</button>
</div>
For example, email field has two validations such as required and email type validate so depends upon the error I have to display error message so in my template am using like
<div *ngIf="!registerFormGroup.get('email').valid && (registerFormGroup.get('email').touched)"
class="alert alert-danger">
</div>
Instead of adding same registerFormGroup.get('email') again and again i trying to create template expression like #emailForm="registerFormGroup.get('email')" in
<input type="email" formControlName="email" placeholder="Enter Email" class="form-control" #emailForm="registerFormGroup.get('email')">
so that i can use use like
<div *ngIf="!emailForm.valid" class="alert alert-danger">
</div>
but i am getting error like
compiler.es5.js:1690 Uncaught Error: Template parse errors:
There is no directive with "exportAs" set to "registerFormGroup.get('email')" ("l>
]#emailForm="registerFormGroup.get('email')">
what mistake i made??
You can create common function to access form like below:
validateFormControl(controName: string) {
let control = registerFormGroup.get(controName);
return control.invalid && control.touched;
}
In Templete use this call wherever you need, you just need to pass your control name in function, you can modify this function as per your need as well and you do not need to use form.get all the time. This makes your template more cleaner and performance efficient.
<div *ngIf="validateFormControl('email')"
class="alert alert-danger">
error message
</div>
<div *ngIf="validateFormControl('password')"
class="alert alert-danger">
error message
</div>
Create a method on the component that returns if the form is valid or not and return it in the component
checkError(){ // either you can pass the form from the template or use the component for decleration
return registerFormGroup.get('email').valid;
}
In the template call
<div *ngIf="checkError()" class="alert alert-danger">
// Always make sure not to use controls in the template it will result in AOT compilation error
</div>
try this :
component.html
<div class="container">
<form [formGroup]="registerFormGroup"
(ngFormSubmit)="registerUser(registerFormGroup.value)" novalidate>
<div class="form-group" [ngClass]="{'has-error':!registerFormGroup.controls['email'].valid}">
<label for="email">Email</label>
<input type="email" formControlName="email" placeholder="Enter Email"
class="form-control">
<p class="alert alert-danger" *ngIf="registerFormGroup.controls['email'].dirty && !registerFormGroup.controls['email'].valid">Invalid email address</p>
</div>
<div class="form-group text-center">
<button type="submit" class="btn btn-primary" [disabled]="!registerFormGroup.valid">Submit</button>
</div>
</form>
</div>
component.ts
import { FormGroup, FormBuilder, Validators } from '#angular/forms';
export class AppComponent implements OnInit {
registerFormGroup: any;
constructor(
private formBuilder: FormBuilder
) {}
ngOnInit() {
this.registerFormGroup = this.formBuilder.group({
email: [null , Validators.compose([Validators.required, Validators.email])]
});
}
}
Angular team strongly discourage from using functions outputs in templates due to change detection strategy. You might be interested in following solution, using ngForm directive:
<div class="container">
<form [formGroup]="registerFormGroup"
(ngFormSubmit)="registerUser(registerFormGroup.value)" novalidate>
<div class="form-group" >
<label for="email">Email</label>
<input type="email" formControlName="email" placeholder="Enter Email "
class="form-control" #email="ngForm">
</div>
<div *ngIf="email.invalid"
class="alert alert-danger">
</div>
It's still a hush to copy-paste it in template, but at least you might have direct reference to control via it's template variable. I personally go with controller getter function still, but I'm posting this answer for sake of answer completeness.
Although I agree with the accepted answer (you should have a dedicated method in your form component that will encapsulate the validation process)
sometimes you need a quick check in the template:
The trick is to expose the formGroup from your component and use it like so:
Template:
<input id="name" class="form-control"
formControlName="name" required
[class.is-invalid]="
f.name.invalid &&
(f.name.touched ||
f.name.touched.dirty)">
Component:
//easily access your form fields
get f() { return this.checkoutForm.controls; }
I was also looking for a more elegant solution than calling form.get multiple times. Here is what I come up with using ngif
<div class="col-sm-6" *ngIf="form.get('sponsor') as sponsor">
<input type="text" formControlName="sponsor" id="sponsor" class="form-control" name="sponsor"
[class.is-invalid]="sponsor.errors && sponsor.touched">
</div>
using the as feature on ngIf to create a template variable
I'm trying to validate that the password and ConfirmPassword fields are the same in my form, but when I add the validation provided by other posts on SO (aside from changing ControlGroup to FormGroup) I keep getting
ERROR: Cannot find control with unspecified name attribute
BUT, when I don't validate using the "matchingPassword" group below and just use the Validators.required syntax, it works just fine.
I don't know why it's throwing that error. Has anyone else worked through this? I'm currently building on Angular 4 Distro.
constructor(
private accountService: accountService,
fb: FormBuilder,
) {
this.changePWForm = fb.group({
'CurrentPassword' : [null, Validators.required],
'SecurityQuestions' : [null, Validators.required],
'SecurityQuestionAnswer' : [null, Validators.required],
'matchingPassword': fb.group({
'NewPassword' : [null, Validators.compose([Validators.pattern(this.strongPW), Validators.required])],
'ConfirmPassword' : [{disabled: true}, Validators.required],
}, {validator: this.equalPasswords})
})
}
equalPasswords(group: FormGroup){
//When I use the syntax above, it never makes it here.
var valid = false;
for (var name in group.controls) {
var val = group.controls[name].value
}
if (valid) {
return null;
}
return {
areEqual: true
};
}
Here's My HTML Template
<form [formGroup]="changePWForm" (ngSubmit)="updatePW(changePWForm.value)" *ngIf="securityQuestions">
<div class="form-group">
<label>Current Password:</label>
<input type="text" [(ngModel)]="changePWData.CurrentPassword" [formControl]="changePWForm.controls['CurrentPassword']">
</div>
<div class="form-group">
<label>New Password:</label>
<input type="text" [(ngModel)]="changePWData.NewPassword" [formControl]="changePWForm.controls['NewPassword']">
<small *ngIf="!changePWForm.controls.NewPassword.valid && !changePWForm.controls.NewPassword.pristine">You need a secure password.</small>
</div>
<div class="form-group" >
<label>Confirm New Password:</label>
<input type="text" [(ngModel)]="changePWData.ConfirmPassword" [formControl]="changePWForm.controls['ConfirmPassword']">
</div>
<div class="form-group">
<label>Security Question:</label>
<select #select type="text" [(ngModel)]="selectedSecurityQuestion" [formControl]="changePWForm.controls['SecurityQuestions']" class="select">
<option *ngFor="let question of securityQuestions" [ngValue]="question">{{question.SecurityQuestionText}}</option>
</select>
</div>
<div class="form-group">
<label>Security Question Answer: </label>
<input type="text" [(ngModel)]="securityQuestionAnswer" [formControl]="changePWForm.controls['SecurityQuestionAnswer']">
</div>
<div *ngIf="processing">
<div class="spinner">
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
</div>
<button *ngIf="!processing" type="submit" [disabled]="!changePWForm.valid">Change Address</button>
</form>
The problem is that you've a nested FormGroup (matchingPassword).
So, you must wrap the controls of this nested group using a <div>, for example.
Wrap your password controls (NewPassword and ConfirmPassword) in a element, like this:
<form [formGroup]="changePWForm" ...>
...
<div formGroupName="matchingPassword">
<!-- you can also use [formGroup] if you want -->
<!-- Put the content of password controls here -->
</div>
...
</form>
Besides having the problem stated by developer033, that you are missing the formGroupName:
<div formGroupName="matchingPassword">
<!-- you can also use [formGroup] if you want -->
<!-- Put the content of password controls here -->
</div>
.. I also didn't really understand the custom validator and it didn't function correctly for me. Also noticed for some reason the validator don't even fire when marking form controls like:
[formControl]="changePWForm.controls['NewPassword']"
I can't really say why, but I also prefer the more "cleaner" version:
formControlName="NewPassword"
So changing those would make the custom validator fire.
Then to the custom validator, this is the way I do it. Also notice I've changed areEqual: true to notEqual:true to better describe what is actually going on, since when we return null it means that the passwords match, and if we return something else than null, it means that we want to mark that the passwords do not match.
equalPasswords = (control: AbstractControl): {[key: string]: boolean} =>{
const newPassword = control.get('NewPassword');
const confirmPassword = control.get('ConfirmPassword');
if (!newPassword || !confirmPassword) {
return null;
}
return newPassword.value === confirmPassword.value ? null : { notEqual: true };
}
Here's a DEMO with a shortened version of your code.