custom async validation not working when returning a promise - validation

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)],
});

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.

error Policy in Apollo Client React does'nt work

I have aproblem when test Apollo.When I try query with apollo and graphql, i want response return error and partical data, so I set property errorPolicy:'all'. But its not work. I don't no why? Help please!
Here my code:
query { animal {
name
age }, school {
name
numberfd } } `
const { loading,data,error} = useQuery(GET_DASHBOARD_DATA, {
errorPolicy:'all',
onCompleted: (res) => {console.log("complete",res)},
onError : (res,data) => {console.log("ERRRR",res,data)},
})
and i want to receive:
{
error:[...], data:[animal:[...]] }
but its only response error.Here is Apollo's doc: https://www.apollographql.com/docs/react/data/error-handling/
onError type is onError?: (error: ApolloError) => void;. You don't have data inside onError callback.
After useQuery you can add:
console.log('data', data)
console.log('error', error)
I faced the same issue with errorPolicy: 'all', I only received the partial result inside onCompleted callback of useQuery, but no errors.
I created an ErrorLink like this:
private createErrorLink = () => {
return new ApolloLink((operation, forward) => {
return forward(operation).map((response) => {
// filter out errors you don't want to display
const errors = filterSomeErrors(response.errors);
if (errors && response?.data) {
response.data.errors = errors;
}
return response;
});
});
};
Now inside my onCompleted callback I get my data as well as errors. You will have to tweak your types a bit, because seems there is no errors field on response.data by default.
Mind that if you use onError from Apollo and return something from the link, it will retry your request containing errors!

How to test emit event on ngSubmit within component subscribe method

On form submit getUsers() is called and if success message is received, the received data is emitted to the parent component.
child html
<form (ngSubmit)="getUsers()">
</form>
child component
getUsers(): void {
this.userService.getUsers().subscribe(users => {
if(users.status=="Success"{
this.listOfUsers = users;
this.user.emit(this.listOfUsers);
this.nextTab.emit(true);
}
});
}
i have written the test case to check emit event as follows
it('should emit data on success', () => {
spyOn(component.user,'emit');
component.getUsers();
expect(component.user.emit).toHaveBeenCalled(); // fails as never called what i am doing wrong
});
You have to make sure that userService.getUsers returns an observable.
import { of } from 'rxjs';
....
it('should emit data on success', () => {
// mock userService.getUsers to return { status: 'Success' } to go inside of the if block
spyOn(userService, 'getUsers').and.returnValue(of({ status: 'Success' }));
spyOn(component.user,'emit');
component.getUsers();
expect(component.user.emit).toHaveBeenCalled(); // fails as never called what i am doing wrong
});
Edit
To actually test ngSubmit, I would use triggerEventHandler. You can do some research on it.
const form = fixture.debugElement.query(By.css('form'));
// The 2nd argument is what you would like the $event value to be.
// In our case, null is fine.
form.triggerEventHandler('ngSubmit', null);
Doing the above will call getUsers.

Angular 2 async validator always invalid

I have the following form control with the simplest async validator I could write:
this.aliasCtrl = this._fb.control('', [(control: AbstractControl) => {
return new Promise(resolve => {
console.log(this.aliasCtrl);
resolve(null);
});
}]);
My form definition is:
this.contactForm = this._fb.group({
alias: this.aliasCtrl
});
My form control is always invalid.
Here's a plunker: http://plnkr.co/edit/vyr48ke7fWEUwrXy43tn?p=preview
I'm sure I've miss something but I cannot find what.
Thanks for help.
Change the code to:
this.aliasCtrl = this._fb.control('', null, (control: AbstractControl) => {
return new Promise(resolve => {
console.log(this.aliasCtrl);
resolve(null);
});
});
Pass null or empty array for validators (second parameter) and the async validator function as the third parameter.
Tested... Works!

Define Knockout validation rule that takes an observable parameter using typescript

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.

Resources