How to trigger mat chip error validation? - html

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;
}

Related

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

Angular displaying a different placeholder in an input dependant on a mat select

I'm trying to display different placeholder text in a mat-input dependant on what I select in my mat-select, and I cant seem to find a way to bind it to my selected option.
<mat-form-field appearance="outline" class="search-field" style="width:40vw">
<input matInput type="text" class="input" placeholder="Enter a Plant No" formControlName="searchQuery">
<mat-select name="ampm" class="ampm" style="width:10vw;" [value]="selected">
<mat-option value="PLANT_NO">PLANT_NO</mat-option>
<mat-option value="EQUIP_NO" disabled>EQUIP_NO</mat-option>
<mat-option value="LOCN_DESC" disabled>LOCN_DESC</mat-option>
<mat-option value="EQUIP_GRP_ID" disabled>EQUIP_GRP_ID</mat-option>
<mat-option value="EQUIP_CLS" disabled>EQUIP_CLS</mat-option>
<mat-option value="MAINT_ZONE_NAM" disabled>MAINT_ZONE_NAM</mat-option>
<mat-option value="PRNT_PLNT_NO" disabled>PRNT_PLNT_NO</mat-option>
<mat-option value="SUBSTATION" disabled>SUBSTATION</mat-option>
<mat-option value="IP_PLNT_NO" disabled>IP_PLNT_NO</mat-option>
</mat-select>
<button mat-icon-button title="Search">
<mat-icon>search</mat-icon>
</button>
</mat-form-field>
you can use [placeholder] property binding, and mat select change event you can update as per your requirement.
ts
inputPlaceholderUpdate = "Enter a Plant No";
selectChange(event){
const value = event.value;
this.inputPlaceholderUpdate = value == 'PLANT_NO' ? 'Enter a Plant No' : value == 'EQUIP_NO' ? 'Enter a Equip No' : 'Enter a Locn Desc';
}
html
<input matInput type="text" class="input" [placeholder]="inputPlaceholderUpdate" formControlName="searchQuery">
<mat-select name="ampm" class="ampm" style="width:10vw;" [value]="selected" (selectionChange)= "selectChange($event)">
......
......
</select>

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'])
}
});
}

Material angular auto complete: how to remains the mat-option in the screen after selecting an option?

I'm using the Material auto complete angular.
After i select one option in my mat-option list, my mat-options hide. I need to remain the options after select. Gif showing the problem:
My html
<div class="col-xl-6 margemColunas">
<label>Anunciaremos nas contas *</label>
<input class="form-control inputContasB2W"
#inputContasB2W
[formControl]="contasB2WControl"
[matAutocomplete]="auto"
(matChipInputTokenEnd)="add($event)">
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="selected($event, i)">
<mat-option *ngFor="let contaB2W of contasFiltradas | async" [value]="contaB2W">
{{contaB2W}}
</mat-option>
</mat-autocomplete>
</div>
<div class="col-xl-6 alinhaChips">
<mat-chip-list>
<mat-chip
*ngFor="let contaB2W of produto.contasAnunciarB2W; let j = index"
[selectable]="selected"
[removable]="removable"
(removed)="removeTag(i, j)">
{{contaB2W}}
<mat-icon matChipRemove><i class="fa fa-close"></i></mat-icon>
</mat-chip>
</mat-chip-list>
</div>
My ts:
constructor(){
this.contasFiltradas = this.contasB2WControl.valueChanges.pipe(
startWith(null),
map((nomeConta: string | null) => nomeConta ? this._filter(nomeConta) : this.contasB2W.slice()));
}
private _filter(value: string): string[] {
const filterValue = value.toLowerCase();
return this.contasB2W.filter(conta => conta.toLowerCase().indexOf(filterValue) === 0);
}
selected(event: MatAutocompleteSelectedEvent, index: number): void {
this.produtosConfirmadosAnuncio[index].contasAnunciarB2W.push(event.option.viewValue);
this.inputContasB2W.nativeElement.value = '';
}
}
I used workaround to blur() and then focus() again the input.
In ts:
#ViewChild('inputContasB2W') autocompleteInput: ElementRef;
selected(event: MatAutocompleteSelectedEvent) {
[..do your stuff...]
this.autocompleteInput.nativeElement.blur();
setTimeout(() => {
this.autocompleteInput.nativeElement.focus();
}, 100
);
}
If you want to preserve input text you have to save it in _filter() and set the value again before blur() like this.contasB2WControl.setValue(this.lastFilterValue);
<mat-form-field>
<input
matInput
placeholder="New fruit..."
#input
#trigger="matAutocompleteTrigger" // <-- get trigger ref
[formControl]="control"
[matAutocomplete]="auto"
/>
<mat-autocomplete
#auto="matAutocomplete"
(optionSelected)="selected($event)"
>
<mat-option
*ngFor="let option of filteredOptions | async"
[value]="option"
(click)="$event.stopPropagation(); trigger.openPanel()" // <-- use it here
>
{{ option }}
</mat-option>
</mat-autocomplete>
</mat-form-field>

