Define Knockout validation rule that takes an observable parameter using typescript - validation

I have defined a validation rule like this
ko.validation.rules["studentValidation"] = {
validator: (val: any, params: any) => {
return (this.IsInRequiredRangeForStudent(params.DateOfBirth) && val === false);
}
}
IsInRequiredRangeForStudent = (dateOfBirth: any) () => {
//my implementation
}
Here is my ViewModel class, where i consume and apply this rule on an observable
this.isStudent = ko.observable<boolean>(isStudent).extend({
studentValidation: {
message: "Invalid student option!",
params: {
DateOfBirth: this.dateOfBirth()
}
}
});
In my validation rule implementation, I always get params.DateOfBirth as null. What I am doing wrong here?

params.DateOfBirth can be null for several reasons. But firstly I would check one scenario. There is a chance that when you extending isStudent observable, you define validation params assigning value of dateOfBirth observable. But the value is evaluated at the moment of assigning, I don't see the rest of your code but it's highly possible that dateOfBirth observable is null at the moment of assigning to params. So every further check of params.DateOfBirth may return NULL value.
Please try following:
this.isStudent = ko.observable<boolean>(isStudent).extend({
studentValidation: {
message: "Invalid student option!",
params: {
DateOfBirth: this.dateOfBirth
}
}
});
and this:
ko.validation.rules["studentValidation"] = {
validator: (val: any, params: any) => {
return (this.IsInRequiredRangeForStudent(params.DateOfBirth()) && val === false);
}
}
What it changes? It defines params.DateOfBirth as function (not a value), so you can evaluate its value on every validation call.

Related

Angular how to combine local function return value with runtime call back http request

