bind form validation in other components - javascript

I have components in order to add user.
This is my form component:
createForm(): void {
this.courseAddForm = this.formBuilder.group({
name: ['', [
Validators.required,
Validators.maxLength(this.val.maxLen.title)
]],
roleId: ['', Validators.compose([Validators.required])]
});
}
name : username ,
roleId : selected role from dropdown .
I create a acomponents for roleId . <kt-searchable-dropdown>
HTML :
<form id="courseAddForm" [formGroup]="courseAddForm" (ngSubmit)="onSubmit()" autocomplete="off">
<div class="form-group kt-form__group row">
<!-- title -->
<div class="col-lg-6 kt-margin-bottom-20-mobile">
<mat-form-field class="mat-form-field-fluid" appearance="outline">
<mat-label>{{'GENERAL.TITLE' | translate}} *</mat-label>
<input matInput formControlName="title" [placeholder]="'GENERAL.TITLE' | translate">
<!--requied error-->
<mat-error *ngIf="courseAddForm.get('title').errors?.required">
{{ 'VALIDATION.REQUIRED.TITLE' | translate }}</mat-error>
<!--length error-->
<mat-error *ngIf="courseAddForm.get('title').errors?.maxlength">
{{'VALIDATION.MAX_LENGTH' | translate}} {{val.maxLen.title}}
</mat-error>
</mat-form-field>
</div>
<div class="col-lg-6 kt-margin-bottom-20-mobile">
<kt-searchable-dropdown [formGroup]="courseAddForm" [formcontrolName]="'courseId'" (selectedId)="selectedCourse($event)"
[formTitle]="'COURSE.COURSE_GROUP'" [url]="url"></kt-searchable-dropdown>
</div>
</div>
</form>
This is my component for roleId dropdown :
TS :
export class SearchableDropdownComponent implements OnInit {
#Input() url: string;
#Input() formTitle: string;
#Input() ItemId: number;
#Input() formcontrolName: string;
#Input() formGroup: FormGroup;
#Output() selectedId = new EventEmitter<number>();
loading = false;
values: KeyValue[];
title: string;
fC: FormControl;
constructor(
private searchService: SearchableDropDownService,
private cdRef: ChangeDetectorRef) {
}
ngOnInit(): void {
this.getValues(null);
}
getValues(event): void {
this.cdRef.detectChanges();
this.loading = true;
let model = {} as SendDateModel;
model.page = 1;
model.pageSize = 60;
model.title = event;
this.searchService.getAll(this.url, model).subscribe(data => {
this.values = data['result']['records'];
this.cdRef.detectChanges();
this.loading = false;
});
}
}
HTML :
<form [formGroup]="formGroup">
<mat-form-field appearance="outline">
<mat-label>{{formTitle| translate}} *</mat-label>
<mat-select formControlName="courseId" >
<div class="col-lg-12 mt-4 kt-margin-bottom-20-mobile">
<mat-form-field class="mat-form-field-fluid" appearance="outline">
<mat-label>{{'GENERAL.TITLE' | translate}} *</mat-label>
<input (keyup)="getValues($event.target.value)" matInput
[placeholder]="'GENERAL.TITLE' | translate">
</mat-form-field>
</div>
<mat-progress-bar *ngIf="loading" class="mb-2" mode="indeterminate"></mat-progress-bar>
<mat-option (click)="emitdata(item.key)" *ngFor="let item of values"
[(ngModel)]="ItemId" [value]="item.key">
{{item.value}}
</mat-option>
<mat-error *ngIf="formGroup.get('courseId').errors?.required">
{{ 'COURSE.VALIDATIONS.REQUIRED.CLASS_LEVEL' | translate }}</mat-error>
</mat-select>
</mat-form-field>
I write this code but it is not work.
i need to bind validation in the form in this components . for example when roleId is required and user not select item ,show error that the roleId is reqierd , I need to show it in this components SearchableDropdownComponent . how can i do this ????

