Send validation from a componentised ReactiveFrom input to another ReactiveForm - javascript

A traditional ReactiveForm you specify all inputs and add formControls and validation to those inputs on the related component HTML file. I am moving some of these inputs into their own components so they become sharable and reusable.
In my example StackBlitz there is already logic to use the validation to disable/enable the search input based on the form validation. However, now that I have moved one of these inputs into its own component, that relationship of being in the same formBuilder form for validation purposes no longer applies.
component.ts
this.registerForm = this.formBuilder.group({
firstName: ['', Validators.required],
lastName: ['', Validators.required],
// password: ['', [Validators.required, Validators.minLength(6)]]
});
I've commented out the password input as I am no longer building it in this form, however I still want to know its validation and apply that to this form so that search will only enable once all 3 inputs have been filled in and pass validation rules. Currently you only have to complete first and last name to enable the search input field.
Password now looks like this :
HTML
<password-input label="Password" [value]=""></password-input>

We can inject ControlContainer inside password-input component to get access to parentFormgroup. Then we can add password form control to existing formGroup dynamically.
component.ts
import { Component, OnInit, Input } from '#angular/core';
import { ControlContainer, FormControl, FormGroup, Validators } from '#angular/forms';
#Component({
selector: 'password-input',
templateUrl: './passwordinput.component.html'
})
export class PasswordInputComponent implements OnInit {
#Input('value') value = '';
#Input('label') label = 'test label';
control: FormControl;
formGroup:FormGroup;
constructor(private controlContainer:ControlContainer) {}
ngOnInit() {
const parentForm = (this.controlContainer['form'] as FormGroup);
parentForm.addControl('password',new FormControl(this.value,[Validators.required, Validators.minLength(6)]));
this.control = parentForm.get('password') as FormControl;
}
}
component.html
<div class="form-group">
<label>Password</label>
<input type="password" [formControl]="control" class="form-control" />
</div>
Working Example

Related

validating and display error messages for specific form controller in a form array angular

import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormGroup, FormArray, Validators, ValidatorFn, AbstractControl } from '#angular/forms';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
myForm: FormGroup;
constructor(private fb: FormBuilder) { }
ngOnInit() {
this.buildForm();
}
get vehicleGroup(): FormArray {
return <FormArray>this.myForm.get('vehicleGroup');
}
buildForm(): void {
this.myForm = this.fb.group({
name: ['', Validators.required],
vehicleGroup: this.fb.array([
this.fb.group({
vehicle: ['', Validators.required]
})
], [Validators.required]),
});
}
addVehicles(): void{
const itemToAdd = this.fb.group({
vehicle: ['', Validators.required]
});
this.vehicleGroup.push(itemToAdd);
}
deleteVehicle(i: number){
this.vehicleGroup.removeAt(i);
}
save(): void{
console.log('save');
}
}
<form novalidate (ngSubmit)="save()" [formGroup]="myForm">
<div class="form-group">
<label for="name">Name</label>
<input id="name" type="text" formControlName="name">
</div>
<div class="form-group">
<label for="vehicle">Vehicle</label>
<div formArrayName="vehicleGroup" *ngFor="let vehicle of vehicleGroup.controls; let i=index">
<div class="form-group" [formGroupName]="i">
<div>
<input id="{{'vehicle'+i}}" type="text" formControlName="vehicle">
<button type="button" (click)="deleteVehicle(i)"
*ngIf="vehicleGroup.length >= 2">remove
</button>
</div>
</div>
</div>
<div class="form-group">
<button type="button" class="link-button" [disabled]="!vehicleGroup.valid" (click)="addVehicles()">
+ Add more vehicles
</button>
</div>
</div>
</form>
I have this (stackBlitz) simple form created with angular formBuilder
I simple need to how to validate each elements of the dynamic formArray and display a unique message for each of them if that particular field is not valid.
I tried several solutions and also tried custom validator function with return an ValidatorFn. with that I could simply validate the formArray, but It's not good enough for my scenario and still I can not display messages according to the validate functions behavior. How ever to narrow it down, I simply need to know if there is a better way to validate each dynamic elements of this formArray. these are the validate rules.
each filed value should be unique.
need to validate real time
after adding few elements, someone edit previously added field, it also should be validate in real time with every other field values(this is where I got struck, I could validate upwards from the editing field, but not the fields below the editing field is validated accordingly)
If some one can show me some way to achieve this in a right way, It would be really great, since I'm struck with this for almost 3 days now, and still can't get an better solution.
I have used unique validator of #rxweb/reactive-form-validators in my project. It can be used directly in the component without creating any custom validation function.
You can edit your addVehicles method as follows:
addVehicles(): void{
const itemToAdd = this.fb.group({
vehicle: ['', RxwebValidators.unique()]
});
this.vehicleGroup.push(itemToAdd);
}
and adding
ReactiveFormConfig.set({"validationMessage":{"unique":"Enter unique value in the input"}});
to ngOnInit.
Here is the forked stackblitz

