Conditional required validator directive in Angular 2 - validation

I need to make certain form fields required or not based on the value of other fields. The built-in RequiredValidator directive doesn't seem to support this, so I created my own directive:
#Directive({
selector: '[myRequired][ngControl]',
providers: [new Provider(NG_VALIDATORS, { useExisting: forwardRef(() => MyRequiredValidator), multi: true })]
})
class MyRequiredValidator {
#Input('myRequired') required: boolean;
validate(control: AbstractControl): { [key: string]: any } {
return this.required && !control.value
? { myRequired: true }
: null;
}
}
Sample usage:
<form>
<p><label><input type="checkbox" [(ngModel)]="isNameRequired"> Is Name Required?</label></p>
<p><label>Name: <input type="text" [myRequired]="isNameRequired" #nameControl="ngForm" ngControl="name" [(ngModel)]="name"></label></p>
<p *ngIf="nameControl.control?.hasError('myRequired')">This field is required.</p>
</form>
This works fine if the user first toggles the check box and then types or erases text in the text box. However, if the user toggles the check box while the text box is blank, then the validation message doesn't update appropriately.
How can I modify MyRequiredValidator to trigger validation when its required property is changed?
Note: I'm looking for a solution that only involves changes to MyRequiredValidator. I want to avoid adding any logic to the App component.
Plunker: https://plnkr.co/edit/ExBdzh6nVHrcm51rQ5Fi?p=preview

I would use something like that:
#Directive({
selector: '[myRequired][ngControl]',
providers: [new Provider(NG_VALIDATORS, { useExisting: forwardRef(() => MyRequiredValidator), multi: true })]
})
class MyRequiredValidator {
#Input('myRequired') required: boolean;
ngOnChanges() {
// Called when required is updated
if (this.control) {
this.control.updateValueAndValidity();
}
}
validate(control: AbstractControl): { [key: string]: any } {
this.control = control;
return this.required && !control.value
? { myRequired: true }
: null;
}
}
See this plunkr: https://plnkr.co/edit/14jDdUj1rdzAaLEBaB9G?p=preview.

Related

VeeValidate infinite args