You can achieve this by using angular observables. Create a service to hold the error state in your first component.
ErrorService.ts
import { Injectable } from '#angular/core';
import { Observable, Subject } from 'rxjs/BehaviorSubject';
#Injectable()
export class ErrorService {
private errorObj = new Subject<any>({});
data = this.errorObj.asObservable();
constructor() { }
updatedErrorObj(error){
this.errorObj.next(error);
}
getErrorObj(){
return this.errorObj.asObservable();
}
}
Inside your first component, you need to create an instance of this service and on error stage update the error object.
FirstComponent.ts
// Creating an instance of service in constructor
constructor(private errorService: ErrorService) { }
onSubmit() {
if(this.courseAddForm.invalid) {
// Updating error object
cont errorObject = { key: 'value' }; //Create your custom error object here
this.errorService.updatedErrorObj(errorObject);
}
}
Caputure this error in the SearchableDropdownComponent
// Creating an instance of service in constructor
constructor(private errorService: ErrorService) {
errorService.getErrorObj.subscribe(data => {
// do what ever needs doing when data changes
// You will recive the error object here.
})
}
Please refer this blog for details.

Related

Angular forms require one of two form groups to be valid

I am trying to implement a reactive angular form where either A or B has to be entered. A is a unique id and B is a set of values which identify the id. Now I try to validate a Form that is valid if either A is entered or B is entered including all the required values. I found several solutions that implement this behavior based on FormFields but was not able to get it working with the group of values.
<form class="container" [formGroup]="myForm" (ngSubmit)="onSubmit()">
<mat-form-field class="w-1/2">
<mat-label>ID</mat-label>
<input matInput type="number" formControlName="id">
</mat-form-field>
<div class="grid grid-cols-3 gap-4" formGroupName="innerGroup">
<mat-form-field>
<mat-label>First Name</mat-label>
<input matInput type="number" formControlName="firstName">
</mat-form-field>
<mat-form-field>
<mat-label>Last Name</mat-label>
<input matInput type="number" formControlName="lastName">
</mat-form-field>
</div>
</form>
My first idea was to override the default validator for the form but I could not figure out how to do that. Not even sure if it would be possible. I was trying to adjust https://stackoverflow.com/a/48714721 to work in my scenario but I had no idea how to get it to work because of the additional complexity with the inner form group.
Using angular 14 I was able to produce a similar result to what you are describing I am not sure it will 100% solve your issue however it might help.
Basically what I did was create a validator function that is to be applied at the group level. This validator will check the valid state of any given controls be they a FormGroup or a FormControl. However, this alone will not solve the problem as if you have a form group angular will see that any underling control or group that is invalid will also invalidate the parent. So what I did was call .disable() on any control that was being checked by the validator function. This will disable the UI element and disable validation checking by angular allowing the parent to be considered valid when one of the children is valid but the other is invalid effectively creating a one and only one validator.
My specific example I was trying to get the OnlyOneValidator to work for a MatStepper.
Validator
export function onlyOneValidator(controlKeys: string[]) {
return (control: AbstractControl): ValidationErrors | null => {
let countOfValidControls = 0;
for (let key of controlKeys) {
const controlToCheck = control.get(key);
if (controlToCheck === null || controlToCheck === undefined) {
throw new Error(`Error: Invalid control key specified key was ${key}`);
}
countOfValidControls += controlToCheck?.valid ? 1 : 0;
}
if (countOfValidControls !== 1) {
// the count is not exactly one
return {
onlyOneValid: {
actualValidCount: countOfValidControls,
expectedValidCount: 1
}
};
}
return null;
};
}
Controller
#Component({
selector: "app-equipment-creation-page",
templateUrl: "./equipment-creation-page.component.html",
styleUrls: ["./equipment-creation-page.component.scss"],
})
export class EquipmentCreationPageComponent implements OnInit, OnDestroy {
public categories = [null, "Tools", "Vehicles"];
constructor(private _formBuilder: FormBuilder) {}
public categoryInformationGroup = this._formBuilder.group({
existingCategory: this._formBuilder.group({
category: new FormControl(null, [ Validators.required ])
}),
newCategory: this._formBuilder.group({
name: new FormControl("", [Validators.required]),
description: new FormControl("", [Validators.required])
})
}, {
validators: [
onlyOneValidator(["existingCategory", "newCategory"])
],
});
public ngOnDestroy(): void {
this.subscriptions.forEach(sub => {
sub.unsubscribe();
});
}
private subscriptions: Subscription[] = [];
public ngOnInit(): void {
this.subscriptions.push(this.categoryInformationGroup.controls.existingCategory.statusChanges.pipe(
tap((status: string) => {
if (status === "VALID") {
this.categoryInformationGroup.controls.newCategory.disable();
} else {
this.categoryInformationGroup.controls.newCategory.enable();
}
})
).subscribe());
this.subscriptions.push(this.categoryInformationGroup.controls.newCategory.statusChanges.pipe(
tap((status: string) => {
if (status === "VALID") {
this.categoryInformationGroup.controls.existingCategory.disable();
} else {
this.categoryInformationGroup.controls.existingCategory.enable();
}
})
).subscribe());
}
}
Template
<form [formGroup]="categoryInformationGroup.controls.existingCategory">
<mat-form-field>
<mat-label>Apply to existing category?</mat-label>
<mat-select formControlName="category">
<mat-option *ngFor="let category of categories" [value]="category">
{{ category ?? "None" }}
</mat-option>
</mat-select>
</mat-form-field>
</form>
OR
<form [formGroup]="categoryInformationGroup.controls.newCategory">
<mat-form-field>
<mat-label>Create New Category</mat-label>
<input matInput formControlName="name" placeholder="Name">
<mat-error *ngIf="categoryInformationGroup.controls.newCategory.controls.name.hasError('required')">This field
is required
</mat-error>
</mat-form-field>
<mat-form-field>
<mat-label>Create New Category</mat-label>
<input matInput formControlName="description" placeholder="Description">
<mat-error *ngIf="categoryInformationGroup.controls.newCategory.controls.description.hasError('required')">
This field is required
</mat-error>
</mat-form-field>
</form>
Hopefully this helps or at least gives you some ideas about how to approach this. If anyone else has any thoughts on this please let me know I would love to find a better way to do this.