Angular 6 Required only one field from many fields Reactive Form

I am new in angular. I have one scenario where I need only one field required from 5 fields in the form, means if the user fills at least one field then form makes valid.
Thanks in advance.
Since you need to check for the validity of whole form only if one of the fields is non empty , You can manually set the validity like below :
if(!this.valid){
this.form.setErrors({ 'invalid': true});
}else{
this.form.setErrors(null);
}
Where this.valid is your condition based on which you can set the validity
You can check the example : https://angular-exmphk.stackblitz.io
You can also check the answer : FormGroup validation in "exclusive or" which does form validation based on some condition
Hope this helps
See Custom Validators and Cross-field validation in https://angular.io/guide/form-validation
Exact example from our app, where at least one phone number field must be entered. This is a Validator Function, i.e. implements https://angular.io/api/forms/ValidatorFn
import { AbstractControl } from "#angular/forms";
import { Member } from "../../members/member";
export function phone(control: AbstractControl): {[key: string]: any} {
if (!control.parent) return;
const form = control.parent;
const member: Member = form.value;
if (member.contactphone || member.mobile || member.contactphonesecond) {
[
form.controls['contactphone'],
form.controls['mobile'],
form.controls['contactphonesecond']
].forEach(control => {
control.setErrors(null);
});
} else return {'noPhone': 'None of contact phones is specified'};
}
Member is our class that defines all the form fields, your code will be different but the example of the custom validator should help.
Check this example of phone number validator
import { FormGroup, FormBuilder, Validators } from '#angular/forms';
import { NumberValidator } from '../validators/form-validators';
constructor(
private fb: FormBuilder){}
FormName: FormGroup = this.fb.group({
phoneNumber: ['', NumberValidator]
});
in form-validator file
import { AbstractControl, ValidatorFn } from '#angular/forms';
export const NumberValidator = (): ValidatorFn => {
return (control: AbstractControl): { [key: string]: any } | null => {
const mobileRegex = /^[()-\d\s]*$/g;
const result = mobileRegex.test(control.value);
return result ? null : { mobileInvalid: { value: control.value } };
};
};
let me know if you have any doubts.
<form [formGroup]="formdata">
<div class="form-group">
<label for="fieldlabel1">fieldLabel1</label>
<input type="text" id="fieldlabel1" formControlName="fieldName1" class="form-control"><br>
<label for="fieldlabel2">fieldLabel2</label>
<input type="text" id="fieldlabel2" formControlName="fieldName2" class="form-control">
</div>
</form>
<div class="row">
<div class="col-sm-12">
<button type="submit" value="submit" (click)="somefunctioncall()" [disabled]="formdata.invalid">
Submit
</button>
</div>
</div>
import { FormGroup, FormControl, Validators } from '#angular/forms';
import { OnInit } from '#angular/core';
export class test {
formdata: FormGroup;
ngOnInit() {
this.formdata = new FormGroup({
fieldName1: new FormControl("", Validators.compose([
Validators.required
])),
fieldName2: new FormControl("", Validators.compose([
// remove required validation for one you dont need.
]))
})
}
}

How to fire Angular 5 async validator onblur for FormGroup