Reset Angular Formly Form Validations on click event

I am implementing dynamic forms using the angular formly module and It's working fine. What functionality I need is Initially there should be a select box which contains multiple options, Based on selection different form fields should be displayed. As I explained I have Implemented and it's working, here what my problem is If I select option 1 and If I submit the form without filling fields, form displaying validation errors that is also cool. But when I select option 2 form fields are changing, but by default, all required fields are showing errors. How can I resist this? Please suggest me.
html
<div class="row">
<mat-form-field class="col-lg-2">
<mat-select placeholder="Form For" (selectionChange)="getSelectedFormName($event)">
<mat-option value="uf001">UF001</mat-option>
<mat-option value="uf002">UF002</mat-option>
<mat-option value="uf003">UF003</mat-option>
</mat-select>
</mat-form-field>
<div class="col-lg-4">
<button type="button" class="btn btn-default btn-one" (click)="getDynamicForm()">GET FORM</button>
</div>
</div>
<form [formGroup]="form" (ngSubmit)="submit(model)" >
<formly-form [model]="model" [fields]="fields" [form]="form" *ngIf="isFormTypeSelected" >
</formly-form>
<button type="submit" class="btn btn-success">Submit</button>
</form>
ts file
getSelectedFormName(eve) {
this.isFormSaved = false;
this.form = new FormGroup({});
this.fields=[];
this.model = {};
this.parentFormName = eve.value;
}
getDynamicForm() {
this.isFormSaved = false;
this.savedFields=[];
this.getDynamicFormBasedOnSelection(this.parentFormName);
//fields getting from api call
}
getDynamicFormBasedOnSelection(formName: string) {
this.auth.getDynamicFormBasedOnSelction(formName, this.userAgencyCode).subscribe(
(result) => {
const str = JSON.stringify(result);
this.fields = JSON.parse(str);
this.isFormTypeSelected = true;
this.addEvents(this.fields);
});
}
Here I'm providing my screens which are for better understanding
Actually form.reset() just reset the form values. You need to reset the form directive too. for eg.
<form [formGroup]='authForm' (submit)='submitForm(formDirective)' #formDirective="ngForm" class="is-mat-form">
<mat-form-field>
<input matInput placeholder="Email ID" formControlName='login'>
<mat-error *ngIf="authForm.controls.login.hasError('required')">
Email is required
</mat-error>
<mat-error *ngIf="authForm.controls.login.hasError('email')">
Please enter a valid email address
</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput type="password" formControlName='password' placeholder="Password">
<mat-error *ngIf="authForm.controls.password.hasError('required')">
Password is required
</mat-error>
<mat-error *ngIf="authForm.controls.password.hasError('minlength')">
Password must be minimum 6 digit long.
</mat-error>
</mat-form-field>
and .ts file is
submitForm(formDirective: FormGroupDirective){
if (this.authForm.invalid) {
console.log('form submitted')
this.authForm.reset()
return;
}
this will reset the form values only, to reset the form error we need to reset the formdirective as well.
submitForm(formDirective: FormGroupDirective){
if (this.authForm.invalid) {
console.log('form submitted')
this.authForm.reset()
formDirective.resetForm();
return;
}