dynamicly set placeholder of input with angular reactive form

I have a component like this:
Form: FormGroup;
constructor(private fb: FormBuilder) { }
ngOnInit(): void {
this.Form = this.fb.group({
contratacion: [],
beneficios: this.fb.array([ this.nuevoBeneficio() ])
});
}
beneficiosBase() {
let _form = this.Form.get('beneficios') as FormArray
_form.push(this.nuevoBeneficio('Ejemplo: Prestaciones de ley'));
_form.push(this.nuevoBeneficio('Ejemplo: Bono por puntualidad'));
_form.push(this.nuevoBeneficio('Ejemplo: Prestaciones de ley'));
}
nuevoBeneficio(textoPlaceholder?:string): FormGroup {
return this.fb.group({
nombre: [],
descripcion: [textoPlaceholder || null],
});
}
And my html looks like this:
<ng-container *ngFor="let item of Form.get('beneficios').controls; let i = index;">
<input formControlName="nombre" type="text" class="form-control" id="" [placeholder]="descripcion">
</ng-container>
I realized this is not the way to set the placeholder value since it expects a formControlName 'descripcion', but I wonder if there's a way to do this.
You could create an auxiliary array like this listOfPlaceholder: string[]; then in your method beneficiosBase
beneficiosBase() {
_form.push(this.nuevoBeneficio();
this.listOfPlaceholders.push('Ejemplo: Prestaciones de ley');
}
Your HTML should looks like this
<ng-container *ngFor="let item of Form.get('beneficios').controls; let i = index;">
<input formControlName="nombre" type="text" class="form-control" id="" [placeholder]="listOfPlaceholders[i]">
</ng-container>

Angular 5 - Uncheck all checkboxes function is not affecting view

I'm trying to include a reset button on a Reactive form in Angular 5. For all form fields, the reset is working perfectly except for the multiple checkboxes, which are dynamically created.
Actually the reset apparently happens for the checkboxes as well, but the result is not reflected in the view.
service.component.html
<form [formGroup]="f" (ngSubmit)="submit()">
<input type="hidden" id="$key" formControlName="$key">
<div class="form-group">
<label for="name">Name</label>
<input type="text" class="form-control" id="name" formControlName="name">
</div>
<br/>
<p>Professionals</p>
<div formArrayName="prof">
<div *ngFor="let p of professionals | async; let i = index">
<label class="form-check-label">
<input class="form-check-input" type="checkbox (change)="onChange({name: p.name, id: p.$key}, $event.target.checked)" [checked]="f.controls.prof.value.indexOf(p.name) > -1"/>{{ p.name }}</label>
</div>
<pre>{{ f.value | json }}</pre>
</div>
<br/>
<button class="btn btn-success" type="submit" [disabled]="f?.invalid">Save</button>
<button class="btn btn-secondary" type="button" (click)="resetForm($event.target.checked)">Reset</button>
</form>
service.component.ts
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormControl, FormGroup, Validators, FormArray } from '#angular/forms'
import { AngularFireAuth } from 'angularfire2/auth';
import { Router, ActivatedRoute } from '#angular/router';
import { AngularFireDatabase, FirebaseListObservable, FirebaseObjectObservable} from 'angularfire2/database';
import { Service } from './service';
export class ServiceComponent implements OnInit {
f: FormGroup;
userId: string;
$key: string;
value: any;
services: FirebaseListObservable<Service[]>;
service: FirebaseObjectObservable<Service>;
professionals: FirebaseListObservable<Service[]>;
profs: FirebaseListObservable<Service[]>;
constructor(private db: AngularFireDatabase,
private afAuth: AngularFireAuth,
private route: ActivatedRoute,
private router: Router,
private fb: FormBuilder) {
this.afAuth.authState.subscribe(user => {
if(user) this.userId = user.uid
this.services = db.list(`services/${this.userId}`);
})
this.afAuth.authState.subscribe(user => {
if(user) this.userId = user.uid
this.professionals = this.db.list(`professionals/${this.userId}`);
})
}
ngOnInit() {
// build the form
this.f = this.fb.group({
$key: new FormControl(null),
name: this.fb.control('', Validators.required),
prof: this.fb.array([], Validators.required)
})
}
onChange(name:string, isChecked: boolean) {
const profArr = <FormArray>this.f.controls.prof;
if(isChecked) {
profArr.push(new FormControl(name));
console.log(profArr.value);
} else {
let index = profArr.controls.findIndex(x => x.value == name)
profArr.removeAt(index);
console.log(profArr.value);
}
}
resetForm(){
let profArr = <FormArray>this.f.controls.prof;
this.f.controls.name.setValue('');
profArr.controls.map(x => x.patchValue(false));
this.f.controls.$key.setValue(null);
}
}
service.ts
export class Service {
$key: string;
name: string;
professionals: string[];
}
The result of the code above, displayed by line <pre> {{f.value | json}} </ pre> is:
When I fill out the form:
{
"$key": null,
"name": "Test service",
"prof": [
{
"name": "Ana Marques",
"id": "-LEZwqy3cI3ZoYykonWX"
},
{
"name": "Pedro Campos",
"id": "-LEZz8ksgp_kItb1u7RE"
}
]
}
When I click on Reset button:
{
"$key": null,
"name": "",
"prof": [
false,
false
]
}
But checkboxes are still selected:
What is missing?
I would stop using FormControls to handle what is basically state.
You have some code which loads the professionals property of the component. Just add to that data a checked property and change the type of professionals to Service[]:
this.db.list(`professionals/${this.userId}`)
.subscribe(professionals => {
professionals.forEach(p => p.checked = false);
this.professionals = professional;
});
Btw, you don't have a checked property on your Service type, so either you extend it or transform professionals in something else (i.e. a CheckableService).
The template becomes:
<div *ngFor="let p of professionals; let i = index">
<label class="form-check-label">
<input class="form-check-input" type="checkbox (change)="onChange(p, $event.target.checked)" [checked]="p.checked"/>{{ p.name }}</label>
</div>
And the onChange method becomes:
onChange(professional: Service, isChecked: boolean) {
professional.checked = isChecked;
this.profArr = this.professionals.filter(p => p.checked);
}
It seems a lot cleaner to me (you will need to adjust for the checked parameter not being in the Service type, but the code is simply adaptable). No messing with controls, only data cleanly flowing through your component.
You are not referencing your checkboxes. Give them a name using the index.
<div formArrayName="prof">
<div *ngFor="let p of professionals | async; let i = index">
<label class="form-check-label">
<input [formControlName]="i" class="form-check-input" type="checkbox (change)="onChange({name: p.name, id: p.$key}, $event.target.checked)" [checked]="f.controls.prof.value.indexOf(p.name) > -1"/>{{ p.name }}</label>
</div>
<pre>{{ f.value | json }}</pre>
</div>
This is my checkall checkbox
<input type="checkbox" value="a" (click)="checkAll" [checked]="checkAll">
This i a regular checkbox, i used this whithin an ngfor
<input type="checkbox" value="a" (click)="check(object)" name="checkbox" [checked]="true">
And the checkall checkboxes function
let elements = document.getElementsByTagName('input');
if (this.checkAll) {
this.checkAll = false;
for (let i = 0; i < elements.length; i++) {
if (elements[i].type == 'checkbox') {
elements[i].checked = false;
}
}
}
else....the other way

