How can I call a function in mat-error element? - html

I want to add an error to my input ! The function will return true if the bith passwords are matching otherwise it will return false
How can I call a boolean function in mat-error !
My Function:
checkPasswords(): Boolean { // here we have the 'passwords' group
this.test = false
let pass = this.changePassForm.value.motpasseconf;
let confirmPass = this.changePassForm.value.Nouveaumotpasse;
console.log(pass);
if (pass === confirmPass) {
return true;
}
}
The ERROR:
<mat-error *ngIf=" "> Not matching Password </mat-error>
The same function in the button works but annd in the error mat dosnt work
TS File:
ngOnInit(): void {
// initialisation du modifier password Form
this.changePassForm = this.formBuilder.group({
'userFullName': [this.user.firstName, [
]],
'Nouveaumotpasse': [this.user.motpasseconf, [
Validators.required,
Validators.minLength(6),
]],
'motpasseconf': [this.user.motpasseconf, [
Validators.required,
Validators.minLength(6),
]]
});
}
HTML Form:
<
div fxFlex="48" class="form" *ngIf="resultUserData != null || resultUserData != undefined">
<form [formGroup]="changePassForm" novalidate>
<mat-form-field class="w-50">
<input id="userFullName" matInput formControlName="userFullName" placeholder="Nom et prénom de l'utilisateur concerné"
type="text" readonly/>
</mat-form-field>
<!-- Nouveau mot de passe -->
<mat-form-field class="w-50">
<input id="Nouveaumotpasse" matInput #passwordWithValidation formControlName="Nouveaumotpasse"
placeholder="Nouveau mot de passe" [type]="hide ? 'password':'text'" required />
<mat-icon matSuffix (click)="hide= !hide" required class="pointer" color="primary">
{{ hide ? 'visibility' : 'visibility_off' }}
</mat-icon>
<mat-error *ngIf="changePassForm.controls['Nouveaumotpasse'].hasError('required')">Votre mot de passe est
<strong>obligatoire</strong>
</mat-error>
<mat-error
*ngIf="(changePassForm.controls['Nouveaumotpasse'].hasError('minlength')) && !changePassForm.controls['Nouveaumotpasse'].hasError('required') && changePassForm.controls['Nouveaumotpasse'].hasError('notEqual')">
Le Mot de passe doit comporter 6 caractères minimum
</mat-error>
<mat-error *ngIf="checkPasswords()">
Les mots de passe saisis ne sont pas identiques</mat-error>
</mat-form-field>
<!-- Confirmer nouveau mot de passe -->
<mat-form-field class="w-50">
<input id="motpasseconf" matInput formControlName=motpasseconf placeholder="Confirmer nouveau mot de passe"
[type]="hide ? 'password':'text'" appConfirmEqualValidator="Nouveaumotpasse" required />
<mat-icon matSuffix (click)="hide= !hide" class="pointer" color="primary">
{{ hide ? 'visibility' : 'visibility_off' }}
</mat-icon>
<mat-error *ngIf="changePassForm.controls['motpasseconf'].hasError('required')">Retapez le mot de passe
pour confirmation </mat-error>
<mat-error
*ngIf="!changePassForm.controls['motpasseconf'].hasError('required') ">
Les mots de passe saisis ne sont pas identiques</mat-error>
</mat-form-field>
Please check edited post
Please check edited post

