@bespunky/angular-zen
Version:
The Angular tools you always wished were there.
740 lines (730 loc) • 34.7 kB
JavaScript
import * as i0 from '@angular/core';
import { InjectionToken, EventEmitter, Injectable, Directive, Attribute, NgModule, Optional, SkipSelf, Inject } from '@angular/core';
import * as i1$1 from '@bespunky/angular-zen/core';
import { Destroyable, CoreModule } from '@bespunky/angular-zen/core';
import * as i1 from '@angular/router';
import { PRIMARY_OUTLET, NavigationStart, RouteConfigLoadStart, RouteConfigLoadEnd, RoutesRecognized, GuardsCheckStart, ChildActivationStart, ActivationStart, GuardsCheckEnd, ResolveStart, ResolveEnd, ChildActivationEnd, ActivationEnd, NavigationEnd, NavigationCancel, NavigationError, Scroll } from '@angular/router';
import { BehaviorSubject, of, from } from 'rxjs';
import { takeUntil, filter, concatAll, toArray, finalize } from 'rxjs/operators';
/**
* An injection token for the provided router configuration.
* `RouterExModule.forRoot()` facilitates the injection of this token. No need to inject directly.
*/
const RouterX = new InjectionToken('RouterX.Config');
/** The default configuration for the router-x module. */
const DefaultRouterXConfig = {};
/**
* Creates a provider for the router-x module configuration.
* Options not provided will be replaced with their default values according to `DefaultRouterXConfig`.
*
* @export
* @param {RouterXConfig} config
* @returns {Provider}
*/
function provideRouterXConfig(config) {
config = Object.assign({}, DefaultRouterXConfig, config);
return { provide: RouterX, useValue: config };
}
/**
* Holds data related with a router outlet event.
*
* @export
* @class RouterOutletEventData
*/
class RouterOutletEventData {
/**
* Creates an instance of RouterOutletEventData.
*
* @param {string} outletName The name of the outlet which triggered the event. For the primary unnamed outlet, this will be angular's PRIMARY_OUTLET.
*/
constructor(outletName) {
this.outletName = outletName;
}
/**
* `true` if the event was triggered by the primary unnamed outlet; otherwise `false`.
*
* @readonly
* @type {boolean}
*/
get isPrimaryOutlet() {
return this.outletName === PRIMARY_OUTLET;
}
}
/**
* Holds data related with component publishing triggered by outlet activation.
*
* @export
* @class ComponentPublishEventData
* @extends {RouterOutletEventData}
*/
class ComponentPublishEventData extends RouterOutletEventData {
/**
* Creates an instance of ComponentPublishEventData.
*
* @param {BehaviorSubject<AnyObject | null>} changes The observable used to track changes to the activated component of the triggering outlet.
* @param {string} outletName The name of the outlet which triggered the event. For the primary unnamed outlet, this will be angular's PRIMARY_OUTLET.
*/
constructor(changes, outletName) {
super(outletName);
this.changes = changes;
}
/**
* The instance of the last component activated by the outlet which triggered the event.
* This will be null if the outlet has deactivated the component.
*
* @readonly
* @type {(AnyObject | null)}
*/
get componentInstance() {
return this.changes.value;
}
}
/**
* Provides a publish bus for the currently rendered component.
*
* **Why?**
* Angular's router only provides the type of component being rendered for a specific route, but not the instance it has created for it.
* This service is a bridge which allows other services to get a hold of the instance of a currently rendered component.
*
* **How to use:**
* Use the [`publishComponent`](/directives/PublishComponentDirective.html) directive on your `<router-outlet>` element. This will hook into the outlet's `activate` event and pass
* the activated component to the bus service:
* @example
* <!-- Component template -->
* <router-outlet publishComponent name="header"></router-outlet>
* <router-outlet publishComponent ></router-outlet>
* <router-outlet publishComponent name="footer"></router-outlet>
*
* @export
* @class RouterOutletComponentBus
*/
class RouterOutletComponentBus {
constructor() {
this._outletsState = new Map();
/**
* A map of the currently instantiated components by outlet name.
* Users can either subscribe to changes, or get the current value of a component.
*
* The primary unnamed outlet component will be accessible via PRIMARY_OUTLET, but for scalability it is better to access it via the `instance()` method.
*
* @private
*/
this.components = new Map();
/**
* Emits whenever a router outlet marked with the `publishComponent` directive activates a component.
* When an outlet deactivates a component, the published component instance will be `null`.
*
* @type {EventEmitter<ComponentPublishEventData>}
*/
this.componentPublished = new EventEmitter();
/**
* Emits whenever a router outlet marked with the `publishComponent` directive is removed from the DOM.
*
* @type {EventEmitter<ComponentPublishEventData>}
*/
this.componentUnpublished = new EventEmitter();
}
/**
* Gets a shallow clone of the current state outlet state.
*
* @readonly
* @type {(Map<string, AnyObject | null>)}
*/
get outletsState() {
return new Map(this._outletsState);
}
/**
* Publishes the instance of a currently activated or deactivated component by the specified outlet.
* When an outlet first publishes, this will create an observable for tracking the outlet's changes.
* The observable can be fetched using the `changes()` method.
* Following calls to publish a component by the same outlet will subscribers.
*
* The last published component of an outlet can be fetched using the `instance()` method.
*
* @param {AnyObject | null} instance The instance of the activated component. For publishing deactivation of a component pass `null`.
* @param {string} [outletName=PRIMARY_OUTLET] (Optional) The name of the outlet which activated or deactivated the component. The primary unnamed outlet will be used when not specified.
*/
publishComponent(instance, outletName = PRIMARY_OUTLET) {
const components = this.components;
let componentChanges = components.get(outletName);
if (!componentChanges) {
componentChanges = new BehaviorSubject(instance);
components.set(outletName, componentChanges);
}
this._outletsState.set(outletName, instance);
componentChanges.next(instance);
this.componentPublished.emit(new ComponentPublishEventData(componentChanges, outletName));
}
/**
* Notifies any subscribers to the outlet's changes observable that the outlet is being removed by completing
* the observable and removes the observable from the service.
*
* @param {string} [outletName=PRIMARY_OUTLET] (Optional) The name of the outlet to unpublish. The primary unnamed outlet will be used when not specified.
*/
unpublishComponent(outletName = PRIMARY_OUTLET) {
const components = this.components;
if (components.has(outletName)) {
// Notify any subscribers that the outlet will stop emitting
components.get(outletName)?.complete();
// Make sure the outlet is no longer present on the bus
components.delete(outletName);
this._outletsState.delete(outletName);
this.componentUnpublished.emit(new RouterOutletEventData(outletName));
}
}
/**
* Checks whether the outlet by the given name is present in the DOM and has already activated at least one component.
* This will be `true` even if the outlet currently has no active component (component is `null`).
*
* A `false` value can either mean the outlet hasn't been marked with `publishComponent`, or that the outlet is not currently rendered (not present in the DOM).
*
* When `true`, the user can subscribe to changes of that outlet through the `changes()` method.
*
* @param {string} [outletName=PRIMARY_OUTLET] (Optional) The name of the outlet to check. The primary unnamed outlet will be checked if no name is provided.
* @returns {boolean} `true` if the outlet has published a component at least once; otherwise `false`.
*/
isComponentPublished(outletName = PRIMARY_OUTLET) {
return this.components.has(outletName);
}
/**
* Gets an observable which can be used to track changes to the activated component of the specified outlet.
* If the outlet is not rendered (present in the DOM), or hasn't been marked with `publishComponent`, this will be `null`.
*
* @param {string} [outletName=PRIMARY_OUTLET] (Optional) The name of the outlet to track changes for. The primary unnamed outlet will be used when not specified.
* @returns {(BehaviorSubject<AnyObject | null> | null)} An observable to use for tracking changes to the activated component for the specified outlet, or `null` if no such outlet exists.
*/
changes(outletName = PRIMARY_OUTLET) {
return this.components.get(outletName) ?? null;
}
/**
* Gets the current instance of the component created by the specified outlet.
*
* @param {string} [outletName=PRIMARY_OUTLET] (Optional) The name of the outlet to fetch the component instance for. If not provided, the primary unnamed outlet's component will be fetched.
* @returns {(AnyObject | null)} The instance of the component created by the specified outlet. If the outlet doesn't exist, or there is no component instance for the requested outlet, returns `null`.
*/
instance(outletName = PRIMARY_OUTLET) {
return this.components.get(outletName)?.value ?? null;
}
}
RouterOutletComponentBus.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: RouterOutletComponentBus, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
RouterOutletComponentBus.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: RouterOutletComponentBus, providedIn: 'root' });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: RouterOutletComponentBus, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}] });
/**
* Hooks into a router outlet's events and publishes the current component to the [`RouterOutletComponentBus`](/injectables/RouterOutletComponentBus.html) to create a mapping
* of component instances by outlet name.
*
* Components instantiated by outlets marked with `publishComponent` will be accessible by outlet name in the bus service.
*
* @example
* <!-- Component template -->
* <router-outlet publishComponent name="header"></router-outlet>
* <router-outlet publishComponent ></router-outlet>
* <router-outlet publishComponent name="footer"></router-outlet>
*
* @See `RouterOutletComponentBus` for more details.
*
* @export
* @class PublishComponentDirective
* @extends {Destroyable}
* @implements {OnInit}
*/
class PublishComponentDirective extends Destroyable {
constructor(outlet, componentBus, outletName) {
super();
this.outlet = outlet;
this.componentBus = componentBus;
this.outletName = outletName;
}
/**
* Registers to outlet events to publish the activated and deactivated components to the bus. *
*/
ngOnInit() {
// When the outlet activates a new instance, update the component on the bus
this.subscribe(this.outlet.activateEvents, this.updateComponentOnBus.bind(this));
// When the outlet deactivates an instance, set the component to null on the bus.
this.subscribe(this.outlet.deactivateEvents, () => this.updateComponentOnBus(null));
}
/**
* Unpublishes the outlet from the bus.
*/
ngOnDestroy() {
// An outlet might be kept alive while its component is switched. So when the outlet is completely destroyed,
// it will be completely removed from the bus, even though its value on the bus is null.
this.componentBus.unpublishComponent(this.outletName);
super.ngOnDestroy();
}
updateComponentOnBus(instance) {
this.componentBus.publishComponent(instance, this.outletName);
}
}
PublishComponentDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: PublishComponentDirective, deps: [{ token: i1.RouterOutlet }, { token: RouterOutletComponentBus }, { token: 'name', attribute: true }], target: i0.ɵɵFactoryTarget.Directive });
PublishComponentDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.12", type: PublishComponentDirective, selector: "router-outlet[publishComponent]", usesInheritance: true, ngImport: i0 });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: PublishComponentDirective, decorators: [{
type: Directive,
args: [{
// eslint-disable-next-line @angular-eslint/directive-selector
selector: 'router-outlet[publishComponent]'
}]
}], ctorParameters: function () { return [{ type: i1.RouterOutlet }, { type: RouterOutletComponentBus }, { type: undefined, decorators: [{
type: Attribute,
args: ['name']
}] }]; } });
/**
* Provides services for libraries requiring integration with their user's language services.
*
* @export
* @class RouterXModule
*/
class RouterXModule {
constructor(parentModule) {
if (parentModule)
throw new Error('`RouterXModule` has already been loaded. Import it only once, in your app module using, `forRoot()`.');
}
/**
* Generates the router-x module with the appropriate providers.
*
* @static
* @param {RouterXConfig} config (Optional) The configuration for the router extension module.
*/
static forRoot(config) {
return {
ngModule: RouterXModule,
providers: [provideRouterXConfig(config)]
};
}
/**
* Generates the router-x module for child modules.
*
* @static
*/
static forChild() {
return { ngModule: RouterXModule };
}
}
RouterXModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: RouterXModule, deps: [{ token: RouterXModule, optional: true, skipSelf: true }], target: i0.ɵɵFactoryTarget.NgModule });
RouterXModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.2.12", ngImport: i0, type: RouterXModule, declarations: [PublishComponentDirective], imports: [CoreModule], exports: [PublishComponentDirective] });
RouterXModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: RouterXModule, imports: [CoreModule] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: RouterXModule, decorators: [{
type: NgModule,
args: [{
imports: [CoreModule],
declarations: [PublishComponentDirective],
exports: [PublishComponentDirective]
}]
}], ctorParameters: function () { return [{ type: RouterXModule, decorators: [{
type: Optional
}, {
type: SkipSelf
}] }]; } });
/**
* Hard-codes event names as strings.
* When AOT compilation is run and constructor names change, the dispatcher will still be able to get a hold
* of the correct event name using this map.
*/
const EventMap = {
[NavigationStart.prototype.constructor.name]: 'NavigationStart',
[RouteConfigLoadStart.prototype.constructor.name]: 'RouteConfigLoadStart',
[RouteConfigLoadEnd.prototype.constructor.name]: 'RouteConfigLoadEnd',
[RoutesRecognized.prototype.constructor.name]: 'RoutesRecognized',
[GuardsCheckStart.prototype.constructor.name]: 'GuardsCheckStart',
[ChildActivationStart.prototype.constructor.name]: 'ChildActivationStart',
[ActivationStart.prototype.constructor.name]: 'ActivationStart',
[GuardsCheckEnd.prototype.constructor.name]: 'GuardsCheckEnd',
[ResolveStart.prototype.constructor.name]: 'ResolveStart',
[ResolveEnd.prototype.constructor.name]: 'ResolveEnd',
[ChildActivationEnd.prototype.constructor.name]: 'ChildActivationEnd',
[ActivationEnd.prototype.constructor.name]: 'ActivationEnd',
[NavigationEnd.prototype.constructor.name]: 'NavigationEnd',
[NavigationCancel.prototype.constructor.name]: 'NavigationCancel',
[NavigationError.prototype.constructor.name]: 'NavigationError',
[Scroll.prototype.constructor.name]: 'Scroll'
};
/**
* The prefix of the id generated for zone macro tasks when calling `RouteAware.resolveInMacroTask()`.
*
* Generated ids will confrom to a `{prefix}-{random number}` format.
*/
const ResolverMacroTaskIdPrefix = 'route-aware-resolver';
/**
* Provides functionality for extending class to easily work with routes and process changes.
*
* @export
* @abstract
* @class RouteAware
* @extends {Destroyable}
*/
// eslint-disable-next-line @angular-eslint/directive-class-suffix
class RouteAware extends Destroyable {
/**
* Creates an instance of RouteAware.
*
* @param {Router} router The instance of Angular's router service.
* @param {ActivatedRoute} route The instance of Angular's active route service.
* @param {RouterOutletComponentBus} [componentBus] (Optional) The component bus for router-x functionality.
* Provide this when you want your route-aware service to have access to the instance(s) of the activated component(s).
*/
constructor(router, route, componentBus) {
super();
this.router = router;
this.route = route;
this.componentBus = componentBus;
// TODO: Scan class and only subscribe if handlers were defined
this.subscribe(this.router.events, this.dispatchRouterEvent.bind(this));
}
/**
* Checks if a handler method for the specific event type exists on the service and calls it.
* Handler methods should comply with `onEventType` naming (lowercase 'on', first-upper event type).
*
* @private
* @param {Event} event The event data received from the router.
*/
dispatchRouterEvent(event) {
// AOT compilation changes class names, causing the dispacher to look for a handler methods with
// wrong names (e.g. `onJ`). The EventMap is used to restore the original names.
const typeName = event.constructor.name;
const handlerName = `on${EventMap[typeName]}`;
const handle = this[handlerName];
if (handle)
handle.call(this, event);
}
/**
* Creates an observable that emits only the specified router events and is automatically destroyed when the service/component is destroyed.
*
* @protected
* @template TEvent The type of router event to emit.
* @param {Type<TEvent>} eventType The type of router event to emit.
* @param {boolean} [autoUnsubscribe=true] (Optional) `true` to make the observable complete when the service/component is destroyed; otherwise `false`. Default is `true`.
* @returns {Observable<TEvent>}
*/
observeRouterEvent(eventType, autoUnsubscribe = true) {
let observable = this.router.events;
if (autoUnsubscribe)
observable = observable.pipe(takeUntil(this.destroyed));
return observable.pipe(filter(event => event.constructor === eventType));
}
/**
* Recoursively runs a processing function on the route and its children.
* Scan is done from parent to child, meaning the parent is the first to process.
*
* @protected
* @param {ActivatedRouteSnapshot} route The top route on which to apply the processing function.
* @param {(route: ActivatedRouteSnapshot, component: any) => boolean | void} process The function to run on the route and its children. The function receives a `route` argument which reflects the route being processed,
* and a `component` argument which reflects the component that was loaded for the route's outlet.
* If the corresponding outlet wasn't marked with the `publishComponent` directive, the `component` argument will be null.
*
* Returning `true` from the process function is equal to saying 'work has completed' and will stop propogation to the route's children.
* @param {number} [levels=-1] (Optional) The number of levels (excluding the parent) to dive deeper into the route tree.
* A value of 1 for example, will process the route and its first-level children only. By default, scans all levels of the route tree.
*/
deepScanRoute(route, process, levels = -1) {
// Make sure the caller wants scan to proceed, then make sure level limit wasn't reached.
const processingConcluded = process(route, this.componentBus?.instance(route.outlet));
// Negative values will scan all, positives will scan until reaching zero.
const shouldScanChildren = !processingConcluded && levels !== 0;
if (shouldScanChildren && route.children)
route.children.forEach(childRoute => this.deepScanRoute(childRoute, process, levels - 1));
}
/**
* Creates an observable that runs all the specified resolvers and concats their results as an array.
* The resolvers will be passed with the instance of the component for the currently activated route.
*
* @protected
* @param {(Resolver | Resolver[])} resolvers The resolver(s) to concat.
* @param {...any[]} resolverArgs (Optional) Any arguments to pass into the resolvers in addition to the component.
* @returns {Observable<any[]>} An array with the concatenated results of the resolvers.
*/
resolve(resolvers, ...resolverArgs) {
if (!resolvers)
return of([]);
// Cast array
if (!Array.isArray(resolvers))
resolvers = [resolvers];
// Run resolvers to create observable tasks
const observables = resolvers.map(resolve => resolve(this.activatedRouteComponent, ...resolverArgs));
// Run tasks and output their returned data as an array
return from(observables).pipe(concatAll(), toArray());
}
/**
* Creates an observable that runs all the specified resolvers and concats their results as an array.
* The resolvers will be passed with the instance of the component for the currently activated route.
*
* **Angular Universal:**
* In SSR, the server doesn't wait for async code to complete. The result is scrapers and search engines receiving a page without resolved data,
* which is bad in case you need them to read some resolved metadata tags for example.
*
* Using `Zone` directly, this method creates a macro task and completes it when resolves are done or have errored.
* This makes the server block and wait until everything is resolved or errors before returning the rendered page.
*
* > *ℹ Make sure your resolves and process function are fast enough so that the server won't hang too much trying to render.*
*
* @see https://stackoverflow.com/a/50065783/4371525 for the discussion.
*
* @see {ResolverMacroTaskIdPrefix} if you need to identify the created macro task in your code.
*
* @protected
* @param {(Resolver | Resolver[])} resolvers The resolver(s) to concat.
* @param {...any[]} resolverArgs (Optional) Any arguments to pass into the resolvers in addition to the component.
*/
resolveInMacroTask(resolvers, ...resolverArgs) {
// eslint-disable-next-line @typescript-eslint/no-empty-function
const macroTask = Zone.current.scheduleMacroTask(`${ResolverMacroTaskIdPrefix}-${Math.random()}`, () => { }, {}, () => { }, () => { });
return this.resolve(resolvers, ...resolverArgs)
// Signal end of macro task on completion or error and allow server to return
.pipe(finalize(() => macroTask.invoke()));
}
/**
* The instance of the component created for the currently activated route.
* If no component bus was supplied at construction time, this will be `undefined`.
*
* @readonly
* @protected
* @type {(any | null)}
*/
get activatedRouteComponent() {
return this.componentBus?.instance(this.route.outlet);
}
}
RouteAware.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: RouteAware, deps: [{ token: i1.Router }, { token: i1.ActivatedRoute }, { token: RouterOutletComponentBus }], target: i0.ɵɵFactoryTarget.Directive });
RouteAware.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.12", type: RouteAware, usesInheritance: true, ngImport: i0 });
RouteAware.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: RouteAware });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: RouteAware, decorators: [{
type: Directive
}, {
type: Injectable
}], ctorParameters: function () { return [{ type: i1.Router }, { type: i1.ActivatedRoute }, { type: RouterOutletComponentBus }]; } });
/**
* Provides tools for breaking the current and any url to their different parts.
*
* @export
* @class UrlReflectionService
*/
class UrlReflectionService {
constructor(document, router, route, config) {
this.document = document;
this.router = router;
this.route = route;
this.config = config;
/**
* A regular expression to match the route part of a url. The url can be fully qualified or start at the route.
* The extracted group will be named 'route'.
*
* @example
* The regex will extract '/this/is/the/route' for all of the following:
*
* Fully qualified urls:
* `https://some.website.com/this/is/the/route?a=1&b=2&c=3`
* `https://some.website.com/this/is/the/route#someFragment`
* `https://some.website.com/this/is/the/route?debug=true#fragment`
*
* Relative routes:
* `/this/is/the/route?a=1&b=2&c=3`
* `/this/is/the/route#someFragment`
* `/this/is/the/route?debug=true#fragment`
*
* The regex will extract 'this/is/the/route' (no head slash) for all of the following:
* `this/is/the/route?a=1&b=2&c=3`
* `this/is/the/route#someFragment`
* `this/is/the/route?debug=true#fragment`
**/
this.RouteRegex = /^(?:http[s]?:\/\/[^/]+)?(?<route>[^?#]+)(?=[?#]|$)/;
/**
* A regular expression to match all segments of a route.
* Looks for `/<segment>/` parts and extract them without the slashes.
* The extracted groups will be named 'segment'.
*/
this.RouteSegmentsRegex = /(?!\/)(?<segment>[^/]+)/g;
/**
* A regular expression to match the question mark and everything that follows in a url.
* The extracted group will be named 'queryString'.
*
* @example
* The regex will extract '?a=1&b=2&c=3' for all of the following:
* https://some.website.com/some/route?a=1&b=2&c=3
* https://some.website.com/some/route?a=1&b=2&c=3#fragment
* /some/route?a=1&b=2&c=3#fragment
* ?a=1&b=2&c=3#fragment
*/
this.QueryStringRegex = /(?<queryString>\?[^#]*)/;
/**
* A regular expression to match the hash sign and everything that follows in a url.
* The extracted group will be named 'fragment'.
*
* @example
* The regex will extract '#fragment' for all of the following:
* https://some.website.com/some/route?a=1&b=2&c=3#fragment
* /some/route?a=1&b=2&c=3#fragment
* some/route?a=1&b=2&c=3#fragment
*/
this.FragmentRegex = /(?<fragment>#.*)$/;
const hostUrl = this.config?.hostUrl;
// If the hostUrl has been provided by the user, use it; otherwise, fetch from the location service
this.hostUrl = hostUrl || this.document.nativeDocument.location.origin;
}
/**
* Extracts the route portion of a given url.
*
* @example
* routeOf('https://some.website.com/some/route?a=1&b=2&c=3') === '/some/route'
*
* @param {string} url The url for which to extract the route portion.
* @returns {string} The route portion of the url.
*/
routeOf(url) {
return url.match(this.RouteRegex)?.groups?.['route'] || '';
}
/**
* Extracts the route portion of a url as an array of route segments, not including the empty root segment.
*
* @example
* routeSegmentsOf('https://some.website.com/some/route?a=1&b=2&c=3') === ['some', 'route']
* routeSegmentsOf('/some/route') === ['some', 'route']
*
* @param {string} routeOrUrl The route or complete url from which to extract the route segments.
* @returns {string[]} The segments of the route.
*/
routeSegmentsOf(routeOrUrl) {
// Extract the route portion only, then match with the regex to extract the array of segments
return this.routeOf(routeOrUrl).match(this.RouteSegmentsRegex) || [];
}
/**
* Extracts the query string of a specified url.
*
* @example
* queryStringOf('https://some.website.com/some/route?a=1&b=2&c=3') === '?a=1&b=2&c=3'
*
* @param {string} url The url from which to extract the query string.
* @returns {string} The query string extracted from the url.
*/
queryStringOf(url) {
const matches = url.match(this.QueryStringRegex) || [''];
return matches[0];
}
/**
* Removes the query portion of a url.
*
* @example
* stripQuery('https://some.website.com/some/route?a=1&b=2&c=3#fragment') === 'https://some.website.com/some/route#fragment'
*
* @param {string} url The url from which to remove the query.
* @returns {string} The specified url without the query portion.
*/
stripQuery(url) {
return url.replace(this.QueryStringRegex, '');
}
/**
* Extracts the fragment from a url.
*
* @example
* fragmentOf('https://some.website.com/some/route?a=1&b=2&c=3#fragment') === '#fragment'
*
* @param {string} url The url from which to extract the fragment.
* @returns {string} The fragment extracted from the url.
*/
fragmentOf(url) {
const matches = url.match(this.FragmentRegex) || [''];
return matches[0];
}
/**
* Removes the fragment portion of a url.
*
* @example
* stripFragment('https://some.website.com/some/route?a=1&b=2&c=3#fragment') === 'https://some.website.com/some/route?a=1&b=2&c=3'
*
* @param {string} url The url to remove the fragment.
* @returns {string} The url without the fragment portion.
*/
stripFragment(url) {
return url.replace(this.FragmentRegex, '');
}
/**
* Makes sure the url is prefixed with https instead of http.
*
* @param {string} url The url to secure.
* @returns {string} The secure url.
*/
forceHttps(url) {
return url.replace(/^http:\/\//, 'https://');
}
/**
* The fully qualified url of the currently navigated route (e.g. 'https://some.website.com/some/route?a=1&b=2&c=3#fragment').
*
* @readonly
* @type {string}
*/
get fullUrl() {
return `${this.hostUrl}${this.router.url}`;
}
/**
* The route url of the currently navigated route (e.g. '/some/route').
*
* @readonly
* @type {string}
*/
get routeUrl() {
return this.routeOf(this.router.url);
}
/**
* The segments of the currently navigated route (e.g. ['some', 'route']).
*
* @readonly
* @type {string[]}
*/
get routeSegments() {
return this.routeSegmentsOf(this.routeUrl);
}
/**
* The object representing the query params in the currently navigated route.
*
* @readonly
* @type {*}
*/
get queryParams() {
return { ...this.route.snapshot.queryParams };
}
/**
* The query string portion of the currently navigated route (e.g. '?a=1&b=2&c=3').
*
* @readonly
* @type {string}
*/
get queryString() {
return this.queryStringOf(this.router.url);
}
/**
* The fragment portion of the currently navigated route, without the hash sign (e.g. 'fragment').
*
* @readonly
* @type {string}
*/
get fragment() {
return this.route.snapshot.fragment || '';
}
/**
* The fragment portion of the currently navigated route, with the hash sign (e.g. '#fragment').
*
* @readonly
* @type {string}
*/
get fragmentString() {
return `#${this.fragment}`;
}
}
UrlReflectionService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: UrlReflectionService, deps: [{ token: i1$1.DocumentRef }, { token: i1.Router }, { token: i1.ActivatedRoute }, { token: RouterX, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
UrlReflectionService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: UrlReflectionService, providedIn: 'root' });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: UrlReflectionService, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}], ctorParameters: function () { return [{ type: i1$1.DocumentRef }, { type: i1.Router }, { type: i1.ActivatedRoute }, { type: undefined, decorators: [{
type: Optional
}, {
type: Inject,
args: [RouterX]
}] }]; } });
/**
* Generated bundle index. Do not edit.
*/
export { ComponentPublishEventData, PublishComponentDirective, ResolverMacroTaskIdPrefix, RouteAware, RouterOutletComponentBus, RouterOutletEventData, RouterXModule, UrlReflectionService };
//# sourceMappingURL=bespunky-angular-zen-router-x.mjs.map