Angular FormGroup Error doesnt get displayed - javascript

I have the problem that my App doesnt diplay the errors.
This is my html:
<ion-item>
<ion-label floating style="margin-left: 20px;">Username</ion-label>
<ion-input type="text" name="username" formControlName="username" [(ngModel)]="registerCredentials.username" required></ion-input>
</ion-item>
<ion-item *ngIf="!registerForm.get('username').valid && (registerForm.get('username').dirty)">
<p style="color: white;" *ngIf="registerForm.get('username').hasError('taken')">funktioniert</p>
<p style="color: white;" *ngIf="registerForm.get('username').hasError('min')">funktioniert</p>
<p style="color: white;" *ngIf="registerForm.get('username').hasError('max')">funktioniert</p>
<p style="color: white;" *ngIf="registerForm.get('username').hasError('latein')">funktioniert</p>
</ion-item>
this is my ts:
registerForm: FormGroup;
this.registerForm = formBuilder.group({
username: ['', Validators.compose([Validators.minLength(3), Validators.maxLength(15), Validators.pattern('^[A-Za-z0-9]+$'), UsernameValidator.checkUsername, Validators.required])],
email: ['', Validators.compose([Validators.pattern('^[a-zA-Z0-9.!#$%’*+/=?^_`{|}~-]+#[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$'), EmailValidator.checkEmail, Validators.required])],
password: ['', Validators.compose([Validators.minLength(8), Validators.maxLength(20), Validators.pattern('^[^-\s][a-zA-Z0-9_\s-]+$'),PasswordValidator.checkPassword, Validators.required])]
});
and this is my custom UsernameValidator:
export class UsernameValidator {
static checkUsername(control: FormControl): any {
var onlyLetters;
return new Promise(resolve => {
if(control.value.toLowerCase() === "greg"){
resolve({
"taken": true
});
} else if (control.value.length < 3){
resolve({
"min": true
});
} else if (control.value.length > 8){
resolve({
"max": true
});
} else if (!(onlyLetters = /^[a-zA-Z\u00C0-\u00ff]+$/.test(control.value))){
resolve({
"latein": true
});
} else {
resolve(null);
}
});
}
}
this custom Validator should return the error Keys. In the html I want to check for the error keys and display the message if an error is true and the key got returned. But it doesnt seem to work. The Validation itselfe works. But ErrorMessages dont work.

Your validator for checking if the username is taken, is an async validator. Async validators should be passed as the third argument.
so change your assignment of your username formcontrol to:
username: ['', [sync validators here], [UsernameValidator.checkUsername]]
** also Validators.compose is not needed.
DEMO
as a non related issue, I would drop the [(ngModel)] and make use of the form control. You can set the initial value to your form control instead. If this arrives later, you can use setValue():
this.registrationForm.setValue({
username: this.registerCredentials.username
// setting other possible values to your form controls
})
and remove the ngModel altogether. Having two bindings (ngModel and FormControl) can cause issues.

Related

Unique username validation in Angular with data returns type of observable from service