Form validation is not working in angular?

I want to check whether the dropdown is empty.
Need to show the required message and
If not empty, enable the submit button.
If empty, disable the submit button. Below is my html
Below is my html
<form [formGroup]="myForm" (ngSubmit)="save()" >
<mat-form-field>
<mat-select formControlName="name" placeholder="Element List" (selectionChange)="elementSelectionChange($event)" required>
<mat-option *ngFor="let element of Elements" [value]="element.name">
{{ element.name }}
</mat-option>
</mat-select>
<mat-error *ngIf="myForm.hasError('required', 'name')">Please choose an name</mat-error>
</mat-form-field>
<mat-form-field>
<mat-select formControlName="symbol" placeholder="Symbol List" required>
<mat-option *ngFor="let element of selectedElementSymbol" [value]="element.symbol">
{{ element.symbol }}
</mat-option>
</mat-select>
<mat-error *ngIf="myForm.hasError('required', 'symbol')">Please choose an symbol</mat-error>
</mat-form-field>
<div mat-dialog-actions>
<button mat-button (click)="onNoClick()">Cancel</button>
<button type="submit" mat-button cdkFocusInitial>Add</button>
</div>
</form>
below is my component
export class DialogOverviewExampleDialog {
myForm: FormGroup;
symbol = new FormControl('', Validators.required);
name = new FormControl('', Validators.required);
constructor(
public dialogRef: MatDialogRef<DialogOverviewExampleDialog>,
#Inject(MAT_DIALOG_DATA) public data: any,
private formBuilder: FormBuilder) {
this.myForm = this.formBuilder.group({
name: [this.name],
symbol: [this.symbol],
});
}
save() {
console.log(this.myForm.value);
}
}
updated demo here
You are currently assigning formcontrols to your formcontrol, whereas you want to assign value to your form controls. Below you are assigning form control name to formcontrol name:
WRONG:
name = new FormControl('', Validators.required);
this.myForm = this.formBuilder.group({
'name': [this.name, Validators.required],
// ...
});
so instead, just declare name, do what you want with the value, then assign that value to your form control...
CORRECT:
name: string;
this.myForm = this.formBuilder.group({
'name': [this.name, Validators.required],
// ...
});
Then just add [disabled]="!myForm.valid" on your submit button.
As for the other question, by default Angular material doesn't show error message unless the field has been touched, so you need to have a custom error state matcher that shows the error even when field is not touched (if that is what you want):
import {ErrorStateMatcher} from '#angular/material/core';
export class MyErrorStateMatcher implements ErrorStateMatcher {
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
return !!(control.invalid);
}
}
and in your TS, declare a error state matcher:
matcher = new MyErrorStateMatcher();
and use in template:
<mat-select formControlName="name" ... [errorStateMatcher]="matcher">
Here's your
StackBlitz
To make the submit button disabled (link)
<button type="submit" [disabled]="!myForm.valid" mat-button cdkFocusInitial>Add</button>
In order to check whether the dropdown is empty or not, you need to make the form fields required like
this.myForm = this.formBuilder.group({
'name': [this.name, Validators.required],
'symbol': [this.symbol, [Validators.required]]
});
Inorder to show the error highlight you need to add an ngClass in the templete like.
[ngClass]="{'error': myForm.controls.name.valid == false}"
You have to insert the validator into the form builder object.
Have a quick look at:
https://angular.io/guide/reactive-forms#validatorsrequired
this.heroForm = this.fb.group({
name: ['', [Validators.required] ],
});
As for the button:
<button type="submit" [disabled]="!form.valid" mat-button cdkFocusInitial>Add</button>