I am trying to make VeeValidate 3 infinite args rule work:
https://logaretm.github.io/vee-validate/guide/basics.html#rule-arguments
Here is the code :
const aValidationFunction(value, values) {
//iterations and business here
}
extend('my_rule', {
validation : aValidationFunction,
computesRequired : true,
immediate : true,
// message etc.....
})
My rule usage im my code :
Child Component
<ValidatedInput name="inputA"
:label="this.$t('XXXX.yyy')"
:iid="'inputA'"
type="text"
:rules="myRulesA"
:immediate="true"
v-model="XXXX.yyyy">
</ValidatedInput>
<ValidatedInput name="inputB"
:label="this.$t('XXXXX.zzzzz')"
:rules="myRulesB"
:iid="'inputB'"
type="text"
:immediate="true"
v-model="XXXX.zzzzz">
</ValidatedInput>
export default {
name: 'ChildComponent',
components: { ValidatedInput },
props: ['myRulesA', 'myRulesB'], ...........
Father component
<ValidationObserver>
<ChildComponent :my-rules-A="rulesA" :my-rules-A="rulesB" />
............
</ValidationObserver>
computed : {
rulesA() {
return { my_rule: ['#inputB', '#anotherInputBBB'], another_rule: ['#anotherInputA', '#anotherInputB'] //a rule with two args, it works};
},
rulesB() {
return {my_rule: ['#inputA', '#anotherInputAAAAA']};
},
but if i put console.log(values) in my code aValidationFunction it prints me ['#inputA', '#inputB']
Any idea ?
Targeting other fields in cross field validations doesn't work with infinite parameters.
https://github.com/logaretm/vee-validate/issues/2839#issue-667100530

Make react-select 2.0.0 <Async> work with redux-form <Field>

react-select just upgraded to 2.0.0 so google results on the first three pages are all about older versions, even the official document, and none of them helped.
My select box can show all options correctly, but redux form won't pick up the value, with the warning: Warning: A component is changing a controlled input of type hidden to be uncontrolled.
I wonder what have I missed here...
Form component:
<Field
name="residentialAddress"
label = "Residential Address"
type="select"
component={AddressField}
validate={required}
/>
Component
export class AddressField extends Component {
searchAddress = input => {
let options = []
return myPromise(input)
.then(suggestions => {
options = suggestions.map(suggestion =>
({
label: suggestion.label,
data: suggestion.value
})
)
return options;
}
).catch(
error => {
return options = [{ label: "Auto fetching failed, please enter your address manually", value: "", isDisabled: true }];
}
);
};
render() {
const {
input,
label,
meta: { touched, error },
type
} = this.props;
return(
<FormGroup>
<ControlLabel>{label}</ControlLabel>
<Async
{...input}
placeholder={label}
isClearable={true}
getOptionValue={(option) => option.residentialAddress}
onChange = { value => input.onChange(value.data) }
loadOptions={this.searchAddress}
/>
{ touched && error && <span>{error}</span> }
</FormGroup>
)
}
Solution: Simply remove the {...input} in <Async>.
Unlike regular custom Field component where we need to pass in {input}, the react-select Async component seems to take care of itself very well and doesn't require any intervene. Someone may explain it in a more professional way perhaps...
Also worth mention for those who come across this question:
loadOptions with promise used to require object {options: options} as return type. Now it changes to just array (options as in my code). But I didn't find any document that mentions this one.
Hope this could help.

How do I dynamically set validations fields in vuelidate

I'm using VueJS2 with vuelidate library. I can validate the fields based on the validation objects. The validation will execute during computed time. But My validations objects is fixed, instead of dynamic. I have some fields will hide based on the selection.
import { validationMixin } from 'vuelidate'
import { required, maxLength, email } from 'vuelidate/lib/validators'
export default {
mixins: [validationMixin],
validations: {
company_name: { required },
company_position_title: { required }
},
methods: {
submit(){
this.$v.touch();
if(this.$v.$invalid == false){
// All validation fields success
}
}
}
}
HTML
<v-select
label="Who are you?"
v-model="select" // can be 'company' or 'others'
:items="items"
:error-messages="selectErrors"
#change="$v.select.$touch();resetInfoFields();"
#blur="$v.select.$touch()"
required
></v-select>
<v-text-field
label="Company Name"
v-model="company_name"
:error-messages="companyNameErrors"
:counter="150"
#input="$v.companyName.$touch()"
#blur="$v.companyName.$touch()"
v-show="select == 'Company'"
></v-text-field>
<v-text-field
label="Company Position Title"
v-model="company_position_title"
:error-messages="companyPositionErrors"
:counter="150"
#input="$v.companyPosition.$touch()"
#blur="$v.companyPosition.$touch()"
v-show="select == 'Company'"
></v-text-field>
<v-btn #click="submit">submit</v-btn>
Problem
When I select 'other' option and click submit, the this.$v.$invalid is still true. It should be false as there is no validation fields required.
When I select 'company', that two fields must required and validated.
you need a dynamic validation schema
validations () {
if (!this.select === 'company') {
return {
company_name: { required },
company_position_title: { required }
}
}
}
More info: Dynamic validation schema
Another way is using requiredIf
itemtocheck: {
requiredIf: requiredIf(function () {
return this.myitem !== 'somevalue'
}),
minLength: minLength(2) },

angular 2 pass array to custom validator (Template driven form)

I need to pass an array of objects to the a2 custom validator and then I d like to validate the value of the template driven form field against the records in that array.
However I can not retrieve the object inside the validator.
The only thing I can see is its name as string.
Any help is kindly appreciated.
<label class="btn btn-default btn-sm"
[(ngModel)]="krediHesaplamaModel.radioModelKrediTur" name="krediHesaplamaModel.radioModelKrediTur"
btnRadio="0"
(click)="onRadioButtonKrediHesaplamaTurChange()" krediTuruValidator="this.krediList" >
import { Directive, forwardRef, Attribute } from '#angular/core';
import { Validator, AbstractControl, NG_VALIDATORS } from '#angular/forms';
import {Kredi} from '../kredi';
#Directive({
selector: '[krediTuruValidator][formControlName],[krediTuruValidator][formControl],[krediTuruValidator][ngModel]',
providers: [{ provide: NG_VALIDATORS, useExisting: forwardRef(() => KrediTuruValidator), multi: true },
]
})
export class KrediTuruValidator implements Validator {
constructor( public krediTuruValidator: Kredi[]) { }
validate(c: AbstractControl): { [key: string]: any } {
console.log('KL' + this.krediTuruValidator[0].krediTuru); //UNDEFINED
let v = c.value;
return null;
}
}
I resolved this issue by delegating the validation function to another method in the component. That way I can access any object I desire
import { Directive, forwardRef, Attribute, Input } from '#angular/core';
import { Validator, ValidatorFn, AbstractControl, NG_VALIDATORS } from '#angular/forms';
#Directive({
selector: '[krediVadeSayisiValidator][formControlName],[krediVadeSayisiValidator][formControl],[krediVadeSayisiValidator][ngModel]',
providers: [{ provide: NG_VALIDATORS, useExisting: forwardRef(() => KrediVadeSayisiValidator), multi: true },]
})
export class KrediVadeSayisiValidator implements Validator {
#Input() krediVadeSayisiValidator: ValidatorFn; //same name as the selector
validate(c: AbstractControl): { [key: string]: any } {
return this.krediVadeSayisiValidator(c);
}
}
How do I access it inside the template?
<input type="number" class="form-control" name="krediVadeSayisi" [(ngModel)]="krediHesaplamaModel.krediVadeSayisi" #krediVadeSayisi="ngModel"
required maxlength="2" [krediVadeSayisiValidator]="validateKrediVadeSayisi()" /> /*this function is inside the component*/