// register-page.ts
this.registerationForm = new FormGroup(
{
username: new FormControl(null,
[
Validators.required,
Validators.minLength(3),
Validators.maxLength(30),
Validators.pattern('^[a-zA-Z-0123456789]*$'),
]
),
// accountService.ts
validateUsername(username: string): Observable<any> {
return this.httpManager.post(authServer + "username-validator", new ValidateUsernameRequest(username)).pipe(
map(
(response: Response) => {
return response.data;
}
)
);
}
// register-page.html
<ion-item [ngClass]="username==null ? 'at-beginning':''">
<ion-label position="floating">Kullanıcı Adı</ion-label>
<ion-input name="username" formControlName="username" inputmode="text" class="ion-text-lowercase"
placeholder="Kullanıcı Adı" (onchange)=(checkUsername($event)) (keypress)=(onKeyUp($event)) (keydown.space)="$event.preventDefault()">
</ion-input>
</ion-item>
<div class="err" *ngIf="formControls.username.errors">
<div *ngIf="formControls.username.errors.required">Kullanıcı adı zorunlu.</div>
<div *ngIf="formControls.username.errors.minlength">Kullanıcı adınız çok kısa.</div>
<div *ngIf="formControls.username.errors.maxlength">Kullanıcı adınız çok uzun.</div>
<div *ngIf="formControls.username.errors.pattern">Kullanıcı adınız özel karakter içeremez.</div>
<div *ngIf="formControls.username.errors.checkUsername">Kullanıcı adınız alınmış.</div>
</div>
I've tried to code a validator for the username that checks its availability whenever the user make changes on the input. I've fight for it two days but I'm just stuck. I understood that I need to use an async function that subscribes the data came from accountService.validateUseranem(control.value) but somehow I failed to make it work.
Can someone help me on this?
I did the following and fixed my own problem.
Created username-validator.directive.ts
import { AbstractControl, AsyncValidatorFn, ValidationErrors } from '#angular/forms';
import { Observable, of } from 'rxjs';
import { map } from 'rxjs/operators';
import { AccountService } from 'src/app/services/yon/auth/account.service';
export function existingUsernameValidator(userService: AccountService): AsyncValidatorFn {
return (control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> => {
return userService.validateUsername(control.value).pipe(map(
(data) => {
console.log("The result is : " + data)
return (!data) ? { "usernameExists": true } : null;
}
));
};
}
Then used it inside of the validators of username
this.registerationForm = new FormGroup(
{
username: new FormControl(null, {
validators: [
Validators.required,
Validators.minLength(3),
Validators.maxLength(20),
],
asyncValidators: [existingUsernameValidator(this.accountService)],
updateOn: 'change'
}),
email: new FormControl(null, {
validators: [
Validators.required,
Validators.email],
asyncValidators: [existingEmailValidator(this.accountService)],
updateOn: 'change'
}),
like this
Also;
updateOn: 'change'
Understands if the input changes on that control and whenever the input changes, it checks the value for validation.
And becasue that I need to send request to API and use that value returns as a response , my validator needed to be an asynchronous validator. Also the sync and aysnc validators need to be splitted as far as I understand, like I did.

How to fix form error 'validate' of undefined in angular

HTML
<form nz-form [formGroup]="formGroup">
<nz-form-item>
<nz-form-label nzFor="username">
<span translate>Username</span>
</nz-form-label>
<nz-form-control nzHasFeedback>
<input nz-input name="username" type="text" id="username" formControlName="username"
[placeholder]="'Enter Username' | translate" (keydown.space)="$event.preventDefault()" />
<nz-form-explain *ngIf="
(formGroup.get('username')?.dirty && formGroup.get('username')?.errors) ||
formGroup.get('username')?.pending
">
<ng-container *ngIf="formGroup.get('username')?.hasError('required')">
Username is required
</ng-container>
<ng-container *ngIf="formGroup.get('username')?.hasError('duplicated')">
Username already exists. Please try different username
</ng-container>
<ng-container *ngIf="formGroup.get('username')?.pending">
Validating...
</ng-container>
</nz-form-explain>
</nz-form-control>
</nz-form-item>
</form>
***TS****
import { FormGroup, FormBuilder, Validators, FormControl, ValidationErrors } from '#angular/forms';
userNameAsyncValidator = (control: FormControl) =>
new Observable((observer: Observer<ValidationErrors | null>) => {
setTimeout(() => {
if (control.value === 'JasonWood') {
observer.next({ error: true, duplicated: true });
} else {
observer.next(null);
}
observer.complete();
}, 1000);
});
createFormGroup(data?: Partial<User>) {
const dataObj = {
id: [data && data.id],
username: [
{
value: (data && data.username) || '',
disabled: data instanceof User
},
[Validators.required],
[this.userNameAsyncValidator]
]
}
return this.fb.group(dataObj);
}
user.module
imports: [
CommonModule,
UsersRoutingModule,
FormsModule,
ReactiveFormsModule,
PageTableFilterModule,
CellRenderersModule,
NzDrawerModule,
NzFormModule,
NzInputModule,
NzSelectModule,
NzDividerModule
]
how to fix the error: ERROR TypeError: Cannot read property 'validate' of undefined.
What I want is username have a validation which is already exists and username is required.
I added the userNameAsyncValidator, after I added in username then it start causing an error which is the ERROR TypeError: Cannot read property 'validate' of undefined. but when I try to remove the userNameAsyncValidator in username it doesn't have any error.
how to fix it?
ERROR:
at normalizeAsyncValidator (forms.js:930)
at Array.map (<anonymous>)
at composeAsyncValidators (forms.js:2150)
at coerceToAsyncValidator (forms.js:2501)
at new FormControl (forms.js:3236)
at FormBuilder.push../node_modules/#angular/forms/fesm5/forms.js.FormBuilder.control (forms.js:6462)
at FormBuilder.push../node_modules/#angular/forms/fesm5/forms.js.FormBuilder._createControl (forms.js:6502)
at forms.js:6488
at Array.forEach (<anonymous>)
at FormBuilder.push../node_modules/#angular/forms/fesm5/forms.js.FormBuilder._reduceControls (forms.js:6487)```
Since this.userNameAsyncValidator is async validator add userNameAsyncValidator as third paramter to username control. another change is move your form creation inside ngOnInit or constructo like this:
Try this:
constructor(private fb: FormBuilder) {
this.validateForm = this.createFormGroup();
}
createFormGroup(data?: Partial<User>) {
const dataObj = {
id: [data && data.id],
username: [
{
value: (data && data.username) || '',
disabled: data instanceof User
},
[Validators.required],
[this.userNameAsyncValidator]
]
}
return this.fb.group(dataObj);
}
In my case, when upgrading to Angular 11. The following validators array was breaking the unit tests:
[, Validators.required]
Notice there is nothing before the comma. Removing the comma fixed the issue for me.
In my case i had to remove FormsModule and ReactiveFormModule from imports to work

How to push more than one value into the form?

I want to push an array of inputs into a form. At the moment I always get with a console.logonly the latest inputted value. How can I push all the input values? I just wonder If I even need a additional form arrays. Since I can output the whole list in my console. So I have access to this data which is imported in order to upload to a server.
page.html
<form [formGroup]="form" (ngSubmit)="addTag(form.value)">
<ion-item>
<ion-input formControlName="tag" clearInput="true" placeholder="Tags" [(ngModel)]="tagInput" name="tagValue"></ion-input>
<ion-button item-right type="submit" icon-only>
<ion-icon name="checkmark"></ion-icon>
</ion-button>
</ion-item>
</form>
<ion-chip *ngFor="let tag of tagList; let i = index">
<ion-icon name="pricetag"></ion-icon>
<ion-label>{{ tag }}</ion-label>
<ion-icon name="close-circle" (click)="removeChip(i)"></ion-icon>
</ion-chip>
page.ts
form: FormGroup;
public tagList: any[] = [];
constructor() { }
addTag(formValue) {
if (this.tagInput !== '') { //no empty input
this.tagList.push(formValue.tagValue);
this.tagInput = '';
}
}
ngOnInit() {
this.form = new FormGroup({
tag: new FormControl(null, {
updateOn: 'submit',
validators: [Validators.required, Validators.maxLength(20), Validators.minLength(1)]
})
});
}
confirm() {
console.log(this.form);
}
so, based on your code, you actually have a form and an array of items added from the form... not sure why you need a form array or anything like that. Your fixed code could just be like this:
<form [formGroup]="form" (ngSubmit)="addTag()">
<ion-item>
<ion-input formControlName="tag" clearInput="true" placeholder="Tags" name="tagValue"></ion-input>
<ion-button item-right type="submit" icon-only>
<ion-icon name="checkmark"></ion-icon>
</ion-button>
</ion-item>
</form>
<ion-chip *ngFor="let tag of tagList; let i = index">
<ion-icon name="pricetag"></ion-icon>
<ion-label>{{ tag }}</ion-label>
<ion-icon name="close-circle" (click)="removeChip(i)"></ion-icon>
</ion-chip>
get rid of the mixing of reactive forms and template forms, and just call add tag, don't pass a value in.
form: FormGroup;
public tagList: any[] = [];
constructor() { }
addTag() { // properly access and reset reactive form values
const tagCtrl = this.form.get('tag');
if (tagCtrl.value) {
this.tagList.push(tagCtrl.value);
this.tagCtrl.reset(''); // reset() sets the value and resets validation
}
}
ngOnInit() {
this.form = new FormGroup({
tag: new FormControl(null, {
updateOn: 'submit',
validators: [Validators.required, Validators.maxLength(20), Validators.minLength(1)]
})
});
}
confirm() {
console.log(this.tagList); // just check your tagList instead of looking at the form
}
you're overthinking it here. a FormArray could be useful under some circumstances, like needing some complex validation / error message capability, or ability to edit tags after adding them, but if simple remove is all you need, you're over engineering this.

Angular5 reactive forms with mailcheck.js

below is HTML code for form
<div class="form-group">
<label for="email">Email</label>
<input type="email" class="form-control"
(blur)="suggestEmail(signupForm.controls['userData'].controls.email.value)"
id="email" formControlName="email">
<span class="help-block" *ngIf="!signupForm.get('userData.email').valid && signupForm.get('userData.email').touched">
please enter a valid email id
</span>
</div>
Below is ts code
constructor(private fb: FormBuilder) {
this.signupForm = this.fb.group({
userData: this.fb.group({
email: [null, [Validators.required, Validators.email]]
})
});
}
ngOnInit() {
}
suggestEmail(email) {
Mailcheck.run({
email: email,
domains: ['gmail.com', 'aol.com', 'hotmail.com', 'yahoo.com', 'rediffmail.com', 'edu', 'msn.com',],
secondLevelDomains: ['domain', 'hotmail'],
topLevelDomains: ["com", "net", "org", "info"],
suggested: function (suggestion) {
console.log(suggestion);
if (suggestion) {
alert(suggestion.full);
console.log(suggestion.full + "dkdjdekjekde")
}
},
empty: function () {
}
});
}
Right now, value of suggestions.full comes in alert if its being called. But I am trying to show suggestions.full in html side, like as a error warning.
Below is link to my stackblitz
stackblitz
To avoid potential problems with access to this within the Mailcheck.run suggested callback, you could save the results of Mailcheck.run, check them and, if appropriate, set an error on your form field.
let check = Mailcheck.run({
email: email,
... other stuff ...
suggested: (suggestion) => {
return suggestion;
},
empty: () => {
return false; // or however you want to handle it...
}
if (check && check.full) {
this.suggestedEmail = check.full;
this.signupForm.get('userData.email').setErrors({ 'has_suggestion': true })
}
// then in your template (using a getter)
<span class="help-block"
*ngIf="f.invalid && f.touched && f.errors?.has_suggestion">
Suggestion: {{suggestedEmail}}
</span>
Please find this stackblitz -- hope it helps!
Instead of using a regular function which will be lost this scope whereas arrow function keeps track of this. Read more about the difference here https://stackoverflow.com/a/34361380/5836034
do something like this
....
suggestion: any;
....
suggestEmail(email) {
Mailcheck.run({
email: email,
domains: ['gmail.com', 'aol.com', 'hotmail.com', 'yahoo.com', 'rediffmail.com', 'edu', 'msn.com',],
secondLevelDomains: ['domain', 'hotmail'],
topLevelDomains: ["com", "net", "org", "info"],
suggested: (suggestion) => {
console.log(suggestion);
if (suggestion) {
alert(suggestion.full);
this.suggestion = suggestion;
console.log(suggestion.full + "dkdjdekjekde")
}
},
empty: function () {
}
});
}
Observe the use of arrow function, to keep track of this scope and also, assigning the value of suggestion to your class variable via
this.suggestion = suggestion
in your template, you can now have access to suggestion like so
<div *ngIf="suggestion">{{suggestion.full}} </div>
Source: https://stackblitz.com/edit/angular-email-checker-bjcrcc

When input field is prepopulated the field is invalid

I have a form where I enter an email and confirm email and then continue to the next page and all is well. The validation works fine when the page initially loads and it's the user's first time, so the input field is not prepopulated from cookie data. However, when the user returns, the input field data is prepopulated from cookie data and that is fine but the submit button is still disabled even though the prepopulated text is valid format. I inspected the elements and it seems to think the field is ng-invalid even though it's valid format.
I noticed when I go to one of the fields and backspace to remove the last character and reinsert the same character as before for email and do the same for the next field, the form is valid again. Even though, it's the same text as before.
I'm wondering why validation fails when the form first loads with prepopulated data?
Here's my code:
export class EmailComponent implements OnInit {
public user : User;
Form : FormGroup;
displayErrors : boolean;
ngOnInit() {
// initialize model here
this.user = {
Email: '',
confirmEmail: ''
}
}
constructor(fb: FormBuilder, private cookieService: CookieService, private cryptoService: CryptoService) {
var encryptedEmail = this.cookieService.get(AppCookie.EmailAddress);
var Cookie = null;
if(encryptedEmail != null && encryptedEmail != 'undefined')
Cookie = this.cryptoService.Decrypt(encryptedEmail);
if(Cookie == null) {
this.Form = fb.group({
email: ['', [Validators.required, Validators.pattern(EMAIL_REGEXP)]],
confirmEmail: ['', [Validators.required, Validators.pattern(EMAIL_REGEXP)]]
},
{
validator: this.matchingEmailsValidator('email', 'confirmEmail')
});
}
else {
this.Form = fb.group({
email: [Cookie, [Validators.required, Validators.pattern(EMAIL_REGEXP)]],
confirmEmail: [Cookie, [Validators.required, Validators.pattern(EMAIL_REGEXP)]]
},
{
validator: this.matchingEmailsValidator('email', 'confirmEmail')
});
}
}
save(model: User, isValid: boolean)
{
model.Email = this.Form.get('email').value;
var encrypted = this.cryptoService.Encrypt(model.Email);
this.cookieService.put(AppCookie.EmailAddress, encrypted);
}
matchingEmailsValidator(emailKey: string, confirmEmailKey: string): ValidatorFn {
return (group: FormGroup): {[key: string]: any} => {
let email = group.controls[emailKey];
let confirmEmail = group.controls[confirmEmailKey];
if (email.value !== confirmEmail.value) {
return {
mismatch: true
};
}
};
}
}
and here's my view:
<form [formGroup]="Form" novalidate (ngSubmit)="Form.valid && save(Form.value, Form.valid)">
<div class="login-wrapper">
<div class="login-page">
<section class="login-form form-group">
<p>
<input id="email"
[class.email-address-entry]="!displayErrors"
[class.email-address-entry-text]="!displayErrors && this.Form.get('email').value !='' "
type="email"
placeholder="name#domain.com" formControlName="email" />
</p>
<p class="login-form__msg">Reenter your email to confirm</p>
<input id="reenteremail"
[class.email-address-entry]="!displayErrors"
[class.entry-border-invalid]="displayErrors && !Form.valid && Form.errors?.mismatch"
[class.email-address-entry-text]="!displayErrors && this.Form.get('email').value !='' "
(blur)="displayErrors=true"
type="email" placeholder="name#domain.com"
formControlName="confirmEmail"/>
<p class="error-msg" *ngIf="displayErrors && !Form.valid && Form.errors?.mismatch">The email you entered does not match.</p>
</section>
<p class="login-confirm">
<span>
<button type="submit" [disabled]="!Form.valid" (click)="Form.get('email').length > 0 ? save(Form.value, Form.valid) : NaN">Confirm</button>
</span>
</p>
</div>
</div>
</form>
EDIT: It's similar to this issue as well:
Angular 2 - Form is invalid when browser autofill
I tried adding this:
ngAfterViewChecked() {
if (Cookie) {
// enable to button here.
var element = <HTMLInputElement> document.getElementById("confirmBtn");
element.disabled = false;
}
But it won't work because fields are still invalid. I need a way to manually set re-validation or change ng-invalid to ng-valid.
If you keep a reference to the form instance (either by using reactive forms or by accessing it using #ViewChild) you should be able to write the following in ngAfterViewInit():
for (var i in this.form.controls) {
this.form.controls[i].updateValueAndValidity();
}
Or perhaps marking the fields as touched will be better in your case:
for (var i in this.form.controls) {
this.form.controls[i].markAsTouched();
}

Categories

Resources