URL Pattern API już jest!
Na początku tego roku, gdy zaczynałem nowy projekt, jedną z potrzeb biznesowych było stworzenie tzw. big page loader.
Jedną z dobrych praktyk tworzenia takiego rozwiązania w Angularze jest podpięcie pokazywania/ukrywania na globalnym interceptorze.
export const loadingInterceptor: HttpInterceptorFn = (req, next) => {
if (isExcluded(req.url)) {
return next(req);
}
const loadingService = inject(LoadingService);
loadingService.show();
return next(req).pipe(
finalize(() => loadingService.hide())
);
};
Nie będę omawiał w tym materiale elementów Angulara, opisuję tylko swój przypadek użycia. URL Pattern można wykorzystać w każdym frameworku, czy czystym JS. Biblioteka, taka jak Axios również ma interceptory, więc możemy w podobny sposób zrobić to w swoim ulubionym frameworku.
Wszystko fajnie, loader pokazuje się i ukrywa przy każdym zapytaniu do serwera.
Gdzie problem?
Problem zaczyna się wtedy, gdy w konkretnych przypadkach chcemy wyłączyć taki loader.
Jak to rozwiązać?
Ilu programistów tyle podejść do tematu:
- doklejanie specyficznego headera w interceptorze, przechwycenie go i obsługa,
- przekazywanie flag true/false przed zapytaniem i wysyłanie do interceptora,
- wrappery na zapytania,
W tych przypadkach problem polega na tym, że musimy przechodzić przez cały projekt, szukać plików i ręcznie je modyfikować.
Może się zdarzyć również, że będziemy mieć kilka różnych rozwiązań od kilku różnych developerów brak spójność i jeden chaos.
Jak rozwiązałem?
Stworzyłem tablicę adresów, które chce wykluczyć z loadera.
const excludedUrls:string[] = [
'/product/search',
'/product/all',
'/product/:id',
'/product/:type/:id'
];
Prawie działa. Statyczne adresy można podstawić i porównać – będzie to działało.
Gorzej z /:id czy /product/:type/:id. Jeśli w interceptorze dostaniemy adres /product/2 lub /product/basic/4, porównanie z naszą tablicą excludedUrls nic nie da, bo to wszystko są statyczne stringi.
Pierwsza myśl? Zamienić na wyrażenia regularne.
const excludedUrls:RegExp[] = [
/^\/product\/search$/,
/^\/products\/all$/,
/^\/product\/[^/]+$/,
/^\/product\/[^/]+\/[^/]+$/
];
Wtedy wszystko, co wpadnie w /:id :type/:id, będzie się dynamicznie podstawiało i przypadek zostanie obsłużony!
Potrzeba biznesowa została zrealizowana, zamieniłem również każdy URL na Regex, aby łatwiej było sprawdzać.
No ale…
Nie wygląda to zbyt czytelnie szczególnie /^\/product\/[^/]+\/[^/]+$/, a dwa – zawsze trzeba pamiętać o konstrukcjach Regex, co też może być kłopotliwe z czasem.
W wolnej chwili przeszukałem internet i natrafiłem na URL Pattern API. Niestety na tamten czas Safari, jak i Firefox nie wspierały jeszcze tego rozwiązania. Musiałem obejść się smakiem i zostać przy Regexach…
Aż do września 2025 roku, gdzie możemy korzystać z tego API we wszystkich popularnych przeglądarkach!
URL Pattern API
new URLPattern()
W skrócie: jest to standard WHATWG zapewniający czytelne i łatwe w utrzymaniu dopasowywanie adresów URL zamiast stosowania wyrażeń regularnych!
Ma status Newly Available, czyli mamy pewność, że wszystkie nowoczesne przeglądarki obsługują to API.
Jest to natywne rozwiązanie, nie jest w żaden sposób powiązane z Angularem, czy innym frameworkiem!
Korzystaj z rozwagą. Jeśli twoje systemy wspierają starsze wersje przeglądarek, to rozwiązanie nie będzie działało. Rozważ inne podejścia np. stosując wyrażenia regularne jak wyżej.
Krótki przegląd klasy
Klasa przyjmuje aż dziewięć parametrów protocol username password hostname port pathname search hash hasRegExpGroups
i dwie metody exec oraz test.
Możliwości są ogromne. Nie musimy podawać wszystkich danych do konstruktora, wystarczy podać te, które nas interesują. Reszta zostanie pominięta w sprawdzaniu.
pathname
W naszym przypadku, gdy chcemy sprawdzać, czy dany URL jest zgodny ze wzorem /product/:id wystarczy, że zastosujemy parametr pathname.
Cała reszta zostanie pominięta w sprawdzaniu.
const pattern = new URLPattern({
pathname: "/product/:id"
});
console.log(pattern.test("https://gonewild.dev/product/123")); // true
console.log(pattern.test({
pathname: "/products/123"
})); //true
hostname
Możemy rozszerzyć o kolejny parametr:
const pattern = new URLPattern({
hostname: "gonewild.dev",
pathname: "/product/:id"
});
console.log(pattern.test("https://gonewild.dev/product/123")); // true
console.log(pattern.test("https://gonewildd.dev/product/123")); // false
console.log(pattern.test({
pathname: "/product/123",
hostname: "gonewild.dev"
})); //true
protocol
Możemy również zawęzić, aby tylko https przyjmowało:
const pattern = new URLPattern({
protocol: "https",
hostname: "gonewild.dev",
pathname: "/product/:type/:id"
});
console.log(pattern.test("https://gonewild.dev/product/basic/123")); // true
console.log(pattern.test("http://gonewild.dev/product/basic/123")); // false
metoda test()
Metoda zwraca wynik boolean testu, nic więcej:
const pattern = new URLPattern();
console.log(pattern.test()); // boolean
metoda exec()
Metoda zwraca obiekt ze szczegółami, jeśli nie ma nic zwraca null.
Co ciekawsze, jeśli się odwołamy do pathname.groups, obiekt zwróci wszystkie zmienne, które zostały zdefiniowane.
const pattern = new URLPattern({
pathname: "/product/:type/:id"
});
const exec = pattern.exec("https://gonewild.dev/product/basic/2");
const { pathname } = pattern.exec({ pathname: "/product/basic/2" });
console.log(exec.pathname.groups) // {type: "basic", id: "2"}
console.log(pathname.groups) // {type: "basic", id: "2"}
Możliwości jest naprawdę sporo, zachęcam do odkrywania tego interfejsu, więcej fajnych przykładów można znaleźć na MDN URL Pattern
Rozwiązanie
Teraz gdy już wiemy, jak obsługiwać URL Pattern API, możemy napisać funkcję sprawdzającą, czy adres URL jest wykluczony z loadera:
const excludedUrls = [
'/product/search',
'/products/all',
'/product/:id',
'/product/:type/:id'
];
const patterns = excludedUrls.map((pathname) => new URLPattern({ pathname }));
const isExcluded = (urlPath) => patterns.some((pattern) => pattern.test(urlPath));
Dla porównania funkcja na wyrażeniach regularnych:
const excludedUrls: RegExp[] = [
/^\/products\/search$/,
/^\/products\/all$/,
/^\/product\/[^/]+$/
];
const isExcluded = (urlPath) => excludedUrls.some((pattern) => pattern.test(urlPath));
Od Autora
Jak widać, w porównaniu, nowy interfejs powoduje dodatkowy narzut w postaci tworzenia instancji klasy za każdym razem.
Z drugiej strony, nie musimy się zastanawiać nad dekodowaniem wyrażenia regularnego oraz daje nam o wiele większe możliwości.
Jak zawsze są plusy/minusy i ‘to zależy’.
Wybierz to, co będzie najlepsze dla Ciebie!
URLPattern
Baseline 2025 newly available
Supported in Chrome: yes.
Supported in Edge: yes.
Supported in Firefox: yes.
Supported in Safari: yes.
Since September 2025 this feature works across the latest devices and browser versions. This feature might not work in older devices or browsers.