I noticed that type="url" gets ignored in Angular 8 when using Reactive Form. I am trying to validate the URL/URI using RFC2396 since the form gets sent to a .NET Core API.
I think .NET Core uses Uri.CheckSchemaName with RFC2396 and I wanted to do the same validation in Angular and avoid having two different RegEx to validate the URL/URI.
RFC2396
Here is my Custom Angular Validator
import { ValidatorFn, AbstractControl } from '#angular/forms';
export function urlValidator(): ValidatorFn {
return (control: AbstractControl): { [key: string]: any } | null => {
const uriRegEx = RegExp('^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?', 'g');
const result = uriRegEx.test(control.value);
if (!result) {
return { 'invalid-url': true };
}
return null;
};
}
The result is always true no matter what control.value is.
Disclaimer: Did my best looking for an answer and avoid the infamous duplicated.
Stackblitz Code
Related
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;
}
}
I want to define whether a function should contain an argument via an interface. The library I'm developing calls for many different methods to be generated, and hardcoding those methods would require too much maintenance; so I figured that types would be a good place to define such things.
Perhaps this is best explained with code. Here's a library that abstracts some rest API:
interface RequestInterface {
endpoint: string
body?: unknown
}
interface GetPosts extends RequestInterface {
endpoint: '/posts'
body: never
}
interface CreatePost extends RequestInterface {
endpoint: '/posts'
body: string
}
function Factory<R extends RequestInterface> (endpoint: R['endpoint']) {
return (body?: R['body']): void => {
console.log(`Hitting ${endpoint} with ${body}`)
}
}
const myLibrary = {
getPosts: Factory<GetPosts>('/posts'),
createPosts: Factory<CreatePost>('/posts'),
}
myLibrary.getPosts('something') // => Correctly errors
myLibrary.createPosts(999) // => Correctly errors
myLibrary.createPosts() // => I want this to error
In the above, I'm defining the endpoint and body of a particular type of request in my interfaces. Although the TypeScript compiler correctly guards me against passing the wrong argument types, it doesn't guard me against not passing a value when one is required.
I understand why TypeScript doesn't error (because the method defined in factory can be undefined according to my typings), but I figured the above code was a good way of describing what I want to achieve: a quick, declarative library of methods which satisfy a particular type.
A Possible Solution
If I'm willing to extend my interfaces from two separate interfaces (one or the other) then I can achieve something close to what I want using Construct Signatures:
interface RequestInterface {
endpoint: string
call: () => void
}
interface RequestInterfaceWithBody {
endpoint: string
call: {
(body: any): void
}
}
interface GetPosts extends RequestInterface {
endpoint: '/posts'
}
interface CreatePost extends RequestInterfaceWithBody {
endpoint: '/posts'
call: {
(body: string): void
}
}
function Factory<R extends RequestInterface|RequestInterfaceWithBody> (endpoint: R['endpoint']): R['call'] {
return (body): void => {
console.log(`Hitting ${endpoint} with ${body}`)
}
}
const myLibrary = {
getPosts: Factory<GetPosts>('/posts'),
createPosts: Factory<CreatePost>('/posts'),
}
myLibrary.getPosts() // => Correctly passes
myLibrary.getPosts('something') // => Correctly errors
myLibrary.createPosts(999) // => Correctly errors
myLibrary.createPosts() // => Correctly errors
myLibrary.createPosts('hi') // => Correctly passes
Aside from the fact that I need to pick between two "super" types before extending anything, a major problem with this is that the Construct Signature argument is not very accessible.
Although not demonstrated in the example, the types I create are also used elsewhere in my codebase, and the body is accessible (i.e GetPosts['body']). With the above, it is not easy to access and I'll probably need to create a separate re-usable type definition to achieve the same thing.
You almost hit the spot with your initial types. Two changes required:
Make body of GetPosts of type void
Make body of returned function required
interface RequestInterface {
endpoint: string;
body?: unknown;
}
interface GetPosts extends RequestInterface {
endpoint: "/posts";
body: void;
}
interface CreatePost extends RequestInterface {
endpoint: "/posts";
body: string;
}
function Factory<R extends RequestInterface>(endpoint: R["endpoint"]) {
return (body: R["body"]): void => {
console.log(`Hitting ${endpoint} with ${body}`);
};
}
const myLibrary = {
getPosts: Factory<GetPosts>("/posts"),
createPosts: Factory<CreatePost>("/posts"),
};
// #ts-expect-error
myLibrary.getPosts("something");
// #ts-expect-error
myLibrary.createPosts(999);
// #ts-expect-error
myLibrary.createPosts();
myLibrary.getPosts();
myLibrary.createPosts("Hello, StackOverflow!");
TS Playground
Explanation
never type tells compiler that this should never happen. So, it someone tries to use GetPosts, it's an error, since it should never happen. void (undefined in this case should be also fine) tells that value should not be there.
Making body required in returned function makes it required. But since it is void for GetPosts, you can call it like myLibrary.getPosts(undefined) or simply myLibrary.getPosts() which is equivalent
Background
I'm not sure how I should approach sanitizing data I get from a Java backend for usage in a React form. And also the other way around: sanitizing data I get from a form when making a backend request. For frontend/backend communication we use OpenApi that generates Typescript interfaces and API for us from DTOs defined in Java.
Scenario
Example of the Schema in Java:
public enum Pet {
CAT,
DOG
}
#Schema(description = "Read, create or update an account")
public class AccountDto {
#NotNull
private Boolean active;
#NotBlank
private String userName;
#NotNull
private Pet preferedPet;
#Nullable
private String bioDescription;
// Constructor and getter/setters skipped
}
Current implementation
Example of the generated Typescript interface:
enum Pet {
CAT,
DOG
}
interface AccountDto {
active: boolean,
userName: string,
preferedPet: Pet,
bioDescription?: string // Translates to: string | undefined
}
Example React.js:
import {getAccount, updateAccount, Pet, AccountDto} from "./api"
export default function UpdateAccount() {
const [formData, setFormData] = useState<AccountDto>({
active: true,
userName: "",
preferedPet: Pet.CAT,
bioDescription: ""
})
useEffect(() => {
async function fetchAccount() {
const response = await getAccount();
// Omitted error handling
setFormData(response.data);
// response.data could look like this:
// {
// active: true,
// userName: "John",
// preferedPet: Pet.DOG,
// bioDescription: null
// }
}
}, [])
async function updateAccountHandler() {
const response = await updateAccount(formData);
// Omitted error handling
// Example formData object:
// {
// active: true,
// userName: "John",
// preferedPet: Pet.CAT,
// bioDescription: ""
// }
}
return (
// All input fields
)
}
Problems
When fetching the account, bioDescription is null. React will throw a warning that a component (bioDescription input) is changing from uncontrolled to controlled.
If by any chance there is a situation where null is set for preferedPet we will get a warning that the select value is not valid.
When updating the account all empty strings should be null. Required for the database and generally cleaner in my opinion.
Questions
1.) I'm wondering how other React users prepare/sanitize their data for usage and requests. Is there a go to or good practice I'm not aware of?
2.) Currently I'm using the following function to sanitize my data. It seems to work and Typescript does not notify me about any type mismatches but I think it should since bioDescription can only be string | undefined and not null.
function sanitizeData<T>(data: T, type: "use" | "request"): T {
const sanitizedData = Object.create({});
for (const [key, value] of Object.entries(data)) {
if (!value && type === "use") {
sanitizedData[key] = "";
} else if (!value && type === "request") {
sanitizedData[key] = null;
} else {
sanitizedData[key] = value;
}
}
return sanitizedData;
}
I have a situation where I'm trying to manually change a prop without using the React setState.
formData.description = null;
At this point Typescript is telling me that null is not possible. That's how I detected that my sanitizer function might not be correct.
Demo
Sandbox - https://codesandbox.io/s/async-cdn-7nd2m?file=/src/App.tsx
So I have a form that I need to add validators to and some of the controls are required only if a certain condition is matched by another control. What is a good way to do this. I originally made a custom validator function that I passed in a parameter to the function to determine if it should be required, but it keeps the original value of the parameter not matter if I update other controls in the form.
public static required(bookType: BookType, controlKey: string) {
return (control: AbstractControl): ValidationErrors | null => {
if(this.isRequired(bookType,controlKey)){
return !control.value? {required: true} : null
}
return null;
}
}
the form book type is originally DIGITAL and I change the book type to PRINT it stays DIGITAL.
This feels like it should stay a form-control validator since I am validating one value, not the group.
What would be the best way to make this work?
You need to implement a cross fields validator. So you will be able to play with values of these fields inside the validator function. Details: https://angular.io/guide/form-validation#cross-field-validation
const deliveryAddressRequiredValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
const bookType = control.get('bookType');
const deliveryAddress = control.get('deliveryAddress');
if (bookType && deliveryAddress && bookType.value === 'PRINT' && Validators.required(deliveryAddress)) {
return { deliveryAddressRequired: true };
}
// no validation for book type DIGITAL
return null;
};
Usage:
this.form = this.formBuilder.group({
bookType: ['', [Validators.required]],
deliveryAddress: [''],
}, {validators: deliveryAddressRequiredValidator});
To display error in the template use: form.errors?.deliveryAddressRequiredValidator
I am trying to write some basic tests for my custom pipe, but being new to Jasmine and Angular pipes I am having some difficulty. Here is my pipe:
decimal-format-pipe.js
import { Pipe , PipeTransform } from '#angular/core';
import { DecimalPipe } from '#angular/common';
#Pipe({
name: 'myDecimalFormatingPipe'
})
export class MyDecimalFormatPipe implements PipeTransform {
constructor(public decimalPipe: DecimalPipe) {};
transform(value: any) {
if (value || value === 0) {
value = this.decimalPipe.transform(value, '1.2-2');
}
return value;
}
}
Obviously, this 'custom' pipe simply implements the decimal pipe transform() for now, but that will change in the future.
Here is my spec:
import { MyDecimalFormatPipe } from './my-decimal-format.pipe';
import { DecimalPipe } from '#angular/common';
describe('MyDecimalFormatPipe', () => {
let pipe: MyDecimalFormatPipe;
let decimalPipe: DecimalPipe;
let inputValue: any = '2.1111';
beforeEach( () => {
decimalPipe = new DecimalPipe(inputValue);
myPipe = new MyDecimalFormatPipe(decimalPipe);
});
it('pipe is defined', () => {
expect(pipe instanceof MyDecimalFormatPipe).toBeTruthy();
});
describe('transform ', () => {
it('should return correct value type ', () => {
spyOn(decimalPipe, 'transform').and.callThrough();
decimalPipe.transform(inputValue, '1.2-2');
expect(decimalPipe.transform).toEqual('2.11');
});
});
});
My first spec passes but for the transform() test it fails and I get the
error:
RangeError: Invalid language tag: 2.1111
at new NumberFormat (native)
at Function.NumberFormatter.format (
I cannot recall the last time I have seen this error. What does 'invalid language tag' refer to? What is it about this spec that is making it break?
Completing y_vyshnevska's answer, there are several points that are to be noted :
You need to use decimalPipe = new DecimalPipe('arab'); to tell the DecimalPipe constructor to use the arabic number format (in your case).
From the official documentation, I believe you don't need to use a spy for this test (https://angular.io/guide/testing#pipes), but simply getting the returned result from your pipe should be enough.
Edited parts :
beforeEach(() => {
decimalPipe = new DecimalPipe('arab');
pipe = new MyDecimalFormatPipe(decimalPipe);
});
...
it('should return correct value type ', () => {
// spyOn(pipe, 'transform').and.callThrough();
const result = pipe.transform(inputValue);
expect(result).toEqual('2.11');
});
...
N.B: One funny thing I could witness, is that with the Headless chrome the test would pass as the result is correct (2.11); but in my chrome browser the test would fail saying the result is incorrect (2,11). I guess this is due to the browser settings, but I didn't expect that either :-)
What does 'invalid language tag' refer to?
The DecimalPipe constructor has an injected string argument for locale, which is used for Intl.NumberFormat construction deeper. Try
new Intl.NumberFormat('1.222').format(1.2222) in browser console, you will see your error.