query-params-store
Version:
[](https://travis-ci.org/IliaIdakiev/query-param-store)
647 lines (639 loc) • 37 kB
JavaScript
import * as i0 from '@angular/core';
import { InjectionToken, Injectable, Inject, NgModule } from '@angular/core';
import * as i1 from '@angular/router';
import { ResolveStart, NavigationEnd, ActivationEnd, NavigationStart, RoutesRecognized, NavigationCancel, Router } from '@angular/router';
import { ReplaySubject, of, defer, Observable } from 'rxjs';
import { pairwise, filter, switchMap, first, mapTo, map, tap, distinctUntilChanged, publishReplay, withLatestFrom } from 'rxjs/operators';
import { compressToEncodedURIComponent, decompressFromEncodedURIComponent } from 'lz-string';
const QPS_CONFIG = new InjectionToken('QPS_CONFIG');
function binaryToNumber(...args) {
if (args.length === 1 && Array.isArray(args[0])) {
args = args[0];
}
return args.reduce((acc, curr, index) => {
if (!curr) {
return acc;
}
return acc + Math.pow(2, index);
}, 0);
}
function compress(queryParams) {
return compressToEncodedURIComponent(JSON.stringify(queryParams));
}
function decompress(compression) {
try {
return JSON.parse(decompressFromEncodedURIComponent(compression));
}
catch {
return null;
}
}
const NOT_PRESENT = Symbol('NOT_PRESENT');
function booleanTypeConverter(val) {
if (['true', 1, '1', true].includes(val)) {
return true;
}
if (['false', 0, '0', false].includes(val)) {
return false;
}
return Boolean(val);
}
class QueryParamsStore {
constructor(router, config) {
this.router = router;
this.snapshot = new ReplaySubject(2);
this.isInDebugMode = false;
this.useCompression = false;
this.compressionKey = null;
this.snapshot.next(null);
this.isInDebugMode = !!config && !!config.debug;
this.useCompression = !!config && !!config.useCompression;
this.compressionKey = !!config && config.compressionKey || null;
}
getConvertBinaryBooleanResult(value, stateConfigLength) {
value = +value;
if (isNaN(value)) {
return { isConvertedCorrectly: false };
}
const binaryBooleanStringArray = value.toString(2).split('');
const isOverflowing = binaryBooleanStringArray.length > stateConfigLength;
let binaryBooleanResult = binaryBooleanStringArray.slice(0, stateConfigLength).map((val) => `${val === '1'}`).reverse();
if (binaryBooleanResult.length < stateConfigLength) {
binaryBooleanResult = binaryBooleanResult.concat(new Array(stateConfigLength - binaryBooleanResult.length).fill('false'));
}
const booleanResult = binaryBooleanResult.map(v => this.getConvertValueResult(v, Boolean));
return { binaryBooleanResult: booleanResult, isConvertedCorrectly: true, isOverflowing };
}
getConvertValueResult(value, typeConvertor) {
const isTypeConverterBoolean = typeConvertor === Boolean || typeConvertor === booleanTypeConverter;
const isBoolean = ['true', 'false'].includes(value) && isTypeConverterBoolean;
const convertedValue = typeConvertor === String && typeof value === 'string' ? value :
typeConvertor(value === 'true' ? 1 : value === 'false' ? 0 : value);
const isValidNumber = typeConvertor === Number && !Number.isNaN(convertedValue);
const isValidString = typeConvertor === String && typeof convertedValue === 'string';
const isValidBoolean = isTypeConverterBoolean && typeof convertedValue === 'boolean';
const hasValidConversion = isValidNumber || isValidString || isValidBoolean;
return {
hasValidConversion,
convertedValue,
invalidRawConversion: (isTypeConverterBoolean && !isBoolean || typeConvertor === Number && isNaN(typeConvertor(value)))
};
}
getTypeConverter(stateConfig) {
if (stateConfig.typeConvertor) {
return stateConfig.typeConvertor === Boolean ? booleanTypeConverter : stateConfig.typeConvertor;
}
const stateConfigValueType = typeof (stateConfig.value ? stateConfig.value : stateConfig);
if (stateConfigValueType === 'number') {
return Number;
}
if (stateConfigValueType === 'boolean') {
return booleanTypeConverter;
}
return String;
}
parseStateConfig(config) {
return Object.entries(config).reduce((acc, [key, configForKey]) => {
if (configForKey === null || Array.isArray(configForKey)) {
// remove invalid configuration
if (this.isInDebugMode) {
console.warn(`Query Params Store: Invalid configuration for parameter key: ${key}! (nulls or arrays are not supported)`);
}
return acc;
}
const typeConvertor = this.getTypeConverter(configForKey);
const isObjectConfig = typeof configForKey === 'object';
if (!isObjectConfig) {
// the case when the property is just a value (page: 1 or filter: 'something')
acc[key] = { value: configForKey, originalValue: configForKey, typeConvertor, multi: false };
return acc;
}
if (!configForKey.hasOwnProperty('value')) {
// missing the value property => remove invalid configuration
if (this.isInDebugMode) {
console.warn(`Query Params Store: Invalid configuration for parameter key ${key}! (value is missing)`);
}
return acc;
}
let value = configForKey.value;
const originalValue = value;
if (!['number', 'string', 'boolean'].includes(typeof value) && value !== null) {
// value is not in the supported type => remove invalid configuration
if (this.isInDebugMode) {
// tslint:disable-next-line:max-line-length
console.warn(`Query Params Store: Invalid value configuration for parameter key ${key}! (value can be number, string, boolean or null)`);
}
return acc;
}
const useCompression = configForKey.useCompression || false;
const isMultiValueConfig = configForKey.multi || false;
const multiCount = isMultiValueConfig ? configForKey.count : null;
const isBinaryBoolean = this.isBinaryBoolean({ value, typeConvertor, multi: isMultiValueConfig });
if (isBinaryBoolean) {
const configForKeyLength = !configForKey.hasOwnProperty('length') ? undefined : +configForKey.length;
value = value.toString(2).split('').slice(0, configForKeyLength).map((val) => `${val === '1'}`).reverse();
if (value.length < (configForKeyLength || 0)) {
value = value.concat(new Array(configForKeyLength - value.length).fill('false'));
}
}
else if (isMultiValueConfig && value !== null) {
if (typeof value !== 'string' && !isBinaryBoolean) {
// value should be a string and if we need multiple values we should use the given separator
// tslint:disable-next-line:max-line-length
if (this.isInDebugMode) {
console.warn(`Query Params Store: Invalid configuration for parameter key ${key}! (value should be a string)`);
}
return acc;
}
value = isBinaryBoolean ?
value : value === '' ?
[] : value.split(configForKey.separator || ';');
const difference = (!multiCount || isBinaryBoolean) ? 0 : +multiCount - value.length;
if (difference > 0) {
for (let i = 0; i < difference; i++) {
value.push(typeConvertor === String ? '' : typeConvertor === Number ? 0 : false);
}
}
}
if (Array.isArray(value)) {
value = value.map(typeConvertor);
}
acc[key] = {
...configForKey,
typeConvertor,
originalValue,
value,
multi: isMultiValueConfig,
multiCount,
useCompression,
separator: configForKey.separator || ';'
};
return acc;
}, {});
}
isBinaryBoolean(cfg) {
return cfg.multi && typeof cfg.value === 'number' &&
(cfg.typeConvertor === Boolean || cfg.typeConvertor === booleanTypeConverter);
}
getSnapshotData(snapshot) {
const defaultStoreConfig = { stateConfig: {}, noQueryParams: false, removeUnknown: false };
let storeConfig = snapshot.data && snapshot.data['storeConfig'] || defaultStoreConfig;
const inherit = storeConfig.inherit || false;
if (inherit) {
// iterate over all route data configs and merge the store configs (priority is from the bottom up)
storeConfig = snapshot.pathFromRoot.slice(0, -1).reduceRight((acc, curr) => {
const currentData = curr.data;
if (!currentData || !currentData['storeConfig']) {
return acc;
}
const { stateConfig: accumulatedStateConfig, ...accumulatedStoreConfigOptions } = acc;
const { stateConfig: currentStateConfig, ...currentStoreConfigOptions } = currentData['storeConfig'];
if (this.isInDebugMode) {
const accumulatedStateKeys = Object.keys(accumulatedStateConfig || {});
const currentStateKeys = Object.keys(currentStateConfig || {});
const accumulatedStoreKeys = Object.keys(accumulatedStoreConfigOptions);
const currentStoreKeys = Object.keys(currentStoreConfigOptions);
const storeKeysIntersection = accumulatedStoreKeys.filter(x => currentStoreKeys.includes(x));
const stateKeysIntersection = accumulatedStateKeys.filter(x => currentStateKeys.includes(x));
// tslint:disable-next-line:max-line-length
if (storeKeysIntersection.length > 0) {
console.warn(`Query Params Store: parameter keys ${storeKeysIntersection} were overridden`);
}
// tslint:disable-next-line:max-line-length
if (stateKeysIntersection.length > 0) {
console.warn(`Query Params Store: parameter keys ${stateKeysIntersection} were overridden`);
}
}
return {
...currentStoreConfigOptions,
...accumulatedStoreConfigOptions,
stateConfig: { ...currentStateConfig, ...accumulatedStateConfig }
};
}, storeConfig);
}
const noQueryParams = storeConfig.noQueryParams || false;
let snapshotQueryParams = snapshot.queryParams;
if (this.useCompression) {
const key = this.compressionKey || 'q';
const compressedString = key === '#' ? snapshot.fragment : snapshotQueryParams[key];
if (compressedString) {
const decompressedString = decompressFromEncodedURIComponent(compressedString);
try {
snapshotQueryParams = JSON.parse(decompressedString);
}
catch {
if (this.isInDebugMode) {
console.warn(`Query Params Store: Can't decompress provided value!`);
}
snapshotQueryParams = {};
}
}
}
const stateConfig = this.parseStateConfig(storeConfig.stateConfig || {});
const removeUnknown = storeConfig.removeUnknown || false;
const caseSensitive = storeConfig.hasOwnProperty('caseSensitive') ? storeConfig.caseSensitive : true;
const allQueryParamsNames = Array.from(new Set(Object.keys(snapshotQueryParams).concat(Object.keys(stateConfig))));
const allQueryParams = allQueryParamsNames.reduce((acc, queryParamName) => {
acc[queryParamName] = snapshotQueryParams[queryParamName] || NOT_PRESENT;
return acc;
}, {});
return { noQueryParams, storeConfig, removeUnknown, caseSensitive, allQueryParams, stateConfig };
}
constructStore(snpsht) {
const stream$ = (snpsht ? of(null, snpsht) : this.snapshot).pipe(pairwise(), filter(val => !this.redirectUrl || !!val[1]), switchMap(s => snpsht ? [s] : this.router.events.pipe(
// emit the store if we are in the resolve phase (the guard checks passed) or catch the case
// if we are just changing a query paramter in an already resolved route (watch for the NavigationEnd)
first(e => e instanceof ResolveStart || e instanceof NavigationEnd), mapTo(s))), map(([prevSnapshot, snapshot]) => {
if (!snapshot?.data) {
return {};
}
const { stateConfig, noQueryParams, caseSensitive, removeUnknown, allQueryParams } = this.getSnapshotData(snapshot);
if (noQueryParams && Object.keys(snapshot.queryParams).length !== 0) {
this.router.navigateByUrl(this.url);
return null;
}
const queryParamsResult = Object.entries(allQueryParams).reduce((queryParams, [paramKey, paramValue]) => {
// if we have caseSensitive: false we have to skip the NOT_PRESENT set when allQueryParams is generated
if (paramValue === NOT_PRESENT && queryParams[paramKey]) {
return queryParams;
}
let stateConfigForParam = stateConfig[paramKey];
paramValue = paramValue === NOT_PRESENT ? NOT_PRESENT : decodeURIComponent(paramValue);
if (!stateConfigForParam && caseSensitive === false) {
const stateConfigKeys = Object.keys(stateConfig);
paramKey = stateConfigKeys.find(objKey => paramKey.toLowerCase() === objKey.toLowerCase());
stateConfigForParam = stateConfig[paramKey];
}
if (removeUnknown && !stateConfigForParam) {
if (this.isInDebugMode) {
console.warn(`Query Params Store: removing parameter key: ${paramKey} because it's unknown!`);
}
return { ...queryParams, [paramKey]: { storeValue: null, urlValue: null } };
}
else if (!stateConfigForParam) {
return { ...queryParams, [paramKey]: { urlValue: paramValue, storeValue: paramValue } };
}
const hasAllowedValuesProperty = stateConfigForParam.hasOwnProperty('allowedValues');
const isAllowedValuesAnArray = Array.isArray(stateConfigForParam.allowedValues);
const allowedValues = hasAllowedValuesProperty && isAllowedValuesAnArray ?
stateConfigForParam.allowedValues : null;
if (hasAllowedValuesProperty && !isAllowedValuesAnArray && this.isInDebugMode) {
console.warn(`Query Params Store: ignoring allowed values for parameter key: ${paramKey} since it's not an array!`);
}
const typeConvertor = stateConfigForParam.typeConvertor;
// const value = paramValue !== NOT_PRESENT ? +paramValue : NaN;
const isBinaryBoolean = this.isBinaryBoolean({
value: stateConfigForParam.originalValue,
multi: stateConfigForParam.multi,
typeConvertor
});
const stateConfigLength = stateConfigForParam.length;
const paramConfigValue = stateConfigForParam.hasOwnProperty('value') ? stateConfigForParam.value : stateConfigForParam;
if (paramValue === NOT_PRESENT) {
return { ...queryParams, [paramKey]: { urlValue: NOT_PRESENT, storeValue: paramConfigValue } };
}
// Binary Boolean Configuration Case
if (isBinaryBoolean) {
if (this.isInDebugMode && allowedValues) {
console.warn(`Query Params Store: Allowed values can't be used with binary boolean values!`);
}
const removeInvalid = stateConfigForParam.removeInvalid;
const isFalseAsString = paramValue === 'false';
const isTrueAsString = paramValue === 'true';
const isBooleanAsString = isFalseAsString || isTrueAsString;
paramValue = isFalseAsString ? 0 : isTrueAsString ? 1 : paramValue;
const binaryBooleanConversionResult = this.getConvertBinaryBooleanResult(paramValue, stateConfigLength);
const { binaryBooleanResult, isConvertedCorrectly, isOverflowing } = binaryBooleanConversionResult;
if (!isConvertedCorrectly || (isOverflowing && removeInvalid)) {
if (this.isInDebugMode) {
if (isOverflowing) {
// tslint:disable-next-line:max-line-length
console.warn(`Query Params Store: removing parameter key: ${paramKey} since ${paramValue} converted to boolean ${binaryBooleanResult.map(r => r.convertedValue).join(',')} is longer than the configured length of ${stateConfigLength} and the property removeInvalid is set to true!`);
}
else {
// tslint:disable-next-line:max-line-length
console.warn(`Query Params Store: Config value ${paramValue} for parameter key ${paramKey} can't be converted correctly so its replaced with the one from the configuration!`);
}
}
const defaultValue = { storeValue: paramConfigValue, urlValue: null };
return { ...queryParams, [paramKey]: defaultValue };
}
return {
...queryParams,
[paramKey]: {
storeValue: binaryBooleanResult?.map(r => r.convertedValue),
urlValue: paramValue,
isNewUrlValue: isBooleanAsString
}
};
}
// const isMultiToMultiParamTransition = !!(stateConfigForParam.multi && prevSnapshot && prevSnapshot.data
// && prevSnapshot.data.storeConfig && prevSnapshot.data.storeConfig.stateConfig &&
// prevSnapshot.data.storeConfig.stateConfig[paramKey] && prevSnapshot.data.storeConfig.stateConfig[paramKey].multi);
// Multi Value Configuration Case
if (stateConfigForParam.multi) {
// const isSingleToMultiParamTransition = !!(prevSnapshot && prevSnapshot.data
// && prevSnapshot.data.storeConfig && prevSnapshot.data.storeConfig.stateConfig &&
// prevSnapshot.data.storeConfig.stateConfig[paramKey] && !prevSnapshot.data.storeConfig.stateConfig[paramKey].multi);
let conversionResults = paramValue.split(stateConfigForParam.separator)
.map((v) => this.getConvertValueResult(v, typeConvertor));
const multiCount = typeof stateConfigForParam.multiCount === 'number' ?
stateConfigForParam.multiCount : paramConfigValue && typeof paramConfigValue.length === 'number' ?
paramConfigValue.length : conversionResults.length || 0;
const difference = isBinaryBoolean ? 0 : +multiCount - conversionResults.length;
if (difference > 0) {
for (let i = 0; i < difference; i++) {
// if we have difference in the lengths of the value of the parameter then we either use the default value
// provided in the config for the selected index or just use a default one
const conversionResultsIndex = conversionResults.length + i;
const hasDefaultValueArrayIndex = paramConfigValue.length > conversionResultsIndex;
conversionResults.push({
hasValidConversion: true, convertedValue: hasDefaultValueArrayIndex ? paramConfigValue[conversionResultsIndex] :
typeConvertor === String ? '' : typeConvertor === Number ? 0 : false,
invalidRawConversion: false
});
}
}
else if (difference < 0) {
conversionResults = conversionResults.slice(0, difference);
}
const checkResult = conversionResults.map(v => {
if (!v.hasValidConversion) {
return 'invalid';
}
if (allowedValues && !allowedValues.includes(v.convertedValue)) {
return 'not-allowed';
}
return null;
});
const hasInvalidValue = checkResult.includes('invalid');
const hasNotAllowedValue = checkResult.includes('not-allowed');
if (hasInvalidValue || hasNotAllowedValue) {
if (this.isInDebugMode) {
if (hasInvalidValue) {
// tslint:disable-next-line:max-line-length
console.warn(`Query Params Store: parameter key ${paramKey} with value: ${paramValue} can't be converted correctly!`);
}
else {
// tslint:disable-next-line:max-line-length
console.warn(`Query Params Store: parameter key ${paramKey} with value: ${paramValue} is not found in the allowed values ${allowedValues}!`);
}
}
const defaultValue = { storeValue: paramConfigValue, urlValue: null };
return { ...queryParams, [paramKey]: defaultValue };
}
const invalidURLValue = !!conversionResults.find(r => r.invalidRawConversion);
const storeValue = conversionResults.map(r => r.convertedValue);
// here we also provide the difference so if it's > 0 we can correctly redirect bellow
return { ...queryParams, [paramKey]: { urlValue: paramValue, storeValue, difference, invalidURLValue } };
}
// Default (string, number, boolean) Value Configuration Case
{
// when using strings hasValidConversion can be true because '10;10' is a valid string so we need to know if we are having
// a multi to single transition in order to be able to redirect the the correct url
const isMultiToSingleParamTransition = !!(!stateConfigForParam.multi && prevSnapshot && prevSnapshot.data
&& prevSnapshot.data['storeConfig'] && prevSnapshot.data['storeConfig'].stateConfig &&
prevSnapshot.data['storeConfig'].stateConfig[paramKey] && prevSnapshot.data['storeConfig'].stateConfig[paramKey].multi);
let isValidMultiToSingle = false;
let invalidURLValue = false;
let conversionResult = this.getConvertValueResult(paramValue, typeConvertor);
if ((!conversionResult.hasValidConversion && prevSnapshot && prevSnapshot.data) || isMultiToSingleParamTransition) {
const { stateConfig: prevStateConfig, allQueryParams: allPrevQueryParams } = this.getSnapshotData(prevSnapshot);
const prevStateConfigForKey = prevStateConfig[paramKey];
if (prevStateConfigForKey && prevStateConfigForKey.multi) {
paramValue = allPrevQueryParams[paramKey] ?
allPrevQueryParams[paramKey].split(prevStateConfig[paramKey].separator)[0] :
prevStateConfig[paramKey].value[0];
conversionResult = this.getConvertValueResult(paramValue, typeConvertor);
isValidMultiToSingle = conversionResult.hasValidConversion;
invalidURLValue = conversionResult.invalidRawConversion;
}
}
const isAllowed = allowedValues ? allowedValues.includes(conversionResult.convertedValue) : true;
if (!conversionResult.hasValidConversion || !isAllowed) {
if (this.isInDebugMode) {
if (!isAllowed) {
// tslint:disable-next-line:max-line-length
console.warn(`Query Params Store: parameter key: ${paramKey} with value: ${paramValue} is not found in the allowed values ${allowedValues}!`);
}
else {
console.warn(`Query Params Store: parameter key: ${paramKey} with value: ${paramValue} can't be converted correctly!`);
}
}
const defaultValue = { storeValue: paramConfigValue, urlValue: null };
return { ...queryParams, [paramKey]: defaultValue };
}
const storeValue = conversionResult.convertedValue;
// if isValidMultiToSingle is true then we need to set isNewUrlValue to true so we can change the URL bellow
return {
...queryParams, [paramKey]: {
urlValue: paramValue,
storeValue,
isNewUrlValue: isValidMultiToSingle,
invalidURLValue
}
};
}
}, {});
// Redirect if needed and send the new store
{
const mustChangeUrl = !!Object.values(queryParamsResult).find((v) => v.urlValue === null || v.isNewUrlValue || v.difference || v.invalidURLValue);
if (mustChangeUrl) {
const queryParamsTupleArray = Object.entries(queryParamsResult).reduce((acc, [key, { urlValue, difference, storeValue, isNewUrlValue, invalidURLValue }]) => {
if (isNewUrlValue || invalidURLValue || ![null, NOT_PRESENT].includes(urlValue)) {
const currentConfig = stateConfig[key];
if (!currentConfig) {
return acc;
}
const separator = currentConfig.separator;
if (invalidURLValue) {
urlValue = currentConfig.multi ? storeValue.join(separator) : storeValue;
difference = 0;
}
if (typeof difference === 'number' && difference !== 0) {
const currentUrlValueArray = urlValue.split(separator);
if (difference > 0) {
for (let i = 0; i < difference; i++) {
urlValue = `${urlValue}${separator}${storeValue[currentUrlValueArray.length + i]}`;
}
}
else {
urlValue = `${currentUrlValueArray.slice(0, difference).join(separator)}`;
}
}
return acc.concat([[key, urlValue]]);
}
return acc;
}, []);
let redirectUrl = null;
if (this.useCompression) {
const queryParamsObject = queryParamsTupleArray.reduce((acc, [key, value]) => {
acc[key] = value;
return acc;
}, {});
const compressedQueryParams = compress(queryParamsObject);
const compressionKey = this.compressionKey || 'q';
const anchorCompressionKey = compressionKey === '#';
redirectUrl = `${anchorCompressionKey ? this.url.split('#')[0] : this.url}${queryParamsTupleArray.length > 0 ?
anchorCompressionKey ? `#${compressedQueryParams}` : `?${compressionKey}=${compressedQueryParams}` : ''}`;
}
else {
const queryParamsArray = queryParamsTupleArray.reduce((acc, [key, value]) => acc.concat(`${key}=${value}`), []);
redirectUrl = `${this.url}${queryParamsTupleArray.length > 0 ? '?' + queryParamsArray.join('&') : ''}`;
}
this.router.navigateByUrl(redirectUrl);
this.redirectUrl = redirectUrl;
return undefined;
}
const store = Object.entries(queryParamsResult).reduce((acc, [key, data]) => {
acc[key] = data.storeValue;
return acc;
}, {});
return store;
}
})).pipe(filter(val => !!val));
return stream$;
}
_constructHandler() {
if (this.subscription) {
return;
}
this.subscription = this.router.events.pipe(filter(event => !(event instanceof ActivationEnd)), tap(event => {
if (event instanceof NavigationStart) {
this.prevFullUrl = this.fullUrl || '';
this.fullUrl = event.url;
this.url = decodeURIComponent(/[^?]+/.exec(event.url)[0]);
}
else if (event instanceof RoutesRecognized) {
this.url = decodeURIComponent(/[^?]+/.exec(event.urlAfterRedirects)[0]);
}
else if (event instanceof NavigationCancel) {
this.fullUrl = this.prevFullUrl;
}
else if (event instanceof NavigationEnd) {
this.redirectUrl = null;
}
}), filter(event => !!event.snapshot), map(event => {
let currSnapshot = event.snapshot;
while (currSnapshot && currSnapshot.children.length !== 0) {
currSnapshot = currSnapshot.children.find((childSnapshot) => {
return this.url === '/' && childSnapshot.url.length === 0
|| !!childSnapshot.url.find((segment) => this.url.includes(segment.path));
});
}
return currSnapshot;
}), filter(val => !!val), distinctUntilChanged()).subscribe(this.snapshot);
this.store = this.constructStore().pipe(publishReplay(1));
this.store.connect();
}
select(selector, disableDistinctUntilChanged = false, compare) {
return defer(() => {
const fn = typeof selector === 'function' ? selector : (state) => state[selector];
let select = this.store.pipe(map(fn));
if (!disableDistinctUntilChanged) {
select = select.pipe(distinctUntilChanged(compare));
}
return select;
});
}
first(selector, predicate, defaultValue) {
selector = selector || (s => s);
return this.select(selector).pipe(first(predicate, defaultValue));
}
_match(snapshot, allowedValues, navigateTo, isDeactivate = false) {
if (!(allowedValues instanceof Observable)) {
allowedValues = of(allowedValues);
}
if (navigateTo === this.fullUrl) {
throw new Error('Navigating to the same route will result into infinite loop!');
}
return this.constructStore(snapshot).pipe(withLatestFrom(allowedValues), map(([queryParams, allowedValuesObj]) => {
let successfulMatch = true;
let redirectQueryParams = {};
for (const [name, value] of Object.entries(queryParams)) {
const config = allowedValuesObj[name];
if (!config) {
continue;
}
const { match, default: defaultValue } = config;
const isCurrentMatch = (Array.isArray(match) ? match.includes(value) : value === match);
if (!isCurrentMatch && defaultValue) {
redirectQueryParams = { ...redirectQueryParams, [name]: defaultValue };
}
successfulMatch = successfulMatch && isCurrentMatch;
}
if (!successfulMatch && typeof navigateTo === 'string') {
if (navigateTo !== this.prevFullUrl) {
this.router.navigateByUrl(navigateTo);
}
}
return successfulMatch;
}));
}
canActivate(allowedValues, currentSnapshot) {
return this._match(currentSnapshot, allowedValues, this.prevFullUrl).pipe(first());
}
canDeactivate(allowedValues, currentSnapshot, routerSnapshot) {
return this._match(currentSnapshot, allowedValues, routerSnapshot.url, true).pipe(first());
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
QueryParamsStore.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.8", ngImport: i0, type: QueryParamsStore, deps: [{ token: i1.Router }, { token: QPS_CONFIG }], target: i0.ɵɵFactoryTarget.Injectable });
QueryParamsStore.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.2.8", ngImport: i0, type: QueryParamsStore });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.8", ngImport: i0, type: QueryParamsStore, decorators: [{
type: Injectable
}], ctorParameters: function () { return [{ type: i1.Router }, { type: undefined, decorators: [{
type: Inject,
args: [QPS_CONFIG]
}] }]; } });
function qpsFactory(router, config) {
const instance = new QueryParamsStore(router, config);
return instance;
}
const serviceProvider = {
provide: QueryParamsStore,
useFactory: qpsFactory,
deps: [Router, QPS_CONFIG],
};
class QueryParamsStoreModule {
constructor(qps) {
qps._constructHandler();
}
static withConfig(config) {
return {
ngModule: QueryParamsStoreModule,
providers: [
{ provide: QPS_CONFIG, useValue: config },
serviceProvider
]
};
}
}
QueryParamsStoreModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.8", ngImport: i0, type: QueryParamsStoreModule, deps: [{ token: QueryParamsStore }], target: i0.ɵɵFactoryTarget.NgModule });
QueryParamsStoreModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.2.8", ngImport: i0, type: QueryParamsStoreModule });
QueryParamsStoreModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.2.8", ngImport: i0, type: QueryParamsStoreModule, providers: [
{ provide: QPS_CONFIG, useValue: null },
serviceProvider
] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.8", ngImport: i0, type: QueryParamsStoreModule, decorators: [{
type: NgModule,
args: [{
declarations: [],
providers: [
{ provide: QPS_CONFIG, useValue: null },
serviceProvider
],
imports: [],
exports: []
}]
}], ctorParameters: function () { return [{ type: QueryParamsStore }]; } });
/*
* Public API Surface of query-params-store
*/
/**
* Generated bundle index. Do not edit.
*/
export { QueryParamsStore, QueryParamsStoreModule, binaryToNumber, compress, decompress, qpsFactory, serviceProvider };
//# sourceMappingURL=query-params-store.mjs.map