Angular Material provides an elegant way to deal with errors where several fields are involved: the ErrorStateMatcher (https://material.angular.io/components/input/overview#changing-when-error-messages-are-shown)
Let's say you use reactive forms, you can use a custom validator match on the form group:
this.passwordForm = this.fb.group({
newPassword: ['', [Validators.required, password()]],
confirmPassword: ['', [Validators.required, password()]]
}, { validators: match('newPassword', 'confirmPassword') })
Here is the implementation of the match validator:
import { AbstractControl, ValidatorFn } from '#angular/forms'
import { error } from './error'
export function match(controlName: string, matchingControlName: string): ValidatorFn {
return (control: AbstractControl) => {
const value = control.get(controlName)?.value as string | null
const matchingValue = control.get(matchingControlName)?.value as string | null
return (value && matchingValue && value !== matchingValue) ? error('notMatching') : null
}
}
This validator will check that the two form control (they need to be in the form group on which the validator is applied) have the same value.
You will also need to implement an ErrorStateMatcher:
import { FormControl, FormGroupDirective, NgForm } from '#angular/forms'
import { ErrorStateMatcher } from '#angular/material/core'
export class CrossFieldErrorMatcher implements ErrorStateMatcher {
constructor(
private crossFieldError: string
) { }
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
const isTouched = control?.touched ?? false
const isInvalid = control?.invalid ?? false
const hasCrossFieldError = form?.hasError(this.crossFieldError) ?? false
return isTouched && (isInvalid || hasCrossFieldError)
}
}
Don't forget to initialise it in your component's ts file:
crossFieldErrorMatcher = new CrossFieldErrorMatcher('notMatching')
The error state matcher will dictate when a field should be considered in error. The implementation here makes sure that the field it is applied to is considered in error if its parent has a certain error whose name is passed in the constructor. In our exemple, it will be 'notMatching'. If the error is detected on the form group then the confirm password field will be considered in error.
And then in your html:
<form (ngSubmit)="editPassword(f)" #f="ngForm" [formGroup]="passwordForm" autocomplete="off">
<mat-form-field>
<mat-label>New Password</mat-label>
<input matInput formControlName="newPassword" required autocomplete="new-password" id="new-password">
<mat-error ngs-error-message [formGroup]="passwordForm" propertyName="newPassword"></mat-error>
</mat-form-field>
<mat-form-field>
<mat-label>Confirm new password</mat-label>
<input matInput formControlName="confirmPassword" [errorStateMatcher]="crossFieldErrorMatcher" required autocomplete="new-password" id="new-password-confirmation">
<mat-error *ngIf="confirmPasswordControl.hasError('required')">
Confirm your password is required
</mat-error>
<mat-error *ngIf="passwordForm.hasError('notMatching')">
The two passwords don't match
</mat-error>
</mat-form-field>
<button mat-flat-button type="submit" color="accent"
[disabled]="passwordForm.invalid || !passwordForm.dirty || isLoading">
Submit
</button>
</form>

Related

How to make a function that navigate when user input equal to the If Condition Using angular

I want to make a function that checks the user input if is equal to (username:"marwen" , password:"hamdi") then navigate to the URL 'apps/e-commerce/products'
This is the TypeScript:
ngOnInit(): void{
this.loginForm = this._formBuilder.group({
email : ["", [Validators.required] ],
password: ["", Validators.required]
});
}
check(){
if (this.email.value==="marwen"&& this.password.value==="hamdi"){
this.router.navigate(['apps/e-commerce/products'])
}
}
And this is the HTML part where I want to execute the function when I click on the button.
<form name="loginForm" [formGroup]="loginForm" novalidate >
<mat-form-field appearance="outline" style="color:white;">
<mat-label style="color:white;">Nom d'Utilisateur</mat-label>
<input matInput formControlName="email" id="email">
<mat-icon matSuffix class="secondary-text" style="color:white;">
supervised_user_circle
</mat-icon>
<mat-error *ngIf="loginForm.get('email').hasError('required')">
Nom d'utilisateur est requis
</mat-error>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label style="color:white;">Mot De Passe</mat-label>
<input matInput type="password" formControlName="password" id="password">
<mat-icon matSuffix class="secondary-text" style="color:white;" >vpn_key</mat-icon>
<mat-error>
Mot De Passe est requis
</mat-error>
</mat-form-field>
<button mat-raised-button color="accent" class="submit-button" aria-label="LOG IN" [disabled]="loginForm.invalid" (click)="check()" >
Connexion
</button>
</form>
I hope you are using this validation only as test or mock your login. Anyways, this can help you:
ngOnInit() {
// do something with this subscription
this.loginForm.valueChanges.subscribe(values => {
if (values.email === "marwen" && values.value==="hamdi") {
this.router.navigate(['apps/e-commerce/products'])
}
});
}

How to trigger mat chip error validation?