I am using Angular version 5.0.1 and I'm trying to fire an AsyncValidator on a FormGroup on a blur event on one of the FormControls in the FormGroup.
I can get onBlur to work on a form control using the following:
name: ['', {updateOn: 'blur'}]
However, when I try to apply it to a FormGroup it doesn't work.
Is it possible to only fire an AsyncValidator onBlur on a FormGroup, and if not, what is the best way to execute the validator only when the user has finished entering data?
My only other option at the moment is to us some sort of debounce wrapper for my validator.
Just reading through here and it appears I should be able to use updateOn: 'blur' for a form group.
After
new FormGroup(value, {updateOn: 'blur'}));
new FormControl(value, {updateOn: 'blur', asyncValidators: [myValidator]})
In Angular5 Specifying the update mode is possible for both types of forms: template-driven forms and reactive forms.
First one is working for you. Here's the working example for reactive forms
Component.ts
import { Component, OnInit } from '#angular/core';
import { FormGroup, FormControl, Validators } from '#angular/forms';
#Component({
selector: 'form01',
templateUrl: './student.component.html',
styleUrls: ['./student.component.scss']
})
export class StudentComponent implements OnInit {
formTitle:string="Student registration ";
nameForm: FormGroup;
constructor() {
}
ngOnInit() {
this.nameForm = new FormGroup ({
firstname: new FormControl('', {
validators: Validators.required,
asyncValidators: [yourAsyncValidatorFunction],
updateOn: 'blur'
}),
lastname: new FormControl('', {
validators: Validators.required,
asyncValidators: [yourAsyncValidatorFunction],
updateOn: 'blur'
})
});
}
}
HTML
<form [formGroup]="nameForm" novalidate>
<div class="form-group">
<label>What's your first name? </label>
<input class="form-control" formControlName="firstname">
</div>
<div class="form-group">
<label>What's your last name?</label>
<input class="form-control" formControlName="lastname">
</div>
<button type="submit" class="btn btn-success">Submit</button>
</form>
<h3>Hello {{nameForm.value.firstname}} {{nameForm.value.lastname}}</h3>
It works on my side. Be aware that the FormBuilder may not support this yet.
this.formGroup = new FormGroup({
name: new FormControl('', {validators: []})
}, {
updateOn: 'blur'
});
I use this code snippet combining AbstractControlOptions with FormBuilder:
constructor(private formBuilder: FormBuilder) { }
this.signUpForm = new FormGroup(
this.formBuilder.group({
'email': ['', ValidationUtils.emailValidators()],
'fullname': ['', ValidationUtils.fullnameValidators()],
'idCard': ['', ValidationUtils.idCardValidators()],
'username': {value: '', disabled: true}
}).controls, {
updateOn: 'blur'
}
);
you can add this directive
import { Directive,Output, HostListener, EventEmitter } from '#angular/core';
#Directive({
selector: '[onBlur]'
})
export class OnBlurDirective {
// #Input('onBlur') onBlurFunction: Function;
#Output('onBlur') onBlurEvent: EventEmitter<any> = new EventEmitter();
constructor() { }
#HostListener('focusout', ['$event.target'])
onFocusout(target) {
console.log("Focus out called");
this.onBlurEvent.emit()
}
}
and use it in the HTML like this
<input type="text" (onBlur)="myFunction()"/>
and in the component you can define the function myFunction as you want

Simple require validation inside Custom Component input

I have multiple components and each having some input, select and other form elements. This all elements developed using template driven form.
But previously it was developed not in component wise, so we were able to check form status. Now as we have divided everything by component but validation we are not able to get it.
I have two questions.
For getting child component form elements to parent component, I am using ControlValueAccessor, is this correct way to do it ? In my child component i have multiple form controls like input, select and other.
Simple validations like required on input element, its not working inside child component. What should i do ?
This is Plunkr which mostly demonstrates what i wanted to achieve.
#Component({
selector: 'my-child',
template: `
<h1>Child</h1>
<input [ngModel]="firstName" (ngModelChange)="firstSet($event)" required>
<input [ngModel]="lastName" (ngModelChange)="lastSet($event)" required> <!-- required validation is not working -->
`,
providers: [
{provide: NG_VALUE_ACCESSOR, useExisting: Child, multi: true}
]
})
export class Child implements ControlValueAccessor, OnChanges {
private firstName: string,
private lastName: string;
fn: (value: any) => void;
constructor(){
}
writeValue(value: any) {
console.log("write value");
console.log(value);
if(value){
this.firstName = value.firstName;
this.lastName = value.lastName;
}
}
registerOnChange(fn: (value: any) => void) {
this.fn = fn;
}
registerOnTouched() {}
firstSet(v: string){
this.firstName = v;
this.fn({
firstName: this.firstName,
lastName: this.lastName
});
}
lastSet(v: string){
this.lastName = v;
this.fn({
firstName: this.firstName,
lastName: this.lastName
});
}
}
If you find wrong way please mention which is correct way to do it ?
Note: Please suggest solution which works on Template Driven Form. and i forgot to mention i am using Angular 2.4.8 version.
Not sure why you wouldn't use Reactive Forms - they are pretty much designed for validation.
You can also mix and match , you aren't locked into using Template Driven Forms just because you are using ngModel.
You can utilise Reactive Forms just for validation.
component.ts
form: FormGroup;
constructor(fb : FormBuilder) {
this.form = fb.group({
'firstName' : ['', Validators.required],
'lastName' : ['', Validators.required]
})
}
hasError(n: string){
return this.form.get(n).hasError('required') && this.form.get(n).touched;
}
template.html
<h1>Child</h1>
<input [ngModel]="firstName" name="firstName" [formControl]="form.get('firstName')" required>
<div *ngIf="hasError('firstName')">This field is required</div>
<input [ngModel]="lastName" name="lastName" [formControl]="form.get('lastName')" required> <!-- required validation is not working -->
<div *ngIf="hasError('lastName')">This field is required</div>
Plunkr found here