How can i pass the id while editing in angular?

I want to pass the id when only user edit the document .here is my html and component.ts
Html
<h1 mat-dialog-title>Hi {{data.name}}</h1>
<form [formGroup]="addTaskForm" (ngSubmit)="save()" >
<mat-form-field>
<mat-select formControlName="name" placeholder="Element Name">
<mat-option *ngFor="let element of Elements" [value]="element.name">
{{ element.name }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field>
<mat-select formControlName="symbol" placeholder="Element symbol">
<mat-option *ngFor="let element of Elements" [value]="element.symbol">
{{ element.symbol }}
</mat-option>
</mat-select>
</mat-form-field>
<div mat-dialog-actions>
<button type="button" mat-button (click)="onNoClick()">Cancel</button>
<button type="submit" mat-button cdkFocusInitial>Add</button>
</div>
</form>
component.ts
export class DialogOverviewExampleDialog {
Elements = ELEMENT_DATA;
addTaskForm: FormGroup;
symbol = new FormControl('', Validators.required);
name = new FormControl('', Validators.required);
id = new FormControl('', Validators.required);
constructor(
public dialogRef: MatDialogRef<DialogOverviewExampleDialog>,
#Inject(MAT_DIALOG_DATA) public data: any,
private formBuilder: FormBuilder) {
if(data.element){
console.log(data.element.name);
this.name = data.element.name;
this.symbol = data.element.symbol;
this.id = data.element.id;
}
this.addTaskForm = this.formBuilder.group({
name: this.name,
symbol: this.symbol,
id: this.id
});
}
onNoClick(): void {
this.dialogRef.close();
}
save(Element) {
console.log('working');
console.log(this.addTaskForm.value);
}
}
when user click add popup will open with two dropdowns and add a button that time I don't want id but when user click edit button popup window will open with particular row details that time if user click add button I should get the particular id , name, symbol in component
demo
Just set the id: this.id while you are adding
this.addTaskForm = this.formBuilder.group({
name: this.name,
symbol: this.symbol,
id: this.id
});
also pass the id with your modal,
if(data.element){
console.log(data.element.name);
this.name = data.element.name;
this.symbol = data.element.symbol;
this.id = data.element.id;
}
DEMO

Categories

Resources