I'm doing a error validation for a field with mat-chip, I'm able to pop up the error validation message only after user add a single mat-chip value, then remove the value. I want to make it where when user click on the field then go to a next field of the form without adding any value, it'll already trigger the error message. Below is my code snippet
HTML :
<form [formGroup]="positionForm">
<div fxLayout="column" fxLayoutAlign="space-between stretch">
<mat-form-field appearance="outline">
<mat-label>Title</mat-label>
<input matInput formControlName="title" type="text" placeholder="Title" required>
<mat-error *ngIf="positionForm.controls.title.invalid">This field is required.</mat-error>
</mat-form-field>
<mat-form-field class="chip-list" appearance="outline" (click)="formValue()">
<mat-label>Skills *</mat-label>
<mat-chip-list #chipSkills>
<mat-chip *ngFor="let skill of positionForm.get('skills').controls; let i = index" [selectable]="selectable"
[removable]="removable" (removed)="removeSkills(i)" (click)="formValue()">
{{skill.value}}
<mat-icon class="remove-chip" matChipRemove *ngIf="removable">cancel</mat-icon>
</mat-chip>
<input placeholder="Enter Skills..."
[matChipInputFor]="chipSkills"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[matChipInputAddOnBlur]="addOnBlur"
(matChipInputTokenEnd)="addEditSkill($event)" required>
</mat-chip-list>
<mat-error *ngIf="positionForm.controls.skills.errors">Atleast 1 skill need to be added </mat-error>
</mat-form-field>
</form>
TS :
title = new FormControl('', [Validators.required,this.customValidator.NoWhiteSpaceValidator]);
skills = new FormArray([], [Validators.required, this.validateArrayNotEmpty] );
validateArrayNotEmpty(chip: FormControl) {
if (chip.value && chip.value.length === 0) {
return {
validateArrayNotEmpty: { valid: false }
};
}
return null;
}

Angular Load new Autocomplete values on each select change event

I have several most important fields in my form. In first form, user should choose the company by mat-select-search. After that, by using (selectionChange) I'm calling API with selected value in the first field( this is company id) and return users from that company to autocomplete for recipient fields. It works perfectly, if user does not change his chosen company. But if he do that (selectionChange) method doesn't reload autocomplete with new value. There is a situation in which the user has another company selected, and autocomplete remains from the previous one. Event only loads new one if the previous output was empty.
Is it possible to change this behavior? May be it,s possible to delete previous results from api calling?
My form :
<form [formGroup]="orderForm" novalidate (ngSubmit)="createOrder()">
<div class="row">
<div class="col-md-12">
<mat-form-field class="example-full-width" formGroupName='Company'>
<mat-select ngDefaultControl placeholder="Company" #singleSelect formControlName="id" (openedChange)="onSelectionChange($event)" [(value)]="selectedVariable">
<mat-option>
<ngx-mat-select-search [formControl]="companySelectFilter" [searching]="searching" placeholderLabel="Find company..." noEntriesFoundLabel="'no matching companies found'" ></ngx-mat-select-search>
</mat-option>
<mat-option *ngFor="let company of filteredCompanies | async" [value]="company.id">
{{company.company_name| titlecase}} {{company.company_address| titlecase}}
</mat-option>
</mat-select>
<mat-error *ngIf="orderForm.hasError('notSame')">
Recipient has another company! Select again.
</mat-error>
</mat-form-field>
</div>
</div>
<div class="row" matAutocompleteOrigin #origin="matAutocompleteOrigin">
<div class="col-md-6">
<mat-form-field class="example-full-width" formGroupName='recipient' >
<input type="text"
matInput
formControlName="user_name"
placeholder="First Name"
[matAutocomplete]="auto"
[matAutocompleteConnectedTo]="origin">
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="setFormData($event)">
<mat-option *ngFor="let recipient of filteredOptions | async" [value]="recipient">
<div style="display:flex;flex-wrap: nowrap;align-items:center;justify-content:center;margin: auto;">
<span style="flex-grow: 1;flex: 1 1 33%;"> {{recipient.user_name.toString() | titlecase}} {{recipient.user_surname.toString() | titlecase}} {{recipient.company.company_name.toString() | titlecase}}
</span>
</div>
</mat-option>
</mat-autocomplete>
</mat-form-field>
</div>
<div class="col-md-6" >
<mat-form-field class="example-full-width" formGroupName='recipient'>
<input matInput formControlName="user_surname" placeholder="Last Name"[matAutocomplete]="auto"
[matAutocompleteConnectedTo]="origin" >
<mat-error *ngIf="orderForm.get('recipient.user_surname').hasError('required')">
Last Name is <strong>required</strong>
</mat-error>
</mat-form-field>
</div>
</div>
<div class="col-md-6" >
<mat-form-field class="example-full-width" formGroupName='recipient'>
<input matInput placeholder="Mobile Phone" type="text" formControlName="user_telephone" >
</mat-form-field>
</div>
OnSelected change method:
onSelectionChange(opened: boolean) {
console.log(`opened is : ${opened}`);
if (!opened && this.selectedVariable) {
this.orderForm.get('recipient.user_name').reset()
this.orderForm.get('recipient.user_surname').reset()
this.orderForm.get('recipient.user_telephone').reset()
this.filteredOptions = this.orderForm
.get('recipient.user_name')
.valueChanges.pipe(
startWith(""),
debounceTime(400),
distinctUntilChanged(),
switchMap((val) => {
return this.doFilter(this.selectedVariable, val || '');
})
);
}
}
And doFilter method :
doFilter(id: number, val: any): Observable<any[]> {
return this.recipientService.getRecipientsByCompany(id).pipe(
map((response) =>
response.filter((option) => {
return (
option.user_name
.toString()
.toLowerCase()
.indexOf(val.toString().toLowerCase()) === 0 ||
option.user_surname
.toString()
.toLowerCase()
.indexOf(val.toString().toLowerCase()) === 0
);
})
)
);
}
Finally, I found a solution. My mistake was I trying to retrieve the data from API after page loaded. Now, I load all the recipients in NgOnInit method and filter the data after user change the company.
LoadRecipients method which we put into ngOnInit:
this.recipientService
.getRecipientsObs()
.subscribe((recipients) => (this.recipients = recipients));
}
And new OnChange method :
selectedVariable: any;
onSelectionChange(opened: boolean) {
console.log(`opened is : ${opened}`);
console.log(this.selectedVariable)
if (!opened && this.selectedVariable) {
this.orderForm.get("recipient.user_name").reset();
this.orderForm.get("recipient.user_surname").reset();
this.orderForm.get("recipient.user_telephone").reset();
const formsInput = merge(
this.orderForm.get('recipient.user_name').valueChanges,
this.orderForm.get('recipient.user_surname').valueChanges
);
formsInput.pipe(
startWith(""),
debounceTime(400),
distinctUntilChanged(),
map((search) => {
if (!this.recipients) {
return [];
} else {
search = search.toLowerCase();
}
return this.recipients.filter(
(recipient) =>
recipient.company.id === this.selectedVariable &&
recipient.user_name.toLowerCase().indexOf(search) > -1 &&
recipient.user_surname.toLowerCase().indexOf(search) > -1
);
}),
delay(500)
)
.subscribe(
(filteredRecipients) => {
this.searching = false;
this.filteredRecipients.next(filteredRecipients);
},
(error) => {
this.searching = false;
}
);
}
}