Validating forms by minlenght, maxlenght, email, number, required etc.. angular 2

I have a sample form in angular
app.html
<form [ngFormModel]="newListingForm" (ngSubmit)="submitListing(newListingForm.value)">
<input type="radio" #typeRadio="ngForm" [ngFormControl]="newListingForm.controls['typeRadio']" value="Event" id="type-event_radio" required>
<input type="radio" #typeRadio="ngForm" [ngFormControl]="newListingForm.controls['typeRadio']" value="Venue" id="type-venue_radio" required>
<input type="text" placeholder="New Title" #mainTitleInput="ngForm" [ngFormControl]="newListingForm.controls['mainTitleInput']" id="main-title_input" class="transparent">
<input type="email" #emailAddressInput="ngForm" [ngFormControl]="newListingForm.controls['emailAddressInput']" id="email-address_input" required>
<!-- etc -->
</form>
app.ts
import {Component} from 'angular2/core';
import {FORM_DIRECTIVES, FormBuilder, ControlGroup, Validators} from 'angular2/common';
#Component({
templateUrl : 'app/components/new-listing/app.html',
directives: [FORM_DIRECTIVES]
})
export class NewListingComponent {
//Helpers
newListingForm: ControlGroup;
//Costructor
constructor(
fb: FormBuilder
){
//New Listing Form
this.newListingForm = fb.group({
'typeRadio': ['', Validators.required],
'mainTitleInput': ['', Validators.required],
'emailAddressInput': ['', Validators.required],
});
}
//TODO Form submission
submitListing(value: string): void {
console.log("Form Submited");
}
}
At the moment only validation I see is default one provided by google on required fields. it all goes away. I've been looking into docs and min/max length seems to be easy to implement with my current setup, however there is another interesting asp NG_VALIDATORS Which I think (not sure) provides validation by looking at things like type="email", required etc.. inside a form. Essentially I want to be able to display messages like invalid email this field is required etc.. through angular2..
Simply use expression like this (sample with Bootstrap) to leverage attributes (valid or not, errors, ...) of the controls you associated with your inputs:
<form [ngFormModel]="newListingForm">
<!-- Field name -->
<div class="form-group form-group-sm"
[ngClass]="{'has-error':!newListingForm.controls.mainTitleInput.valid}">
<label for="for"
class="col-sm-3 control-label">Name:</label>
<div class="col-sm-8">
<input [ngFormControl]="newListingForm.controls.mainTitleInput"
[(ngModel)]="newListing.mainTitleInput"/>
<span *ngIf="!newListingForm.controls.mainTitleInput.valid"
class="help-block text-danger">
<span *ngIf="newListingForm.controls.errors?.required">
The field is required
</span>
</span>
</div>
</div>
</form>
With this sample, fields with errors will be displayed in red and error messages is dynamically displayed.
Moreover you add implement custom validators for your fields:
export class CityValidator {
static validate(control: Control) {
var validValues = [ 'mycity', ... ];
var cnt = validValues
.filter(item => item == control.value)
.length;
return (cnt == 0) ? { city: true } : null;
}
}
Simply add then into your validators for fields:
this.newListingForm = fb.group({
'myfield': ['', Validators.compose([
Validators.required, CityValidator.validate])]
(...)
});
Hope it helps you,
Thierry
#Ilja you are on good track, improve your code with this:
this.newListingForm = fb.group({
'typeRadio': ['', Validators.compose([Validators.required, Validators.minLength(2), Validators.maxLength(40)])],
'mainTitleInput': ['', Validators.compose([Validators.required, Validators.minLength(2), Validators.maxLength(40)])],
'emailAddressInput': ['', Validators.compose([Validators.required, Validators.minLength(2), Validators.maxLength(40)])],
});
Consider this example ( with custom email Validator )
https://plnkr.co/edit/5yO4HviXD7xIgMQQ8WKs?p=info

Categories