I have local function to check some validation which returns true/false. I also have runtime callback function which is an async function ie. http call.
Note: This checkPermission function is happening inside a for loop.
I want to check if any othese two function call is true. Can anyone help me how to achieve this?
private checkPermissions(
moduleId: number,
permissions: number[],
callback?: () => Observable<boolean>
): boolean {
if(callback) {
console.log('callback function defined');
}
//following is the local function. how to make callback() here?
return this.userSecurityService.userHasLicenseAndPermission(
moduleId,
permissions
);
}
My complete code is:
Component:
options: NavOption[] = [];
this.options = this.sideNavService.loadMenus();
Sidenav service:
loadMenus(): NavOption[] {
return this.getMenus();
}
private getMenus(): NavOption[] {
const filteredMenuItems: NavOption[] = [];
let menus = [{
id: 'recorded-events',
label: 'Recorded events',
icon: 'far fa-calendar-check fa-2x',
url: `/incident/${this.organisationId}/list`,
permissions: [
EventReportingPermissions.View,
EventReportingPermissions.ViewOwnEvents,
EventReportingPermissions.ViewEmployeesEvents
],
additionalPermissionCheck: () =>
this.eventAccessGroupService.hasEventAccessGroupException()//this is the service to make http call
},
{
id: 'new-events',
label: 'Report new event',
icon: 'far fa-calendar-plus fa-2x',
url: `/incident/${this.organisationId}/create`,
permissions: [EventReportingPermissions.Report]
}]
for(let item of menus) {
let canAccess = this.checkPermissions(
topLevelItem.module,
subItem.permissions
);
filteredMenuItems.push(item);
}
return filteredMenuItems;
}
//local function
private checkPermissions(moduleId: number, permissions: number[]): boolean {
//following returns value from local function and no http call
return this.userSecurityService.userHasLicenseAndPermission(
moduleId,
permissions
);
}
//additionalPermissionCheck?: () => Observable<boolean>;
I am not sure I am understanding correctly but is your callback the function that performs the permission checking?
If so you can use a map pipe:
// Beware this returns Observable<boolean> and not boolean
const safeCallbackResult = callback ? callback() : of(true) // default to returning true as we'd like to check for the second condition
return callback().pipe(
map(canDoAction => canDoAction ? this.userSecurityService.userHasLicenseAndPermission(...) : false)
)
If you'd like to return a boolean, you can't. Because the moment you need to await for the callback's observable emission that is an operation that can take some time. Even though you could make the function async
private async checkPermissions(
moduleId: number,
permissions: number[],
callback?: () => Observable<boolean>
): Promise<boolean> {
// callback().toPromise() if using RxJS 6
// firstValueFrom(callback()) if using RxJS 7
if(callback && ! (await callback().toPromise())) return false
return this.userSecurityService.userHasLicenseAndPermission(...)
}
Something like this:
sub = myHttpGetCall$().subscribe(value => {
if (value && localValue) {
// do whatever when both are true
}
}
Where localValue is the return value from your local function, which I assume is not an async operation.
Use an RxJs iif https://www.learnrxjs.io/learn-rxjs/operators/conditional/iif
booleanObservable$ = iif(() => yourLocalCondition, yourHttpRequest$, of(false));
If your localCondition is true it will make the http request otherwise there is no point so it just retuns an observable that emits false.

custom async validation not working when returning a promise

I'm calling the web api to check if an urlalias is available, for this task I'm using a httpservice in my async validator.
The issue is that when the validator is called, all the correct code path is performed (all the console.log() run and behave as expected).
Whether the promise from the validation returns/resolves to null or an { 'isUrlAliasActivityMainAvailable': true }, the controller always shows an error object as following, thus keeping the form state as invalid, why (bloody hell!)?
I'm using: angular:2.1.0 and rxjs:5.0.0-beta.12
This is my formbuilder:
this.formBuilder.group({
//...
"urlAliasActivityMain":[null,[ ValidatorsZr.isUrlAliasActivityMainAvailableAsyncValidator(this.httpActivityService)]],
});
This is my validator:
public static isUrlAliasActivityMainAvailableAsyncValidator(httpActivityService: HttpActivityService) {
return function (control: FormControl): Promise<any> | Observable<any> {
const promise = new Promise<any>(
(resolve, reject) => {
httpActivityService.isUrlAliasActivityMainAvailable(control.value)
.subscribe(
(data: any) => {
console.log("isUrlAliasActivityMainAvailableAsyncValidator");
console.log(data);
if (data == false) {
console.log("data == false");
resolve({ 'isUrlAliasActivityMainAvailable': true });
}
else {
console.log("data == true");
resolve(null);
}
},
)
});
return promise;
}
}
Your async validator is listed in the synchronous validators location in the array and is being incorrectly evaluated.
[objectValue, synchronous validators, asynchronous validators]
control(formState: Object, validator?: ValidatorFn|ValidatorFn[],
asyncValidator?: AsyncValidatorFn|AsyncValidatorFn[]) : FormControl
Construct a new FormControl with the given formState,validator, and
asyncValidator.
formState can either be a standalone value for the form control or an
object that contains both a value and a disabled status.
To correct it, move your validator to the appropriate array location:
this.formBuilder.group({
//...
"urlAliasActivityMain":[null, null, ValidatorsZr.isUrlAliasActivityMainAvailableAsyncValidator(this.httpActivityService)],
});

Angular2: Conditional required validation

I am trying to create a conditional required validation on a specific field.I try doing this by return Validators.required back in my function, but this doesn't seem to work. How do I go about doing this? Here's my code:
private _ansat: AbstractControl = new FormControl('', Validators.required);
private _helbred: AbstractControl = new FormControl('', Validators.compose([this.useValidateIfRadio(this._ansat, 0, Validators.required)]) );
constructor(private _fb: FormBuilder) {
this.myForm = this._fb.group({
ansat: this._ansat,
helbred: this._helbred
});
}
useValidateIfRadio (c: AbstractControl, n: number, v) {
return function (control) {
return new Promise(resolve => {
// this.msg = ansatControl.value;
console.log(v);
if (c.value === n) {
resolve(v);
}
else {
resolve(null);
}
});
};
};
Any help is greatly appreciated.
I had a similar problem but couldn't find a answer. Since nobody has answered this yet I'll provide an example of how I solved my problem, and how you can solve your issue using the same solution.
Example: (Phone number is required only if email is not set)
export class UserComponent implements OnInit {
userForm: FormGroup;
constructor(private fb: FormBuilder) {}
ngOnInit() {
//Create my userForm and and add initial validators
this.userForm = this.fb.group({
username: [null, [Validators.required]],
name: [null, [Validators.required]],
email: [],
phoneNumber: [null, [Validators.required, Validators.minLength(4)],
});
//Listen to email value and update validators of phoneNumber accordingly
this.userForm.get('email').valueChanges.subscribe(data => this.onEmailValueChanged(data));
}
onEmailValueChanged(value: any){
let phoneNumberControl = this.userForm.get('phoneNumber');
// Using setValidators to add and remove validators. No better support for adding and removing validators to controller atm.
// See issue: https://github.com/angular/angular/issues/10567
if(!value){
phoneNumberControl.setValidators([Validators.required, Validators.minLength(4)]);
}else {
phoneNumberControl.setValidators([Validators.minLength(4)]);
}
phoneNumberControl.updateValueAndValidity(); //Need to call this to trigger a update
}
}
So in your case you should add a changeListener to "_ansat" equal to my email listener, and then add required to "_helbred" accordingly.
Just add validator for the field:
if(some_logic) {
this.your_form.get('field_name').setValidators([Validators.required]);
}
These answers got me most of the way there, but I found out a pretty big gotcha… in some cases, setValidators only adds to the existing array of validators and does not work well to clear them. In some cases, like when ngOnInit loads twice in a row, the conditions could be first negative and then positive for a passed-in value you're depending on. In such a case, you will set it to required, then later attempt to clear it, but the UI will still behave like it expects it. To fix this, consider the following...
const myControl = this.your_form.get('field_name');
if(some_logic) {
myControl.clearAsyncValidators();
myControl.clearValidators();
myControl.updateValueAndValidity({onlySelf:true});
} else {
myControl.setValidators([Validators.required, Validators.other…]);
}

Mongoose conditional required validation

I'm using mongoose and trying to set a custom validation that tells the property shall be required (ie. not empty) if another property value is set to something. I'm using the code below:
thing: {
type: String,
validate: [
function validator(val) {
return this.type === 'other' && val === '';
}, '{PATH} is required'
]}
If I save a model with {"type":"other", "thing":""} it fails correctly.
If I save a model with {"type":"other", "thing": undefined} or {"type":"other", "thing": null} or {"type":"other"} the validate function is never executed, and "invalid" data is written to the DB.
As of mongoose 3.9.1, you can pass a function to the required parameter in the schema definition. That resolves this problem.
See also the conversation at mongoose: https://github.com/Automattic/mongoose/issues/941
For whatever reason, the Mongoose designers decided that custom validations should not be considered if the value for a field is null, making conditional required validations inconvenient. The easiest way I found to get around this was to use a highly unique default value that I consider to be "like null".
var LIKE_NULL = '13d2aeca-54e8-4d37-9127-6459331ed76d';
var conditionalRequire = {
validator: function (value) {
return this.type === 'other' && val === LIKE_NULL;
},
msg: 'Some message',
};
var Model = mongoose.Schema({
type: { type: String },
someField: { type: String, default: LIKE_NULL, validate: conditionalRequire },
});
// Under no condition should the "like null" value actually get persisted
Model.pre("save", function (next) {
if (this.someField == LIKE_NULL) this.someField = null;
next()
});
A complete hack, but it has worked for me so far.
Try adding this validation to the type attribute, then adjust your validation accordingly. E.g.:
function validator(val) {
val === 'other' && this.thing === '';
}
thing: {
type: String,
required: function()[{
return this.type === 'other';
}, 'YOUR CUSTOM ERROR MSG HERE']
}

Breeze client-side custom validation with server-side data

I created a custom validator that check if a username is used on a DB.
The whole process of validation works. What is not working is result.
function createExistingUsernameValidator() {
var name = 'existingUsernameValidator';
var ctx = { messageTemplate: 'Questa partita I.V.A. o codice fiscale sono già stati inseriti.', displayName: "Partita IVA o Codice Fiscale" };
var val = new Validator(name, valFunction, ctx);
return val;
function valFunction(value, context) {
var result = ko.observable(true);
require('services/datacontext').getIsUserByUsername(value, result)
.then(function () {
debugger;
return !result();
});
}
}
The promise works: I know because it hits the debbugger line and the retunrnig value is correct.
But the validator always evaluate as false because I'm not returning anything when the validator is called. In other words: it won't wait for the promise.
Is it my bad javascript or something else?
Any help is welcome.
Thank you!
Edited after answer
I've come to a solution that involves Knockout Validation (very useful script).
function createIsExistingUserKoValidation() {
ko.validation.rules['existingUsername'] = {
async: true,
validator: function (val, params, callback) {
if (val) {
var result = ko.observable();
require('services/datacontext').getIsUserByUsername(val, result)
.then(function () {
callback(!result());
});
}
},
message: ' Existing username.'
};
ko.validation.registerExtenders();
}
In the entity creation:
var createDitta = function () {
var ditta = manager.createEntity(entityNames.ditta,
{
id: newGuid(),
legaleRappresentante: createPersona(),
isAttiva: true
});
ditta.pivaCodFiscale.extend({ existingUsername: { message: ' Existing username.', params: true } });
ditta.pivaCodFiscale.isValidating(false);
return ditta;
};
ditta.pivaCodFiscale.isValidating(false); this is needed because isValidating is initialized with true.
The problem is that your valFunction as written will ALWAYS return 'undefined'. ( which is 'falsy'.
The 'return !result()' expression is NOT the return value of 'valFunction', it is simply the result of an anonymous function that executes AFTER valFunction has already returned. This is the async nature of promises.
What you are trying is to write an 'asynchronous' validation which is NOT supported out of the box with Breeze, but the idea IS a good one.
I think that you might be able to accomplish what you want by having your async callback actually 'set' a value on the entity and have that set operation itself trigger a seperate 'synchronous' validation.
This IS a good idea for Breeze to support more naturally so please feel free to add a feature request to the Breeze User Voice for something like "asynchonous validation". We use this to gauge the communities interest in the various proposed features/extensions to Breeze.

Resources