How to write global custom validation in vue3? - javascript

Presently working in vue3 and using the validation as below -
const validationRule = object({
name: string()
.required("This field is required")
.max(128, maxMessage("Student Name", 128))
.test("Check-unique-field", function (Fieldvalue) {
const checkValidation = uniquenessValidate(Fieldvalue, targetUniqueNames);
if (!checkValidation ) {
return this.createError({
path: this.path,
message: uniqueMessage("Student Name")
});
} else {
return true;
}
})
In this scenario, I write a custom validation that is uniquenessValidate using the function in form. This is working absolutely fine but the problem this code need to repeatedly using for each and every form to check the uniqueness which I not want to do. I want to write this function in a global places and uses it whenever I need to use the function to call by. Now the problem is when I written the code globally it create to issues to pass the this.createError and this.path . Can we get an idea to write this function globally?
Writing the global function I tried out as below -
import {
uniqueMessage
} from "#/core/libs/test-veevalidate-validations";
import { uniquenessValidate} from "#/core/libs/ test-veevalidate-validator";
import { computed, ComputedRef, defineComponent, ref, toRef } from "vue";
export function fn_uniqueValidator( fieldValue:string, targetValue: ComputedRef<(string | null | undefined)[]>){
const checkValidation = uniquenessValidate(fieldValue, targetValue );
if (!checkValidation ) {
return this.createError({
path:this.path,
message: uniqueMessage("Student Name")
});
} else {
return true;
}
}

Related

Circular reference using dependency inversion issue

I have a circular reference issue using this pattern approach. TypeError: Class extends value undefined is not a constructor or null .
The strange thing is, if I move the field.type.ts in src/constants.ts, it doesn't throw an error and it works as expected, but crashes on the Unit Tests. If it leave the fied.type.ts contents in it's own file, it crashes.
Maybe I am not using/understanding this dependency inversion pattern the right way. I could probably fixed by passing the FieldTypeToClassMapping as a parameter in Field.create(options: FieldOptions, fieldTypeMapping: FieldTypeToClassMapping), but I want to understand why this is happening.
import { StringField } from './string.field.model';
import { IntegerField } from './integer.field.model';
...
export const FieldTypeToClassMapping = {
//Constructor of eg. StringField class so I can use `new FieldTypeToClassMapping[options.type](options)`;
[FieldTypeEnum.STRING]: StringField,
[FieldTypeEnum.INTEGER]: IntegerField,
};
//field/field.ts
import { FieldOptions } from 'src/interfaces/field.options.interface';
import { FieldTypeToClassMapping } from 'src/model/template/field.type.to.mapping.ts'
export abstract class Field {
value: any;
type: string;
errors: string[] = [];
public constructor(options: FieldOptions) {
this.value = options.value;
this.type = options.type;
}
public static create(options: FieldOptions): any {
try {
return new FieldTypeToClassMapping[options.type](options);
} catch (e) {
throw new Error(`Invalid field type: ${options.type}`);
}
}
}
//field/integer.field.ts
import { FieldOptions } from 'src/interfaces/field.options.interface';
import { Field } from './field.model';
export class IntegerField extends Field {
constructor(options: FieldOptions) {
super(options);
}
protected validateValueDataType() {
this.validateDataType(this.value, "value");
}
protected validateDefaultDataType() {
this.validateDataType(this.defaultValue, "defaultValue");
}
}
//field/service.ts
payload const postFields = [
{
type: "string", //FieldTypeEnum.STRING,
value: 'a name'
},
];
const postFields = [
{
type: "string",
value: "John",
},
{
type: "integer",
value: 32,
},
];
const fieldsArray = [];
postFields.forEach((item) => {
const field: Field = Field.create(item);
fieldsArray.addField(field);
});
return fieldsArray;
The create(options: FieldOptions) function is defined inside the class Field, but then it tries to instantiate an instance of a class that extends Field.
I think that is where the problem arises. I don't know the entire contents of your files, but I imagine that at the top of any field.type.ts file you import Field. However since Field can instantiate any concrete implementation of itself it would need to know about them so you would need to import everything that extends Field inside Field.
I don't know/understand the dependency inversion pattern well enough to relate it to your question. But given the provided information, perhaps a Factory Pattern is what you need?
You could move the the function create(options: FieldOptions) to a FieldFactory class. Your create function is practically a factory function already.

Custom vue validator to have same behavior as `required` validator

Greetings I created custom validator with custom msg for my webpage to check if on register form entered email in unique.
import userService from '#/services/user-service';
import { helpers } from '#vuelidate/validators';
const { withAsync } = helpers;
const uniqueEmail = helpers.withMessage(
() => 'Email is already registered',
withAsync(async (value: string) => {
return await userService.isEmailTaken(value);
})
);
export { uniqueEmail };
So how it is added to component. The problem is I did not found solution to add same option as 'required' have to turn off validator if I needed.
validations() {
return {
form: {
email: {
required: requiredIf(() => this.isSubsidiary), // I want validator pass function tu turn it off If i want
email,
uniqueEmail: requiredIf(() => false) // this thing does not work to turn the validation off if needed
}
}
};
}
Any ideas how to make uniqueEmail validator work same as required validator. So I can pass to it boolean to turn it on/off ?

How to unit test form validation in Aurelia

I am trying to implement some unit tests on a form to see if the validation rules are working as expected.
from this page : https://github.com/aurelia/testing/issues/63
I found this implementation : https://github.com/aurelia/validation/blob/master/test/validate-binding-behavior.ts
and I tried to implement it in my project
login.spec.js
import {bootstrap} from 'aurelia-bootstrapper';
import {StageComponent} from 'aurelia-testing';
import {PLATFORM} from 'aurelia-pal';
import { configure, blur, change } from './shared';
import { Login } from './login';
describe('ValidateBindingBehavior', () => {
it('sets validateTrigger', (done) => {
const component = StageComponent
.withResources(PLATFORM.moduleName('features/account/login/login'))
.inView('<login></login>')
.boundTo({});
component.bootstrap(configure);
let viewModel;
const renderer = { render: jasmine.createSpy() };
component.create(bootstrap)
// grab some references.
.then(() => {
viewModel = component.viewModel;
viewModel.controller.addRenderer(renderer);
})
.then(() => expect(viewModel.controller.errors.length).toBe(0))
.then(() => blur(viewModel.firstName))
.then(() => expect(viewModel.controller.errors.length).toBe(1))
.then(() => component.dispose())
.then(done);
});
});
login.js
import { inject, NewInstance } from 'aurelia-dependency-injection';
import { ValidationController } from 'aurelia-validation';
import { User } from './login.model';
#inject(NewInstance.of(ValidationController), User)
export class Login {
constructor(controller, user) {
this.controller = controller;
this.firstName = '';
this.lastName = '';
this.userName = '';
this.showForm = true;
this.user = user;
}
};
login.model.js
import {ValidationRules} from 'aurelia-validation';
export class User {
firstName = '';
lastName = '';
userName = '';
constructor() {
ValidationRules
.ensure('firstName')
.required()
.ensure('lastName')
.required()
.minLength(10)
.ensure('userName')
.required()
.on(this);
}
}
shared.js
import {DOM, PLATFORM} from 'aurelia-pal';
export function configure(aurelia) {
return aurelia.use
.standardConfiguration()
.plugin(PLATFORM.moduleName('aurelia-validation'))
}
export function blur(element) {
element.dispatchEvent(DOM.createCustomEvent('blur', {}));
return new Promise(resolve => setTimeout(resolve));
}
export function change(element, value) {
element.value = value;
element.dispatchEvent(DOM.createCustomEvent('change', { bubbles: true }));
return new Promise(resolve => setTimeout(resolve));
}
and here is a piece of html markup :
<div>
<input ref="firstName" type="text" value.bind="user.firstName & validateOnBlur"
validation-errors.bind="firstNameErrors">
<label style="display: block;color:red" repeat.for="errorInfo of firstNameErrors">
${errorInfo.error.message}
</label>
</div>
<div>
in the spec, when I blur the element I expect to get one error, but "controller.errors" is always an empty array. and I get this for the failed message :
Error: Expected 0 to be 1.
UPDATE 1:
I tried to validate manually, so I added this in my spec :
.then(()=>
viewModel.controller.validate({object: viewModel.user, propertyName: 'firstName' })
)
and it works fine, but the blur and change functions don't trigger validation.
UPDATE 2:
I changed it like "Sayan Pal" suggested. and it works now but with a tiny problem. when I "blur" the element once it shows one error. but when I "blur" several elements ( let's say three ) it doesn't show the last error. in this case controller.errors.length would be 2.
I can blur the last element two times to get the correct length of errors. but I think there should be a better solution.
.then(() => blur(viewModel.firstName))
.then(() => blur(viewModel.userName))
.then(() => blur(viewModel.lastName))
.then(() => blur(viewModel.lastName))
I think instead of using createCustomEvent you simply need to do element.dispatchEvent(new Event("blur"));. Same goes for change event.
This has always worked for me, and hope it will help you too :)
On related note, I use a default ValidationController generator factory method that ensures the default trigger as follows.
import { validateTrigger, ValidationControllerFactory } from "aurelia-validation";
...
const validationController = validationControllerFactory.createForCurrentScope();
validationController.changeTrigger(validateTrigger.changeOrBlur);
Update after OP updated the question
It is difficult to say why it is happening, without debugging. As I don't see any imminent problem in your test code, my assumption is that it is a timing issue. The main idea is that you need to wait for the change to happen. There are several ways you can do it, all of those needs change in how you are asserting.
One way to do it is to employ a promise with a timeout that polls in a regular interval for the change. And then wait for the promise.
Or you can use TaskQueue to queue your assertion, and after the assertion call done. This looks something like below.
new TaskQueue().queueMicroTask(() => {
expect(foo).toBe(bar);
done();
});
Other alternative is to use cypress as an e2e test framework. Out of the box, Cypress waits for the change to happen until times out.
Choose what best fits your need.

How to Add Custom Validation Error Message for Custom Validator in Angular

I'm using reactive angular forms and created new custom form validator but its not showing custom messages I wanted to add custom message for custom validator.
I'm trying to ignore static message I want that message to be added in that validator itself so it can show for wherever places I use that validator.
custom validator codes :
import { FormControl } from '#angular/forms';
export function validateJson(input: FormControl): object | null {
try {
if (input.value) {
JSON.parse(input.value);
}
return null;
} catch (error) {
return { invalidFormat: true };
}
}
just change invalidFormat property's value to object with property message instead of true
import { FormControl } from '#angular/forms';
export function validateJson(input: FormControl): object | null {
try {
if (input.value) {
JSON.parse(input.value);
}
return null;
} catch (error) {
return { invalidFormat: {message: "your message here"} };
}
}
and in html if error exists display message like so
<div *ngIf="formControl.errors.invalidFormat && formControl.dirty">
{{ formControl.errors.invalidFormat.message}}
</div>

Access service from Custom Validator in Angular

I'm trying to access my service in order to make check for the validator but all i get is console full of errors I'm sure I'm just bad with syntax stuff =/
validator:
import { DataService } from './services/data.service';
import { AbstractControl, FormGroup } from '#angular/forms';
export function titleValidator(control: AbstractControl,dataService:DataService) {
console.log(dataService.moviesArray) -->> How can I access this service?
if (control && (control.value !== null || control.value !== undefined)) {
if (control.value=="test") {
return {
isError: true
};
}
}
return null;
}
component:
this.movieForm = this.fb.group({
title: ['', [Validators.required,titleValidator]],
...
});
}
If anyone has even another solution to make the custom validation in the component itself I would like any help.. thanks!
update: the errors:
AddMovieComponent_Host.ngfactory.js? [sm]:1 ERROR TypeError: Cannot read property 'moviesArray' of undefined
at titleValidator (validator.ts:8)
at forms.js:602
at Array.map (<anonymous>)
at _executeValidators (forms.js:602)
at FormControl.validator (forms.js:567)
at FormControl.push../node_modules/#angular/forms/fesm5/forms.js.AbstractControl._runValidator (forms.js:2510)
at FormControl.push../node_modules/#angular/forms/fesm5/forms.js.AbstractControl.updateValueAndValidity (forms.js:2486)
at new FormControl (forms.js:2794)
at FormBuilder.push../node_modules/#angular/forms/fesm5/forms.js.FormBuilder.control (forms.js:5435)
at FormBuilder.push../node_modules/#angular/forms/fesm5/forms.js.FormBuilder._createControl (forms.js:5473)
You have to pass the service to the validator, there is no dependency injection here as this is not an Angular directive, it is a pure function. The way to do this is to use a factory method that accepts the service and creates a validator function.
export function titleValidator(dataService:DataService): ValidatorFn {
return (control: AbstractControl) => {
console.log(dataService.moviesArray) // now you can :)
// Test for control.value only, for eg:
if (control.value && dataService.moviesArray.includes(control.value))
return null;
else
return { 'movieNotFound' : { value: control.value } };
}
}
Usage:
this.movieForm = this.fb.group({
title: ['', [
Validators.required,
titleValidator(this.dataService)
]],
...
});
There is no need to check for the presence of control as Angular only calls the validator function with a valid control. Test only the value. More info here

Categories