Nested FormArray Error: "cannot find control with path:"

I am developing an APP Using Angular 9.
But I'm facing a problem with nested formArray.
I'm having the error: "Cannot find control with path: 'arrayProcedimientos -> subprocedimientos'"
The goal of this form is that arrayProcedimientos is an array, and inside that there are subprocedimientos which is an array too.
Below is my code:
HTML:
<form #f="ngForm" [formGroup]="registros" class="fila-contenido">
<!-- A lot of mat-form-field -->
<div formArrayName="arrayProcedimientos"
*ngFor="let procedimientoArr of registros.get('arrayProcedimientos')['controls']; let i = index">
<div [formGroupName]="i">
<!-- Procedimientos ESTO ES UN ARRAY-->
<mat-form-field appearance="">
<mat-label>Procedimientos</mat-label>
<mat-select formControlName="procedimiento">
<mat-option *ngFor="let procedimiento of procedimientos"
[value]="procedimiento">
{{procedimiento}}
</mat-option>
</mat-select>
</mat-form-field>
<!-- Contenido ESTO ES UN ARRAY-->
<mat-form-field>
<mat-label>Fecha Inicio</mat-label>
<input matInput [matDatepicker]="picker8"
formControlName="fechaInicioProcedimiento">
<mat-datepicker-toggle matSuffix [for]="picker8"></mat-datepicker-toggle>
<mat-datepicker #picker8></mat-datepicker>
</mat-form-field>
<!-- Contenido ESTO ES UN ARRAY-->
<mat-form-field>
<mat-label>Fecha Fin</mat-label>
<input matInput [matDatepicker]="picker9" formControlName="fechaFinProcedimiento">
<mat-datepicker-toggle matSuffix [for]="picker9"></mat-datepicker-toggle>
<mat-datepicker #picker9></mat-datepicker>
</mat-form-field>
<!-- Notas -->
<mat-form-field appearance="fill">
<mat-label>Notas</mat-label>
<textarea matInput formControlName="notasProcedimiento"></textarea>
</mat-form-field>
</div>
{{registros.value | json}}
<div formArrayName="subprocedimientos"
*ngFor="let subprocedimiento of procedimientoArr.get('subprocedimientos')['controls']; let j = index">
<div [formGroupName]="j">
{{registros.value | json}}
</div>
</div>
</div>
</form>
TS:
export class AddRegistroComponent implements OnInit {
data = {
procedimientos: [
{
procedimiento: "",
fechaInicioProcedimiento: "",
fechaFinProcedimiento: "",
notasProcedimiento: "",
subprocedimientos: [
{
subprocedimiento: "",
fechaInicioSubprocedimiento: "",
fechaFinSubprocedimiento: "",
fechaLimiteSubprocedimiento: "",
notasSubprocedimiento: ""
}
]
}
]
}
registros: FormGroup;
constructor(public dialog: MatDialog, private service: AddRegistroService, private fbuilder: FormBuilder) {
this.inicializaFormGroup();
}
inicializaFormGroup() {
this.registros = this.fbuilder.group({
arrayProcedimientos: this.fbuilder.array([]),
});
this.setProcedimientos();
}
setProcedimientos() {
let control = <FormArray>this.registros.controls.arrayProcedimientos;
this.data.procedimientos.forEach(x => {
control.push(this.fbuilder.group({
procedimiento: x.procedimiento,
fechaInicioProcedimiento: x.fechaInicioProcedimiento,
fechaFinProcedimiento: x.fechaFinProcedimiento,
notasProcedimiento: x.notasProcedimiento,
subprocedimientos: this.setSubProcedimientos(x)
}))
})
}
setSubProcedimientos(x) {
let arr = new FormArray([])
x.subprocedimientos.forEach(y => {
arr.push(this.fbuilder.group({
subprocedimiento: y.subprocedimiento,
fechaInicioSubprocedimiento: y.fechaInicioSubprocedimiento,
fechaFinSubprocedimiento: y.fechaFinSubprocedimiento,
fechaLimiteSubprocedimiento: y.fechaLimiteSubprocedimiento,
notasSubprocedimiento: y.notasSubprocedimiento
}))
})
return arr;
}
}
My solution is based on an stackblitz that a mate from StackOverflow made, here is the link of his solution:
https://stackblitz.com/edit/angular-dffny7?file=app%2Fapp.component.html
Thanks for your time.
Alberto.

