Angular2: how to add validator and exclude required - forms

I created a custom validator to check email addresses filled by users. Anyway the email field is not required, but if I add my validator it includes required as well.
Here is my form in the constructor of my component class:
this.myForm = fb.group({
'name': [''],
'surname': [''],
'username': ['', Validators.required],
'email': ['', validateEmail]
});
Email field is not required, but if users doesn't fill it, the form doesn't get validated. At the same time if users fill it, I want that the email validation is applied.
Here is my email validator:
export function validateEmail(c: FormControl) {
var EMAIL_REGEXP = /^(|(([A-Za-z0-9]+_+)|([A-Za-z0-9]+\-+)|([A-Za-z0-9]+\.+)|([A-Za-z0-9]+\++))*[A-Za-z0-9]+#((\w+\-+)|(\w+\.))*\w{1,63}\.[a-zA-Z]{2,6})*$/i;
return EMAIL_REGEXP.test(c.value) ? null : {
validateEmail: {
valid: false
}
};
}
I could edit my custom validator to accept empty strings, but I think this is not the right way to solve my problem.
Do you have a better idea?

I create a custom validator that take a parameter. So it can be use to check valid email and even to check required valid email.
It is still reusable and it can be even not required, according if you call it with true or false.
Here is the code:
import {FormControl} from "#angular/forms";
export const validateEmail = (canBeEmpty:boolean) => {
return (control:FormControl) => {
var EMAIL_REGEXP = /^(|(([A-Za-z0-9]+_+)|([A-Za-z0-9]+\-+)|([A-Za-z0-9]+\.+)|([A-Za-z0-9]+\++))*[A-Za-z0-9]+#((\w+\-+)|(\w+\.))*\w{1,63}\.[a-zA-Z]{2,6})*$/i;
if (canBeEmpty) {
EMAIL_REGEXP = /^$|^(|(([A-Za-z0-9]+_+)|([A-Za-z0-9]+\-+)|([A-Za-z0-9]+\.+)|([A-Za-z0-9]+\++))*[A-Za-z0-9]+#((\w+\-+)|(\w+\.))*\w{1,63}\.[a-zA-Z]{2,6})*$/i;
}
return EMAIL_REGEXP.test(control.value) ? null : {
validateEmail: {
valid: false
}
};
};
}

Allow an empty string for control value, as follows:
static emailValidator(control: any) {
// RFC 2822 compliant regex
var EMAIL_REGXP = /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*#(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/;
if ( control.value === '' || control.value.match(EMAIL_REGXP) ) {
return null;
} else {
return { 'invalidEmailAddress': true };
}
};
I don't think this could be seen as a side-effect as the purpose of the function is to find invalid strings that should conform to an email format.

Related

mongodb/mongoose: Save unique value if data is not null in from nestjs

I am trying to save data in MongoDB. I want to store unique data when data is not null. However, I want to allow multiple null values in the unique identifier.
My sample schema:
#Schema()
export class Contact extends Document {
#Prop({ unique: true, sparse: true, require: true })
email: string;
#Prop({ default: '+1' })
countryCode: string;
#Prop({ unique: true, sparse: true })
mobile: string;
}
In this case, a mobile number is not required. User can add their contact information with or without providing a mobile number. If the user sends their mobile number that should be unique. So, I need to allow multiple null values in the mobile field. However, that field should be unique when the user provides any mobile number.
Empty entries seem to get the value null so every entry without mobile crashes with the unique identifier.
Is there any way to solve this problem either from the database layer or the application layer?
I am using NestJS for developing my API.
A unique index still does not allow multiple docs with a field of null. You need to transform your data payload by dropping the null field before you save your docs in MongoDB. A transform pipe will help you to handle this issue. Here is a transform pipe that you can use for this purpose:
#Injectable()
export class NullValidationPipe implements PipeTransform {
private isObj(obj: any): boolean {
return typeof obj === 'object' && obj !== null;
}
private dropNull(values) {
Object.keys(values).forEach((key) => {
if (!(key === 'password' || key === '_id')) {
if (this.isObj(values[key])) {
values[key] = this.dropNull(values[key]);
} else if (Array.isArray(values[key]) && values[key].length > 0) {
values[key] = values[key].map((value) => {
if (this.isObj(value)) {
value = this.dropNull(value);
}
return value;
});
} else {
if (values[key] === null || values[key] === undefined) {
delete values[key];
}
}
}
});
return values;
}
transform(values: any, metadata: ArgumentMetadata) {
const { type } = metadata;
if (type === 'param' || type === 'custom') return values;
else if (this.isObj(values) && type === 'body') {
return this.dropNull(values);
}
throw new BadRequestException('Validation failed');
}
}
Use this pipe in the controller and this pipe will drop all incoming null fields which will come with the request payload.
You can also check nest pipe transform docs: https://docs.nestjs.com/techniques/validation

Angular form validation: compare two fields

In an Angular 4 application, how can I validate two fields of a form doing a comparison?
For example, let's suppose that my form has a startDate and an endDate date fields and I want to make sure that the endDate must be bigger than the startDate.
When you want to implement validations containing one or more sibling (form)controls, you have to define the validator function on a level up/above that of the sibling controls. For ex:
ngOnInit() {
this.form = this.formbuilder.group({
'startDate': ['', [<control-specific - validations >]],
'endDate': ['', [<control-specific - validations >]]
}, { validator: checkIfEndDateAfterStartDate });
}
Then outside the component class's definition (in the same file), define the function checkIfEndDateAfterStartDate as well.
export function checkIfEndDateAfterStartDate (c: AbstractControl) {
//safety check
if (!c.get('startDate').value || !c.get('endDate').value) { return null }
// carry out the actual date checks here for is-endDate-after-startDate
// if valid, return null,
// if invalid, return an error object (any arbitrary name), like, return { invalidEndDate: true }
// make sure it always returns a 'null' for valid or non-relevant cases, and a 'non-null' object for when an error should be raised on the formGroup
}
This validation will make the FormGroup invalid by adding the error-flag (here invalidEndDate) to true to the errors object for that FormGroup. If you want to have specific errors to be set on any of the sibling controls instead, then you can manually set the error flags on that formControl by using something like, c.get('endDate').setErrors({ invalidEndDate: true }). If you do this, then make sure you clear them for a valid case by setting the errors to null like this, c.get('endDate').setErrors(null).
A live demo of a similar validation can be seen here.
try this
export class validationComponent implements OnInit {
private testForm:FormGroup;
constructor(private fb: FormBuilder) {
}
ngOnInit() {
this.testForm = this.fb.group({
'startDate': ['', [Validators.required]],
'endDate': ['', [Validators.required]]
});
this.subscribeDateChanges();
}
subscribeDateChanges() {
const startDateChanges = (<any>this.testForm).controls.startDate.valueChanges;
const endDateChanges = (<any>this.testForm).controls.endDate.valueChanges;
startDateChanges.subscribe(start => {
this.testForm.controls['endDate'].
setValidators(
[Validators.required,
CustomValidators.minDate(this.toYYYYMMDD(start))]);
this.validateDates();
});
endDateChanges.subscribe(end => {
this.validateDates();
});
}
dateError: boolean = false;
validateDates(): void{
let startDate = this.testForm.controls['startDate'].value;
let endDate = this.testForm.controls['endDate'].value;
if(endDate && startDate){
this.dateError = endDate <= startDate;
}
}
toYYYYMMDD(d:Date): string {
d = new Date(d)
var yyyy = d.getFullYear().toString();
var mm = (d.getMonth() + 101).toString().slice(-2);
var dd = (d.getDate() + 100).toString().slice(-2);
return yyyy + '-' + mm + '-' + dd;
}
based on the dateError boolean value you show error msg

Custom Validator Angular 2

I've written a web api function that takes a username from the textfield and checks if the username is already taken. To know if the username is available or not, my server returns Y if it is available and N if its not.
To validate the username, I'm using a ValidatorFn in Angular2 so validate the input. However, my validator function is not working.
Here is the validator function:
interface Validator<T extends FormControl> {
(c: T): { [error: string]: any };
}
function validateUsername(c: string) : ValidatorFn {
return (this.isAvailable(c)=='Y') ? null : {
validateUsername: {
valid: false
}
};
}
Here is the isAvailable function:
private isAvailable(username: string) {
let usernameAvailable;
let url = 'URL/api/auth/checkuser/' + username;
let headers = new Headers();
headers.append('User', sessionStorage.getItem('username'));
headers.append('Token', sessionStorage.getItem('token'));
headers.append('AccessTime', sessionStorage.getItem('AccessTime'));
let options = new RequestOptions({ headers: headers });
this.http.get(url, options)
.subscribe((res: Response) => usernameAvailable);
return usernameAvailable; //returns Y or N
}
Form Builder:
complexForm: FormGroup;
constructor(private http: Http, fb: FormBuilder) {
this.complexForm = fb.group({
'username': [null, Validators.compose([Validators.required, Validators.minLength(5), Validators.maxLength(10), validateUsername(this.complexForm.controls['username'].value)])],
})
}
validateUsername(this.complexForm.controls['username'].value) is failing and I'm getting this error:
[ts] Type '{ validateUsername: { valid: boolean; }; }' is not assignable to type 'ValidatorFn'. Object literal may only specify known properties, and 'validateUsername' does not exist in type 'ValidatorFn'. (property) validateUsername: {
valid: boolean;
}
You not adding your validator function correctly. You don't need to call your function when you register it:
this.complexForm = fb.group({
'username': [null, Validators.compose(
[
Validators.required,
Validators.minLength(5),
Validators.maxLength(10),
validateUsername <----- don't call it here
]
)],
})
You can see that some functions are called:
Validators.minLength(5),
But that is factory function call and not a validator function call. During initialization they return ValidatorFn:
/**
* Validator that requires controls to have a value of a minimum length.
*/
static minLength(minLength: number): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
...
}
See more in the official docs.
Also, it seems that your validator is async, so you have to pass it in the async array. And I don't think you need Validators.compose. The correct configuration should therefore be like this:
this.complexForm = fb.group({
'username': [null, [
Validators.required,
Validators.minLength(5),
Validators.maxLength(10),
], [validateUsername]]
})
Regarding the error:
Type '{ valid: boolean; }' is not assignable to type ValidatorFn.
You need to use the correct return type ValidationErrors instead of ValidatorFn:
function validateUsername(c: string) : ValidationErrors {
return (this.isAvailable(c)=='Y') ? null : {
validateUsername: {
valid: false
}
};
}

Angular2 interdependent form field validation

I have two form fields, where if the first field is filled in, the second field is mandatory. If I try to do this in Angular2, using a custom validator, the validator is only fired on initialization and when the specific field is changed.
Case:
- User fills in field 1
- Field 2 should become required, but isn't till the user actually changes field 2 (firing the custom validation).
private createForm():void {
this.testForm = this._formBuilder.group({
'field1': [],
'field2': ['', this.validateRequired()]
});
}
private validateRequired(){
console.log("something", this);
let component = this;
return (control: Control): { [s: string]: boolean } => {
return component.testModel.field1 && !control.value {"required":true} : null;
}
}
See this plunkr: http://plnkr.co/edit/PEY2QIegkqo8BW1UkQS5?p=preview
Edit:
For now I subscribed to field1's valueChange observable and when changed execute a manual check on field2, like:
this.testForm.controls['field1'].valueChanges.subscribe(
value => {
component.testForm.controls['field2].updateValueAndValidity();
}
)
But I feel like there must be a better way to do this.
You could use a global validator for the group like this:
private createForm():void {
this.testForm = this._formBuilder.group({
'field1': [],
'field2': ['', this.validateRequired()]
}, {
validator: this.someGlobalValidator // <-----
});
}
someGlobalValidator(group: ControlGroup) { // <-----
var valid = false;
for (name in group.controls) {
var val = group.controls[name].value
(...)
}
if (valid) {
return null;
}
return {
someValidationError: true
};
}
I want to expand on Thierry's answer a bit in order to address Arne's comment. In order to handle the validation of multiple fields and possibly multiple validations in your formgroup level validator the solution is to return a function from your validator that then returns an object that indicates the error type. Here is a example of a field matching validator that I added some extra errors to in order to illustrate the point. Note that it returns an object with possibly several properties where each object property is any string and the value is a boolean.
export function FieldMatchingValidator(field1: string, field2 :string) {
return (cg: FormGroup): { [s: string]: boolean } => {
let retVal = null;
let f1 = cg.controls[field1];
let f2 = cg.controls[field2];
retVal = f1.value === f2.value ? null : { fieldMismatch: true };
if(somecondition){
retVal['someerror'] = true;
}
if(someothercondition){
retVal['someothererror'] = true;
}
return retVal;
}
}
When this validator runs, if an error condition is encountered, then the form's errors property will be populated with the returned object with one or more properties indicating different errors. Then all you have to do it put the appropriate angular property setting on the controls that have the validation errors.
<div *ngIf="myForm.hasError('fieldMismatch')">
Field Mismatch
</div>
<div *ngIf="myForm.hasError('someerror')">
Some Error
</div>
<div [class.Errors]="myForm.hasError('someothererror')">
Some Other Error
</div>

sails js model validation against database

I am writing a custom validation rule to check if the "category_id" passed to my create function is valid or not.
types: {
isValidCategoryId: function(id){
return Category.findOne({id: id}).exec(function(err, user){
if(err || !user)
return false;
else{
return true;
}
});
}
},
attributes: {
category_id : { isValidCategoryId: true, required: true, type: 'string' },
}
I understand that my custom validation function should return true, but in an asynchronous context, this may not work, like checking the value in DB.
How should I write my custom validation function to make it behave correctly?
I tried particlebanana's solution. It didn't work but at least it pointed me in the right direction.
According to the docs:
Validation rules may be defined as simple values or functions (both sync and async) that return the value to test against.
So, one easy way to do this would be:
attributes: {
category_id : {
required: true,
type: 'string',
'true': function(cb) {
Category.findOne({id: this.category_id}).exec(function(err, category){
return cb(!err && category);
});
}
}
}
As you can see, I'm just using the "true" validator here, but you could of course write your own validator to work out some more advanced logic. The key here is that your custom validators aren't async, but your validation rules can be. Yes, the terminology is very confusing.
Here's another example with a custom validator:
attributes: {
author: {
required: true,
type: 'string',
canPost: function(cb) {
Author.findOne(this.author).exec(function(err, author) {
return cb(author);
});
}
}
},
types: {
canPost: function(id, author) {
return author && author.canPost();
}
}
Hopefully that makes sense. If not, See the docs.
You can pass in a callback and return the result. It's a bit weird because it doesn't look like it follows the (err, result) standard but instead just uses (result). Give this a try:
types: {
isValidCategoryId: function(id, cb){
return Category.findOne({id: id}).exec(function(err, user){
if(err || !user)
return cb(false);
else{
return cb(true);
}
});
}
},