Angular2 Model Driven Dynamic Form Validation

Please Refer to my plunkr
I've been playing around with the new Angular 2 RC and I think I have figured out how the form validation works.
First I build 2 objects called defaultValidationMessages and formDefinition
private defaultValidationMessages: { [id: string]: string };
formDefinition: {
[fieldname: string]:
{
displayName: string,
placeholder: string,
currentErrorMessage: string,
customValidationMessages: { [errorKey: string]: string }
defaultValidators: ValidatorFn,
defaultValue: any
}
};
Then I load up those objects with the default validators and field information. and build the ControlGroup from the formDefinition object.
this.defaultValidationMessages = {
'required': '{displayName} is required',
'minlength': '{displayName} must be at least {minlength} characters',
'maxlength': '{displayName} cannot exceed {maxlength} characters',
'pattern': '{displayName} is not valid'
}
this.formDefinition = {
'name': {
displayName: 'Name',
placeholder: '',
currentErrorMessage: '',
customValidationMessages: {},
defaultValidators: Validators.compose(
[
Validators.required,
Validators.minLength(3),
Validators.maxLength(50)
]),
defaultValue: this.person.name
},
'isEmployee': {
displayName: 'Is Employee',
placeholder: '',
currentErrorMessage: '',
customValidationMessages: {},
defaultValidators: Validators.compose([]),
defaultValue: this.person.isEmployee
},
'employeeId': {
displayName: 'Employee Id',
placeholder: '',
currentErrorMessage: '',
customValidationMessages: { 'pattern': '{displayName} must be 5 numerical digits' },
defaultValidators: Validators.compose(
[
Validators.pattern((/\d{5}/).source)
]),
defaultValue: this.person.employeeId
}
}
this.personForm = this.formBuilder.group({});
for (var v in this.formDefinition) {
this.personForm.addControl(v, new Control(this.formDefinition[v].defaultValue, this.formDefinition[v].defaultValidators));
}
this.personForm.valueChanges
.map(value => {
return value;
})
.subscribe(data => this.onValueChanged(data));
Using a technique that I learned from Deborah Kurata's ng-conf 2016 session I bind a method to the ControlGroups valueChanges event.
By defining sets of default validators on each control it allows the control to dynamically append new validators to it based on future action. And then clearing back to the default validators later.
Issue I still have.
I was having an issue getting my typescript intellisense to import the ValidatorFn type. I found it here but I don't think I'm suppose to access it like this:
import { ValidatorFn } from '../../../node_modules/#angular/common/src/forms/directives/validators'
I also had to reset the form by setting some internal members. Is there a better way to reset the form? see below:
(<any> this.programForm.controls[v])._touched = false;
(<any> this.programForm.controls[v])._dirty = false;
(<any> this.programForm.controls[v])._pristine = true;
Please look at my plunk and let me know if there is a better way to handle model driven dynamic form validation?
My import string looks like this and it isn't marked as an error.
import { ValidatorFn } from '#angular/common/src/forms/directives/validators';
And some info about the reset form issue. There is not proper reset feature available yet, but a workaround exists. I've found it in docs.
You need a component field
active: true;
and you need to check it in your form tag:
<form *ngIf="active">
After that you should change your personFormSubmit() method to:
personFormSubmit() {
this.person = new Person();
this.active = false;
setTimeout(()=> {
this.active=true;
this.changeDetectorRef.detectChanges();
alert("Form submitted and reset.");
}, 0);
}
I tried this solution with you plnkr example and seems that it works.

Resources