@ibenvandeveire/ngx-utils
Version:
A series of abstracts, utils, pipes and services for Angular applications, created by Iben Van de Veire.
789 lines (770 loc) • 34.4 kB
JavaScript
import * as i0 from '@angular/core';
import { input, output, HostListener, Directive, inject, Injectable, Pipe, InjectionToken } from '@angular/core';
import { NgxWindowService } from '@ibenvandeveire/ngx-core';
import { EMPTY, fromEvent, Subject, NEVER, BehaviorSubject, ReplaySubject, filter, map, tap, takeUntil, take } from 'rxjs';
import { DomSanitizer } from '@angular/platform-browser';
import { ActivatedRoute, Router } from '@angular/router';
class FocusClickDirective {
// Allow the button to ignore click events when set to true
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
// Allow the function passed by the host to be executed
// when the emit() method gets called
/**
* This directive replaces the default `click` directive and allows the user to execute
* the `click` event by clicking the mouse **and** by using the `enter` key on focus.
*
* A tabindex of `0` gets added to the host.
*
* @memberof FocusClickDirective
*/
focusClick = output();
// Add eventhandler to the click event
isClicked(event) {
if (!this.disabled()) {
this.focusClick.emit(event);
}
}
// Add eventhandler to keydown event When enter is pressed and the event
// isn't blocked, execute the click function of the host
isEntered() {
if (!this.disabled()) {
this.focusClick.emit();
}
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: FocusClickDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.1.2", type: FocusClickDirective, isStandalone: true, selector: "[focusClick]", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { focusClick: "focusClick" }, host: { listeners: { "click": "isClicked($event)", "keydown.enter": "isEntered()" }, properties: { "attr.tabIndex": "0" } }, ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: FocusClickDirective, decorators: [{
type: Directive,
args: [{
selector: '[focusClick]',
standalone: true,
host: {
'[attr.tabIndex]': '0',
},
}]
}], propDecorators: { isClicked: [{
type: HostListener,
args: ['click', ['$event']]
}], isEntered: [{
type: HostListener,
args: ['keydown.enter']
}] } });
const Directives = [FocusClickDirective];
/**
* A service that wraps the BroadCastChannel API and provides an Observable based implementation to the channel messages.
*
* For more information:
* https://developer.mozilla.org/en-US/docs/Web/API/BroadcastChannel
*/
class NgxBroadcastChannelService {
windowService = inject(NgxWindowService);
/**
* A record holding all the broadcast channels
*/
broadcastChannel = {};
/**
* initChannel
*
* The initChannel method initializes a new BroadcastChannel instance.
*
* @param args{ConstructorParameters<typeof BroadcastChannel>} - The arguments to pass to the BroadcastChannel constructor.
*/
initChannel(...args) {
// Iben: Only run when in browser
this.windowService.runInBrowser(() => {
const [channelName] = args;
if (!channelName) {
console.error('NgxUtils: There was an attempt to initialize a BroadcastChannel without providing a name.');
return;
}
if (!this.broadcastChannel[channelName]) {
this.broadcastChannel[channelName] = new BroadcastChannel(...args);
}
});
}
/**
* closeChannel
*
* The closeChannel method closes a selected BroadcastChannel instance.
*
* @param channelName{string} - The name of the Broadcast Channel.
*/
closeChannel(channelName) {
if (!channelName || !this.broadcastChannel[channelName]) {
return;
}
this.broadcastChannel[channelName].close();
delete this.broadcastChannel[channelName];
}
/**
* postMessage
*
* The postMessage method sends a message to a selected BroadcastChannel instance.
*
* @param channelName{string} - The name of the Broadcast Channel.
* @param message{any} - The payload to send through the channel.
*/
postMessage(channelName, message) {
if (!channelName || !this.broadcastChannel[channelName]) {
console.error('NgxUtils: There was an attempt to post a message to a channel without providing a name or the selected channel does not exist. The included message was:', message);
return;
}
this.broadcastChannel[channelName].postMessage(message);
}
/**
* selectChannelMessages
*
* The selectChannelMessages method subscribes to the `message` (bc.onmessage) event of a selected BroadcastChannel instance.
*
* @param channelName{string} - The name of the Broadcast Channel.
* @returns Observable<MessageEvent> - The message event of the channel wrapped in an observable.
*/
selectChannelMessages(channelName) {
if (!channelName || !this.broadcastChannel[channelName]) {
console.error("NgxUtils: There was an attempt to select a BroadcastChannel's messages without providing a name or the selected channel does not exist.");
return EMPTY;
}
return fromEvent(this.broadcastChannel[channelName], 'message');
}
/**
* selectChannelMessageErrors
*
* The selectChannelMessageErrors method subscribes to the `messageerror` (bc.onmessageerror) event of a selected BroadcastChannel instance.
*
* @param channelName{string} - The name of the Broadcast Channel.
* @returns Observable<MessageEvent> - The messageerror event of the channel wrapped in an observable.
*/
selectChannelMessageErrors(channelName) {
if (!channelName || !this.broadcastChannel[channelName]) {
console.error("NgxUtils: There was an attempt to select a BroadcastChannel's message errors without providing a name or the selected channel does not exist.");
return EMPTY;
}
return fromEvent(this.broadcastChannel[channelName], 'messageerror');
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: NgxBroadcastChannelService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: NgxBroadcastChannelService, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: NgxBroadcastChannelService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}] });
/**
* A service that provides a SSR-proof Observable based approach to the session- and localStorage.
*/
class NgxStorageService {
windowService = inject(NgxWindowService);
/**
* A record to hold the properties in the sessionStorage
*/
sessionStorageRecord = {};
/**
* A record to hold the properties in the localStorage
*/
localStorageRecord = {};
/**
* A subject to hold the events of the storage
*/
storageEventSubject = new Subject();
/**
* An observable that emits whenever the session- or the localStorage was updated
*/
storageEvents$ = this.storageEventSubject.asObservable();
constructor() {
const windowService = this.windowService;
// Iben: Get the initial values of the session and the local storage
windowService.runInBrowser(() => {
this.setupStorage(sessionStorage, this.sessionStorageRecord);
this.setupStorage(localStorage, this.localStorageRecord);
});
}
/**
* A localStorage implementation using observables
*/
get localStorage() {
return {
getItem: (key) => this.getItem(key, localStorage),
getItemObservable: (key) => this.getItemObservable(key, this.localStorageRecord),
removeItem: (key) => this.removeItem(key, localStorage, this.localStorageRecord, 'local'),
setItem: (key, item) => this.setItem(key, item, localStorage, this.localStorageRecord, 'local'),
clear: () => this.clearStorage(localStorage, this.localStorageRecord, 'local'),
};
}
/**
* A sessionStorage implementation using observables
*/
get sessionStorage() {
return {
getItem: (key) => this.getItem(key, sessionStorage),
getItemObservable: (key) => this.getItemObservable(key, this.sessionStorageRecord),
removeItem: (key) => this.removeItem(key, sessionStorage, this.sessionStorageRecord, 'session'),
setItem: (key, item) => this.setItem(key, item, sessionStorage, this.sessionStorageRecord, 'session'),
clear: () => this.clearStorage(sessionStorage, this.sessionStorageRecord, 'session'),
};
}
getItem(key, storage) {
return this.parseValue(storage.getItem(key));
}
/**
* Returns an observable version of the storage value
*
* @param key - The key of the storage value
* @param record - The storage record
*/
getItemObservable(key, record) {
// Iben: Return NEVER when not in browser
if (!this.windowService.isBrowser()) {
return NEVER;
}
// Iben: If the subject already exists, we return the observable
if (record[key]) {
return record[key].asObservable();
}
// Iben: If no subject exits, we create a new one
record[key] = new BehaviorSubject(undefined);
// Iben: Return the observable
return this.getItemObservable(key, record);
}
/**
* Sets an item in the storage
*
* @param key - The key of the item
* @param item - The item in the storage
* @param storage - The storage in which we want to save the item
* @param record - The corresponding storage record
*/
setItem(key, item, storage, record, type) {
// Iben: Early exit when we're in the browser
if (!this.windowService.isBrowser()) {
return undefined;
}
// Iben: Check if there's already a subject for this item. If not, we create one
let subject = record[key];
if (!subject) {
subject = new BehaviorSubject(undefined);
storage[key] = subject;
}
// Iben: Store the current value of the subject
const oldValue = subject.getValue();
// Iben: Set the item in the storage
storage.setItem(key, typeof item === 'string' ? item : JSON.stringify(item));
// Iben: Update the subject in the record
subject.next(item);
// Iben: Create the storage event
const event = {
key,
newValue: item,
oldValue,
storage: type,
type: 'set',
};
// Iben: Emit the storage event
this.storageEventSubject.next(event);
// Iben: Return the storage event
return event;
}
/**
* Remove an item from the storage and emit a remove event
*
* @param key - The key of the item
* @param storage - The storage we wish to remove the item from
* @param record - The record with the subject
* @param type - The type of storage
*/
removeItem(key, storage, record, type) {
// Iben: Early exit when we're not in the browser
if (!this.windowService.isBrowser()) {
return undefined;
}
// Iben: Get the old item
const oldValue = this.parseValue(storage.getItem(key));
// Iben: Remove the item from the storage
storage.removeItem(key);
// Iben Update the subject if it exists
record[key]?.next(undefined);
// Iben: Create the event and return and emit it
const event = {
oldValue,
storage: type,
key,
type: 'remove',
};
this.storageEventSubject.next(event);
return event;
}
/**
* Clears the storage, completes all subjects and emits a clear event
*
* @param storage - The storage we wish to clear
* @param record - The record with the subjects
* @param type - The type of storage
*/
clearStorage(storage, record, type) {
// Iben: Early exit when we're not in the browser
if (!this.windowService.isBrowser()) {
return undefined;
}
// Iben: Clear the storage
storage.clear();
// Iben: Clear the record and complete all subjects
Object.entries(record).forEach(([key, subject]) => {
subject.next(undefined);
subject.complete();
record[key] = undefined;
});
// Iben: Create and emit event
const event = {
type: 'clear',
storage: type,
};
this.storageEventSubject.next(event);
return event;
}
/**
* Grabs the existing storage and updates the record
*
* @private
* @param {Storage} storage - The current state of the storage
* @param {NgxStorageRecord} record
* @memberof NgxStorageService
*/
setupStorage(storage, record) {
Object.entries(storage).forEach(([key, value]) => {
record[key] = new BehaviorSubject(this.parseValue(value));
});
}
/**
* Parses a string value from the storage to an actual value
*
* @param value - The provided string value
*/
parseValue(value) {
// Iben: If the value does not exist, return the value
if (!value) {
return value;
}
// Iben: If the value is either true or false, return a boolean version of the value
if (value === 'true' || value === 'false') {
return value === 'true';
}
// Iben: If the value is a number, return the parsed number
if (value.match(/^[0-9]*[,.]{0,1}[0-9]*$/)) {
return Number(value);
}
// Iben: If the value is an object, return the parsed object
if (value.match(/{(.*:.*[,]{0,1})*}/)) {
return JSON.parse(value);
}
// Iben: Return the string value as is
return value;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: NgxStorageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: NgxStorageService, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: NgxStorageService, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}], ctorParameters: () => [] });
/**
* A service that can be used to track media queries and their changes. It exposes a method
* to register media queries, which takes an array of tuples with the id of the media query
* and the query itself. The service will then emit the id of the media query that has
* changed when subscribed to the `getMatchingQuery$` method.
*/
class NgxMediaQueryService {
windowService = inject(NgxWindowService);
/**
* A map of media queries that are registered with the service.
*/
queryListMap = new Map();
/**
* A map of the registered media queries with their id.
*/
queryIdMap = new Map();
/*
* A map of listeners that are registered with the service.
* They are saved to be able to remove them when the service is destroyed.
*/
mediaQueryListenerMap = new Map();
/**
* A subject that emits the id of the media query that has changed.
*/
queryChangedSubject = new ReplaySubject();
/**
* Register a list of media queries that need to be tracked by the service.
*
* @param queries - A list of media queries that should be registered with the service.
*/
registerMediaQueries(...queries) {
this.windowService.runInBrowser(({ browserWindow }) => {
for (const [id, query] of queries) {
// Wouter: Warn if the id has already been registered.
if (this.queryIdMap.get(id)) {
return console.warn(`NgxMediaQueryService: Media query with id '${id}' already exists and is defined by '${this.queryIdMap.get(id)}'`);
}
// Wouter: If the query has already been registered, throw an error to prevent duplicate subscriptions
if ([...this.queryIdMap].some(([_, value]) => value === query)) {
const duplicateQuery = [...this.queryIdMap].find(([_, value]) => value === query);
throw new Error(`NgxMediaQueryService: Query of ['${id}', ${query}] already exists and is defined by ['${duplicateQuery[0]}', ${duplicateQuery[1]}]`);
}
// Wouter: save the id and query
this.queryIdMap.set(id, query);
// Wouter: For each query, create a MediaQueryList object
const matchedQuery = browserWindow.matchMedia(query);
// Wouter: Save the query
this.queryListMap.set(id, matchedQuery);
// Wouter: Emit the id of the query that has changed
this.queryChangedSubject.next(id);
// Wouter: Create a listener for the query. This is done separately to be
// able to remove the listener when the service is destroyed
const listener = (queryChangedEvent) => {
this.queryListMap.set(id, queryChangedEvent.currentTarget);
// Wouter: Emit the id of the query that has changed
this.queryChangedSubject.next(id);
};
// Wouter: Register the listener to the query
matchedQuery.addEventListener('change', listener);
// Wouter: Save the listener
this.mediaQueryListenerMap.set(id, listener);
}
});
}
/**
* Pass the id of the query whose changes need to be listened to.
*
* @param id - The id of the media query that should be checked.
* @returns An observable that emits a boolean value whenever the requested media query changes.
*/
getMatchingQuery$(id) {
// Wouter: Throw an error if the query has not been registered
if (!this.queryIdMap.has(id)) {
throw new Error(`NgxMediaQueryService: No media query with id '${id}' has been registered. Please register the media query first using the 'registerMediaQueries' method.`);
}
return this.queryChangedSubject.asObservable().pipe(
// Wouter: Filter the query that has changed.
// This will make sure only the [id] streams are triggered.
filter((queryId) => queryId === id), map(() => this.queryListMap.get(id).matches));
}
/**
* Unregister all media query subscriptions from the service.
*/
ngOnDestroy() {
this.windowService.runInBrowser(() => {
// Wouter: Remove all eventListeners
for (const [id, query] of this.queryListMap) {
query.removeEventListener('change', this.mediaQueryListenerMap.get(id));
}
// Wouter: Complete subscriptions
this.queryChangedSubject.next(null);
this.queryChangedSubject.complete();
// Wouter: Clear maps
this.queryListMap.clear();
this.mediaQueryListenerMap.clear();
});
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: NgxMediaQueryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: NgxMediaQueryService, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: NgxMediaQueryService, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}] });
class BtwPipe {
/**
* Converts a BTW number to the correct format
*
* @param value - The value we wish to convert
*/
transform(value) {
if (!value) {
// Denis: if the value is falsy, return it without transform.
return value;
}
const addCharAtIndex = (original, char, index) => {
return original.slice(0, index) + char + original.slice(index);
};
// Iben: Convert to string if it's a number
value = value.toString();
if (value.replace(/\./g, '').length === 9) {
value = '0' + value;
}
// Iben: Format: xxxx.xxx.xxx
if (value.charAt(4) !== '.') {
value = addCharAtIndex(value, '.', 4);
}
if (value.charAt(8) !== '.') {
value = addCharAtIndex(value, '.', 8);
}
return value;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: BtwPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.1.2", ngImport: i0, type: BtwPipe, isStandalone: true, name: "btw" });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: BtwPipe, decorators: [{
type: Pipe,
args: [{
name: 'btw',
standalone: true,
}]
}] });
class HasObserversPipe {
transform(output) {
return output && output.observers.length > 0;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: HasObserversPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.1.2", ngImport: i0, type: HasObserversPipe, isStandalone: true, name: "hasObservers" });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: HasObserversPipe, decorators: [{
type: Pipe,
args: [{
name: 'hasObservers',
standalone: true,
}]
}] });
class IbanPipe {
transform(value = '') {
value = value.replace(/\s/g, ''); // replace all spaces
let reformat = value.replace(/(.{4})/g, function (match) {
return match + ' '; // reformat into groups of 4 succeeded with a space
});
reformat = reformat.trim(); // remove trailing space
return reformat;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: IbanPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.1.2", ngImport: i0, type: IbanPipe, isStandalone: true, name: "IBAN" });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: IbanPipe, decorators: [{
type: Pipe,
args: [{
name: 'IBAN',
standalone: true,
}]
}] });
/** The configuration token for the NgxReplaceElementsPipe */
const NgxReplaceElementsConfigurationToken = new InjectionToken('NgxReplaceElementsConfigurationToken');
/**
* A pipe that allows to replace text elements with a WebComponent
*/
class NgxReplaceElementsPipe {
configuration = inject(NgxReplaceElementsConfigurationToken);
sanitizer = inject(DomSanitizer);
/**
* Replaces all matches of a specific selector with provided WebComponents
*
* @param value - The original string value
* @param items - The items we wish to replace
*/
transform(value, items) {
// Iben: If the value isn't a string we early exit and warn the user
if (typeof value !== 'string') {
console.warn('NgxReplaceElements: A non string-value was provided to the NgxReplaceElementsPipe');
return '';
}
// Iben: If no items were provided to replace, we just return the value
if (!items || items.length === 0) {
return value;
}
// Iben: set up a new instance of the DOMParser and parse the value as text/html.
// This will return a Document which we can work with to find/replace elements.
const parser = new DOMParser();
const body = parser.parseFromString(value, 'text/html');
// Iben: Loop over all items we wish to replace
items.forEach((item) => {
// Iben: Get the selector and the element we want to replace the target with
const { selector, element, includeInnerHtml } = this.configuration[item.elementId];
// Iben: Select the target
const target = body.querySelector(selector.replace('{{id}}', item.id));
// Iben: If no target was found, early exit
if (!target) {
return;
}
// Iben: Create a new element within the Document based on the provided selector.
// The selector can be any native or custom web component (not an Angular component).
// Keep in mind that the element will need to have a lowercase input prop for the reference.
const replacement = body.createElement(element);
// Iben: If the item included data, we set these attributes
if (item.data) {
Object.entries(item.data).forEach(([key, value]) => {
replacement.setAttribute(key, value);
});
}
// Iben: Copy the innerHtml of the target element to the new element if needed.
if (includeInnerHtml) {
replacement.innerHTML = target.innerHTML;
}
// Iben: Replace the target with the new element within the Document.
target.replaceWith(replacement);
});
// Iben: sanitize the document and mark it as trusted HTML before returning it to the template.
return this.sanitizer.bypassSecurityTrustHtml(body.documentElement.innerHTML);
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: NgxReplaceElementsPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.1.2", ngImport: i0, type: NgxReplaceElementsPipe, isStandalone: true, name: "ngxReplaceElements" });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: NgxReplaceElementsPipe, decorators: [{
type: Pipe,
args: [{
name: 'ngxReplaceElements',
standalone: true,
}]
}] });
/**
* A pipe to pass a transformer function to. By using this setup, we can use functions without causing rerender issues
*/
class TransformPipe {
/**
* Transforms a value based on a provided transform function
*
* @param value - The provided value we wish to transform
* @param transformer - A provided transform function
*/
transform(value, transformer) {
// Iben: If no transformer is passed, we return the original value
if (!transformer) {
return value;
}
// Iben: Transform the value and return
return transformer(value);
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: TransformPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.1.2", ngImport: i0, type: TransformPipe, isStandalone: true, name: "transform" });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: TransformPipe, decorators: [{
type: Pipe,
args: [{
name: 'transform',
standalone: true,
}]
}] });
const Pipes = [BtwPipe, HasObserversPipe, IbanPipe, TransformPipe, NgxReplaceElementsPipe];
class NgxQueryParamFormSyncComponent {
route = inject(ActivatedRoute);
router = inject(Router);
destroyed$ = new Subject();
/**
* The form in which we will save the queryParam data
*/
form;
/**
* The query params we wish to form
*/
queryParams$ = this.route
.queryParams;
ngOnInit() {
//Iben: Warn the user if one of the two methods isn't provided
if ((!this.scrambleParams && this.unscrambleParams) ||
(this.scrambleParams && !this.unscrambleParams)) {
console.error(`NgxUtils: NgxQueryParamFormSyncComponent detected the use of the parameter scrambling but is missing an implementation for the ${this.scrambleParams ? 'unscrambleParams' : 'scrambleParams'} method. Please provide this method in order for this flow to work correctly.`);
}
// Iben: Setup the form for the data
this.form = this.initForm();
// Iben: Listen to the form changes
this.form.valueChanges
.pipe(tap((data) => {
// Iben: Update the route params
this.setDataInRoute(data);
// Iben: Handle the route data changes
if (this.handleDataChanges) {
this.handleDataChanges(data);
}
}), takeUntil(this.destroyed$))
.subscribe();
// Iben: Listen to the initial query param update so we can set the data in the form if we navigate to a link with the params
this.queryParams$
.pipe(take(1), filter(Boolean), tap((data) => {
// Iben: Convert the route data properties to the actual data
let value = Object.keys(data || {}).reduce((previous, current) => {
return {
...previous,
[current]: data[current] ? JSON.parse(data[current]) : undefined,
};
}, {});
// Iben: In case the unscrambleParams method is provided, we unscramble the data
if (this.unscrambleParams) {
value = this.unscrambleParams(value);
}
//Iben: If the entire object is empty, we early exit and do not set the form
if (Object.keys(value).length === 0) {
return;
}
// Iben: Set the current form value
this.form.setValue(value);
}), takeUntil(this.destroyed$))
.subscribe();
}
ngOnDestroy() {
this.destroyed$.next();
this.destroyed$.complete();
this.clearData();
}
/**
* Clears the data in the form
*/
clearData() {
this.form.reset();
}
/**
* Sets the provided data in the route, so the filtered view can be shared by url
*
* @param data - The provided data
*/
setDataInRoute(data) {
// Iben: If no data was provided, we simply unset the current params
if (Object.keys(data || {}).length === 0) {
this.router.navigate([], {
relativeTo: this.route,
queryParams: {},
});
return;
}
// Iben: In case a scrambleParams function was provided, we scramble the params first
const parsedData = this.scrambleParams ? this.scrambleParams(data) : data;
// Iben: Stringify all properties of the data
const queryParams = Object.keys(parsedData || {}).reduce((previous, current) => {
return {
...previous,
[current]: JSON.stringify(parsedData[current]),
};
}, {});
// Iben: Add the queryParams to the route
this.router.navigate([], {
relativeTo: this.route,
queryParamsHandling: 'merge',
queryParams,
});
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: NgxQueryParamFormSyncComponent, deps: [], target: i0.ɵɵFactoryTarget.Directive });
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.1.2", type: NgxQueryParamFormSyncComponent, isStandalone: true, ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: NgxQueryParamFormSyncComponent, decorators: [{
type: Directive
}] });
/**
* Provides the configuration for the NgxReplaceElements directive
*
* @param configuration - The required configuration
*/
const provideNgxReplaceElementsConfiguration = (configuration) => {
return {
provide: NgxReplaceElementsConfigurationToken,
useValue: configuration,
};
};
/**
* Method checks if given value has changed
* @param {SimpleChange} value: value to check
* @returns {boolean} whether or not if the given input has changed
*/
const simpleChangeHasChanged = (value) => {
return value && JSON.stringify(value.previousValue) !== JSON.stringify(value.currentValue);
};
/*
* Public API Surface of utils
*/
/**
* Generated bundle index. Do not edit.
*/
export { BtwPipe, Directives, FocusClickDirective, HasObserversPipe, IbanPipe, NgxBroadcastChannelService, NgxMediaQueryService, NgxQueryParamFormSyncComponent, NgxReplaceElementsConfigurationToken, NgxReplaceElementsPipe, NgxStorageService, Pipes, TransformPipe, provideNgxReplaceElementsConfiguration, simpleChangeHasChanged };
//# sourceMappingURL=ibenvandeveire-ngx-utils.mjs.map