Password is not matched with pattern in Angular CLI

I have a problem with password validation, I am using a regex such as;
'(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])'
and my ng-form field is;
password: ['', [Validators.pattern('(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])'), Validators.required]]
also in HTML, I get input as;
<div class="form-group">
<label for="password">Password</label>
<input type="password" formControlName="password" placeholder="******" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.password.errors }" />
<div *ngIf="submitted && f.password.errors" class="invalid-feedback">
<div *ngIf="f.password.errors">Invalid Password</div>
</div>
</div>
where f is function such as;
get f() {
return this.userForm.controls;
}
When I entered a password as: Harun123, I get invalid password error. Why this happens?
This question could be solved with a combination of these two answers:
So first of all, you would need a custom validator for checking the passwords, that could look like this
checkPasswords(group: FormGroup) { // here we have the 'passwords' group
let pass = group.controls.password.value;
let confirmPass = group.controls.confirmPass.value;
return pass === confirmPass ? null : { notSame: true }
}
and you would create a formgroup for your fields, instead of just two form controls, then mark that custom validator for your form group:
this.myForm = this.fb.group({
password: ['', [Validators.required]],
confirmPassword: ['']
}, {validator: this.checkPasswords })
and then as mentioned in other answer, the mat-error only shows if a FormControl is invalid, so you need an error state matcher:
export class MyErrorStateMatcher implements ErrorStateMatcher {
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm |
null): boolean {
const invalidCtrl = !!(control && control.invalid && control.parent.dirty);
const invalidParent = !!(control && control.parent && control.parent.invalid
&& control.parent.dirty);
return (invalidCtrl || invalidParent);
}
}
in the above you can tweak when to show error message. I would only show message when the password field is touched. Also I would like above, remove the required validator from the confirmPassword field, since the form is not valid anyway if passwords do not match.
Then in component, create a new ErrorStateMatcher:
matcher = new MyErrorStateMatcher();
Finally, the template would look like this:
<form [formGroup]="myForm">
<mat-form-field>
<input matInput placeholder="New password" formControlName="password"
required>
<mat-error *ngIf="myForm.hasError('required', 'password')">
Please enter your new password
</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput placeholder="Confirm password"
formControlName="confirmPassword" [errorStateMatcher]="matcher">
<mat-error *ngIf="myForm.hasError('notSame')">
Passwords do not match
</mat-error>
</mat-form-field>
</form>
Here's a demo for you with the above code: stackblitz