Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 31 additions & 8 deletions src/app/core/_validators/url.validator.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,45 @@
// url.validator.ts
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';

/**
* List of allowed URL protocols.
* Modify this array to add or remove allowed protocols.
*/
const allowedProtocols = ['http:', 'https:'];

/**
* Creates an Angular ValidatorFn that validates whether a control's value is a valid URL.
* This validator attempts to parse the value using the browser's native URL API.
* @returns A ValidatorFn function to use in Angular Reactive Forms.
*/
export function urlValidator(): ValidatorFn {
// Build regex dynamically from allowedProtocols to enforce "//"
const protocolPattern = allowedProtocols.map((protocol) => protocol.replace(':', '')).join('|');
const doubleSlashRegex = new RegExp(`^(${protocolPattern})://.+`, 'i');

return (control: AbstractControl): ValidationErrors | null => {
const rawValue = control.value?.trim();
if (!rawValue) {
// Don't validate empty values; leave that to Validators.required
return null;
const value: string | null = control.value?.trim();
if (!value) {
return null; // Let empty values be handled by Validators.required
}
if (URL.canParse(rawValue)) {
const { hostname } = new URL(rawValue);
return hostname ? null : { invalidUrl: { value: rawValue } };

// Ensure "//" is present
if (!doubleSlashRegex.test(value)) {
return { invalidUrl: { value } };
}

// Use the native URL API for validation
try {
const url = new URL(value);

// Validate protocol
if (allowedProtocols.includes(url.protocol)) {
return null; // valid URL
} else {
return { invalidUrl: { value } };
}
} catch {
return { invalidUrl: { value } };
}
return { invalidUrl: { value: rawValue } };
};
}