Merged PR 1837: Fix - Filter Reset und Filter Sync - Removed unused code, logger performance

refactor: improve code formatting and readability in provide-filter.ts and filter-menu components

fix: delay filter rollback on close in FilterMenuButtonComponent

fix: update filter clear button text and method calls in filter-menu.component.html

chore: update package-lock.json to remove unnecessary dev flags and add new dependencies

Ref: #5125, #5076
This commit is contained in:
Lorenz Hilpert
2025-05-26 15:02:43 +00:00
committed by Nino Righi
parent c322020c3f
commit 0d202ab97c
17 changed files with 1050 additions and 263 deletions

View File

@@ -1,6 +1,258 @@
# shared-filter
# ISA Filter Library
This library was generated with [Nx](https://nx.dev).
A powerful and flexible filtering library for Angular applications that provides a complete solution for implementing filters, search functionality, and sorting capabilities.
## Overview
The ISA Filter Library is designed to help developers quickly implement advanced filtering capabilities in their Angular applications. It provides a set of reusable components, schemas, and services to handle various types of filters, user inputs, and sorting options.
## Features
- **Multiple Filter Types**: Support for text search, checkboxes, date ranges, and more
- **Flexible Architecture**: Easily extendable to support custom filter types
- **Modern Angular Patterns**: Built with Angular signals for reactive state management
- **Schema-based Validation**: Type-safe filter models with zod schema validation
- **Responsive Design**: Mobile-friendly filter UI components
- **Declarative API**: Simple and intuitive API for configuring filters
- **Sorting Capabilities**: Built-in support for sorting data by various criteria
- **TypeScript Support**: Fully typed for excellent developer experience
## Installation
The library is part of the ISA Frontend monorepo and can be imported using:
```typescript
import { ... } from '@isa/shared/filter';
```
## Core Concepts
### Filter Service
The `FilterService` is the central component of the library, responsible for managing filter state and providing methods to update and query filter values.
```typescript
// Example of using FilterService
export class MyComponent {
constructor(private filterService: FilterService) {}
applyFilters() {
// Apply filters and get the query
const query = this.filterService.getQuery();
// Use the query to fetch filtered data
}
resetFilters() {
this.filterService.reset();
}
}
```
### Filter Types
The library supports several filter input types:
- **Text Filters** (`InputType.Text`): For search queries and text-based filtering
- **Checkbox Filters** (`InputType.Checkbox`): For multi-select options
- **Date Range Filters** (`InputType.DateRange`): For time-based filtering
### Query Settings
Filter configurations are defined using the `QuerySettings` interface, which includes filter groups, input fields, and sort options.
```typescript
// Example QuerySettings configuration
const settings: QuerySettings = {
filter: [
{
group: 'products',
label: 'Product Filters',
input: [
{
key: 'category',
label: 'Category',
type: InputType.Checkbox,
options: {
values: [
{ label: 'Electronics', value: 'electronics' },
{ label: 'Clothing', value: 'clothing' },
],
},
},
],
},
],
input: [],
orderBy: [
{ by: 'price', label: 'Price (Low to High)', desc: false },
{ by: 'price', label: 'Price (High to Low)', desc: true },
],
};
```
## Components
### Filter Menu
The `FilterMenuComponent` provides a UI for displaying and interacting with filters:
```html
<filter-filter-menu (applied)="onApplyFilters()" (reseted)="onResetFilters()">
</filter-filter-menu>
```
### Order By Toolbar
The `OrderByToolbarComponent` allows users to sort data based on different criteria:
```html
<filter-order-by-toolbar [commitOnToggle]="true" (toggled)="onSortChanged()">
</filter-order-by-toolbar>
```
### Input Components
The library includes specialized components for different input types:
- `CheckboxInputComponent`: For multi-select options
- `DatepickerRangeInputComponent`: For date range selection
- `SearchBarInputComponent`: For text-based search
### Input Renderer
The `InputRendererComponent` dynamically renders the appropriate input component based on the filter type:
```html
<filter-input-renderer [filterInput]="myFilterInput"></filter-input-renderer>
```
## Usage Examples
### Basic Filter Implementation
```typescript
// Import filter library
import { QuerySettings, InputType, provideFilter } from '@isa/shared/filter';
// Define filter settings
const settings: QuerySettings = {
filter: [
{
group: 'filter',
label: 'Filters',
input: [
{
key: 'status',
label: 'Status',
type: InputType.Checkbox,
options: {
values: [
{ label: 'Active', value: 'active' },
{ label: 'Inactive', value: 'inactive' },
],
},
},
],
},
],
input: [],
orderBy: [
{ by: 'name', label: 'Name (A-Z)', desc: false },
{ by: 'name', label: 'Name (Z-A)', desc: true },
],
};
// Provide filter in component/module
@Component({
// ...
providers: [provideFilter(settings)],
})
export class MyFilterComponent {
// ...
}
```
### Implementing Filter Logic
```typescript
@Component({
// ...
})
export class ProductListComponent {
constructor(private filterService: FilterService) {}
products = computed(() => {
const query = this.filterService.getQuery();
return this.applyFilters(this.allProducts, query);
});
private applyFilters(products: Product[], query: Query): Product[] {
// Implement filtering logic based on query
return filteredProducts;
}
onApplyFilters() {
// Trigger data refresh or update
}
}
```
## Schema Validation
The library uses Zod schemas to validate filter configurations and user inputs:
```typescript
// Example of a filter schema
export const CheckboxFilterInputSchema = CheckboxFilterInputBaseSchema.extend({
options: z
.object({
values: z.array(CheckboxFilterInputOptionSchema).optional(),
max: z.number().optional(),
})
.optional(),
selected: z.array(z.string()).default([]),
type: z.literal(InputType.Checkbox),
});
```
## Architecture
The Filter library is organized into several key modules:
### Core Module
- **Schemas**: Type definitions and validation using Zod
- **Mappings**: Functions to transform between API and UI models
- **Service**: The `FilterService` for state management
- **Tokens**: Injection tokens for DI configuration
### Inputs Module
- **CheckboxInput**: Multi-select options component
- **DatepickerRangeInput**: Date range selection component
- **SearchBarInput**: Text-based search component
- **InputRenderer**: Dynamic component renderer
### Menus Module
- **FilterMenu**: UI for displaying and interacting with filters
- **InputMenu**: Specialized component for input configuration
### Actions Module
Components for filter operations (apply, reset, etc.)
### Order By Module
Components for sorting data based on different criteria
## Best Practices
1. **Group Related Filters**: Use filter groups to organize related filters together
2. **Provide Clear Labels**: Use descriptive labels for filters and options
3. **Use Appropriate Filter Types**: Choose the right filter type for the data being filtered
4. **Handle Filter State**: Properly manage filter state, especially for URL synchronization
5. **Combine with NgRx**: For complex applications, consider using the library with NgRx for state management
## Running unit tests

View File

@@ -77,14 +77,7 @@ export class FilterActionsComponent {
* Otherwise, all filter inputs are committed.
*/
onApply() {
const inputKey = this.inputKey();
if (!inputKey) {
this.filterService.commit();
} else {
this.filterService.commitInput(inputKey);
}
this.filterService.commit();
this.applied.emit();
}
@@ -99,11 +92,11 @@ export class FilterActionsComponent {
const inputKey = this.inputKey();
if (!inputKey) {
this.filterInputs().forEach((input) => {
this.filterService.resetInput(input.key);
});
this.filterService.resetInput(
this.filterInputs().map((input) => input.key),
);
} else {
this.filterService.resetInput(inputKey);
this.filterService.resetInput([inputKey]);
}
this.filterService.commit();

View File

@@ -1,4 +1,4 @@
import { computed, inject, Injectable, signal } from '@angular/core';
import { computed, effect, inject, Injectable, signal } from '@angular/core';
import { InputType } from '../types';
import { getState, patchState, signalState } from '@ngrx/signals';
import { filterMapping } from './mappings';
@@ -11,9 +11,15 @@ import {
QuerySchema,
} from './schemas';
import { FILTER_ON_COMMIT, FILTER_ON_INIT, QUERY_SETTINGS } from './tokens';
import { logger } from '@isa/core/logging';
@Injectable()
export class FilterService {
#logger = logger(() => ({
library: 'shared/filter',
class: 'FilterService',
}));
#onInit = inject(FILTER_ON_INIT, { optional: true })?.map((fn) => fn(this));
#onCommit = inject(FILTER_ON_COMMIT, { optional: true })?.map((fn) =>
fn(this),
@@ -23,7 +29,8 @@ export class FilterService {
private readonly defaultState = filterMapping(this.settings);
#commitdState = signal(structuredClone(this.defaultState));
// Use a more lightweight approach for creating the initial state
#commitdState = signal({ ...this.defaultState });
#state = signalState(this.#commitdState());
@@ -39,11 +46,19 @@ export class FilterService {
});
}
/**
* Sets the ordering field and direction for the filter.
*
* @param by - The field to order by
* @param dir - The direction to order by (asc or desc)
* @param options - Optional parameters
* @param options.commit - If true, commits the changes immediately
*/
setOrderBy(
by: string,
dir: OrderByDirection | undefined,
options?: { commit: boolean },
) {
): void {
const orderByList = this.#state.orderBy().map((o) => {
if (o.by === by && o.dir === dir) {
return { ...o, selected: true };
@@ -58,6 +73,14 @@ export class FilterService {
}
}
/**
* Sets the text value for an input with the specified key.
*
* @param key - The key of the input to update
* @param value - The new text value to set
* @param options - Optional parameters
* @param options.commit - If true, commits the changes immediately
*/
setInputTextValue(
key: string,
value: string | undefined,
@@ -72,8 +95,7 @@ export class FilterService {
return { ...input, value };
}
console.warn(`Input type not supported: ${input.type}`);
this.logUnsupportedInputType(input, 'setInputTextValue');
return input;
});
@@ -84,6 +106,14 @@ export class FilterService {
}
}
/**
* Sets the selected values for a checkbox input with the specified key.
*
* @param key - The key of the checkbox input to update
* @param selected - Array of selected values
* @param options - Optional parameters
* @param options.commit - If true, commits the changes immediately
*/
setInputCheckboxValue(
key: string,
selected: string[],
@@ -98,8 +128,7 @@ export class FilterService {
return { ...input, selected };
}
console.warn(`Input type not supported: ${input.type}`);
this.logUnsupportedInputType(input, 'setInputCheckboxValue');
return input;
});
@@ -110,6 +139,15 @@ export class FilterService {
}
}
/**
* Sets the date range values for an input with the specified key.
*
* @param key - The key of the date range input to update
* @param start - The start date as a string
* @param stop - The end date as a string
* @param options - Optional parameters
* @param options.commit - If true, commits the changes immediately
*/
setInputDateRangeValue(
key: string,
start?: string,
@@ -125,8 +163,7 @@ export class FilterService {
return { ...input, start, stop };
}
console.warn(`Input type not supported: ${input.type}`);
this.logUnsupportedInputType(input, 'setInputDateRangeValue');
return input;
});
@@ -137,6 +174,20 @@ export class FilterService {
}
}
/**
* Helper method to consistently log unsupported input type warnings
* @private
* @param input - The input that has an unsupported type
* @param method - The method name where the warning occurred
*/
private logUnsupportedInputType(input: FilterInput, method: string): void {
this.#logger.warn(`Input type not supported`, () => ({
inputType: input.type,
inputKey: input.key,
method,
}));
}
/**
* Indicates whether the current state is the default state.
* This computed property checks if the current state is equal to the default state.
@@ -146,7 +197,13 @@ export class FilterService {
return isEqual(currentState.inputs, this.defaultState.inputs);
});
isDefaultFilterInput(filterInput: FilterInput) {
/**
* Checks if a specific filter input is in its default state.
*
* @param filterInput - The filter input to check
* @returns True if the input is in its default state, false otherwise
*/
isDefaultFilterInput(filterInput: FilterInput): boolean {
const currentInputState = this.#state
.inputs()
.find((i) => i.key === filterInput.key);
@@ -160,7 +217,7 @@ export class FilterService {
/**
* Indicates whether the current state is empty.
*/
isEmptyFilter = computed(() => {
isEmpty = computed(() => {
const currentState = getState(this.#state);
return currentState.inputs.every((input) => {
if (input.type === InputType.Text) {
@@ -175,31 +232,50 @@ export class FilterService {
return !input.start && !input.stop;
}
console.warn(`Input type not supported`);
this.#logger.warn(`Input type not supported`, () => ({
input,
method: 'isEmptyFilter',
}));
return true;
});
});
isEmptyFilterInput(filterInput: FilterInput) {
/**
* Checks if a specific filter input has an empty value.
* For text inputs, checks if the value is falsy.
* For checkbox inputs, checks if the selected array is empty.
* For date range inputs, checks if both start and stop are falsy.
*
* @param filterInput - The filter input to check
* @returns True if the filter input is empty, false otherwise
*/
isEmptyFilterInput(filterInput: FilterInput): boolean {
const currentInputState = this.#state
.inputs()
.find((i) => i.key === filterInput.key);
if (currentInputState?.type === InputType.Text) {
if (!currentInputState) {
this.#logger.warn(`Input not found`, () => ({
inputKey: filterInput.key,
method: 'isEmptyFilterInput',
}));
return true;
}
if (currentInputState.type === InputType.Text) {
return !currentInputState.value;
}
if (currentInputState?.type === InputType.Checkbox) {
if (currentInputState.type === InputType.Checkbox) {
return !currentInputState.selected?.length;
}
if (currentInputState?.type === InputType.DateRange) {
if (currentInputState.type === InputType.DateRange) {
return !currentInputState.start && !currentInputState.stop;
}
console.warn(`Input type not supported`);
this.logUnsupportedInputType(currentInputState, 'isEmptyFilterInput');
return true;
}
@@ -207,36 +283,86 @@ export class FilterService {
* Reverts the current state to the last committed state.
* This method restores the state by applying the previously saved committed state.
*/
rollback() {
rollback(): void {
const currentState = getState(this.#state);
const committedState = this.#commitdState();
if (isEqual(currentState, committedState)) {
this.#logger.debug('No changes to rollback', () => ({
changes: false,
}));
return;
}
this.#logger.debug('Rolling back filter state', () => ({
changes: true,
currentState,
committedState,
}));
patchState(this.#state, this.#commitdState);
}
/**
* Rolls back the input state for a specific key to its last committed state.
* Rolls back the input state for specific keys to their last committed state.
* If the input with the given key exists in the committed state, it replaces
* the current input with the committed one. Otherwise, the input remains unchanged.
*
* @param key - The key of the input to roll back.
* @param keys - The keys of the inputs to roll back
*/
rollbackInput(key: string) {
rollbackInput(keys: string[]): void {
// Find committed inputs for the specified keys
const committedInputs = this.#commitdState().inputs;
// First check if there's anything to rollback
const hasChangesToRollback = keys.some((key) => {
const currentInput = this.#state.inputs().find((i) => i.key === key);
const committedInput = committedInputs.find((i) => i.key === key);
return committedInput && !isEqual(currentInput, committedInput);
});
// Only proceed if there are changes to rollback
if (!hasChangesToRollback) {
this.#logger.debug('No changes to rollback for specified inputs', () => ({
inputKeys: keys,
}));
return;
}
// Apply rollback for changed inputs
const inputs = this.#state.inputs().map((input) => {
if (input.key !== key) {
if (!keys.includes(input.key)) {
return input;
}
return this.#commitdState().inputs.find((i) => i.key === key) || input;
// Get the committed version of this input
const committedInput = committedInputs.find((i) => i.key === input.key);
return committedInput || input;
});
this.#logger.debug('Rolling back specified inputs', () => ({
inputKeys: keys,
}));
patchState(this.#state, { inputs });
}
/**
* Commits the current state by capturing a snapshot of the internal state.
* This method updates the private `#commitdState` property with the result
* of the `getState` function applied to the current `#state`.
* This method updates the private `#commitdState` property with the current state
* and triggers any registered commit callbacks.
*/
commit() {
this.#commitdState.set(getState(this.#state));
commit(): void {
const currentState = getState(this.#state);
const committedState = this.#commitdState();
if (!isEqual(currentState, committedState)) {
this.#commitdState.set(currentState);
this.#logger.debug('Filter state committed', () => ({
changes: true,
}));
} else {
this.#logger.debug('No changes to commit', () => ({
changes: false,
}));
}
this.#onCommit?.forEach((commitFn) => {
commitFn();
@@ -244,50 +370,39 @@ export class FilterService {
}
/**
* Commits the input associated with the specified key to the committed state.
* Clears all filter values without resetting to default values.
* This sets text inputs to undefined, checkbox selections to empty arrays,
* and date ranges to undefined for both start and stop.
*
* This method searches for an input in the current state that matches the given key.
* If found, it clones the committed state, updates the corresponding input in the
* cloned state with the found input, and then replaces the committed state with the
* updated clone.
*
* @param key - The unique identifier of the input to commit.
* @param options - Optional parameters
* @param options.commit - If true, commits the changes immediately after clearing
*/
commitInput(key: string) {
const inputToCommit = this.#state.inputs().find((i) => i.key === key);
clear(options?: { commit: boolean }): void {
// First check if there's anything to clear
const hasDataToClear = this.#state.inputs().some((input) => {
if (input.type === InputType.Text) {
return !!input.value;
}
if (!inputToCommit) {
console.warn(`No input found with key: ${key}`);
if (input.type === InputType.Checkbox) {
return input.selected?.length > 0;
}
if (input.type === InputType.DateRange) {
return !!input.start || !!input.stop;
}
return false;
});
// Only proceed if there's data to clear
if (!hasDataToClear) {
this.#logger.debug('No filter data to clear', () => ({
changes: false,
}));
return;
}
const inputIndex = this.#commitdState().inputs.findIndex(
(i) => i.key === key,
);
if (inputIndex === -1) {
console.warn(`No committed input found with key: ${key}`);
return;
}
this.#commitdState.set({
...this.#commitdState(),
inputs: this.#commitdState().inputs.map((input, index) =>
index === inputIndex ? inputToCommit : input,
),
});
}
commitOrderBy() {
const orderBy = this.#state.orderBy();
this.#commitdState.set({
...this.#commitdState(),
orderBy,
});
}
clear(options?: { commit: boolean }) {
const inputs = this.#state.inputs().map((input) => {
if (input.type === InputType.Text) {
return { ...input, value: undefined };
@@ -304,6 +419,10 @@ export class FilterService {
return input;
});
this.#logger.debug('Clearing filter state', () => ({
changes: true,
}));
patchState(this.#state, { inputs });
if (options?.commit) {
this.commit();
@@ -316,49 +435,71 @@ export class FilterService {
* @param options - Optional parameters for the reset operation.
* @param options.commit - If `true`, the changes will be committed after resetting the state.
*/
reset(options?: { commit: boolean }) {
patchState(this.#state, structuredClone(this.defaultState));
reset(options?: { commit: boolean }): void {
// Use a more lightweight approach than structuredClone
patchState(this.#state, { ...this.defaultState });
if (options?.commit) {
this.commit();
}
}
/**
* Resets the input with the specified key to its default state.
* Resets one or more inputs to their default state.
*
* @param key - The key of the input to reset.
* @param keys - The key or array of keys of the inputs to reset.
* @param options - Optional parameters for the reset operation.
* @param options.commit - If `true`, commits the changes after resetting the input.
* @param options.commit - If `true`, commits the changes after resetting the input(s).
*
* @remarks
* - If no input is found with the specified key, a warning is logged, and the method exits.
* - The method updates the state by replacing the input with its default configuration.
* - If no input is found with the specified key, a warning is logged for that key.
* - The method updates the state by replacing the input(s) with their default configuration.
*
* @example
* ```typescript
* // Reset a single input
* filterService.resetInput('exampleKey', { commit: true });
*
* // Reset multiple inputs
* filterService.resetInput(['key1', 'key2'], { commit: true });
* ```
*/
resetInput(key: string, options?: { commit: boolean }) {
const defaultFilter = structuredClone(this.defaultState);
const inputToReset = defaultFilter.inputs.find((i) => i.key === key);
resetInput(keys: string[], options?: { commit: boolean }): void {
// Use a more lightweight approach than structuredClone
const defaultFilter = { ...this.defaultState };
if (!inputToReset) {
console.warn(`No input found with key: ${key}`);
// Find all default inputs that match the provided keys
const inputsToReset = keys
.map((key) => {
const inputToReset = defaultFilter.inputs.find((i) => i.key === key);
if (!inputToReset) {
this.#logger.warn(`No input found with key`, () => ({
key,
method: 'resetInput',
}));
}
return { key, defaultInput: inputToReset };
})
.filter((item) => item.defaultInput !== undefined);
if (inputsToReset.length === 0) {
return;
}
const inputIndex = this.#state.inputs().findIndex((i) => i.key === key);
// Create a set of keys for faster lookups
const keysToReset = new Set(inputsToReset.map((item) => item.key));
if (inputIndex === -1) {
console.warn(`No input found with key: ${key}`);
return;
}
const inputs = this.#state.inputs().map((input) => {
if (!keysToReset.has(input.key)) {
return input;
}
const resetItem = inputsToReset.find((item) => item.key === input.key);
return resetItem?.defaultInput ?? input;
});
patchState(this.#state, {
inputs: this.#state
.inputs()
.map((input, index) => (index === inputIndex ? inputToReset : input)),
inputs,
});
if (options?.commit) {
@@ -366,9 +507,15 @@ export class FilterService {
}
}
resetOrderBy(options?: { commit: boolean }) {
const defaultOrderBy = structuredClone(this.defaultState.orderBy);
patchState(this.#state, { orderBy: defaultOrderBy });
/**
* Resets the orderBy state to its default values.
*
* @param options - Optional parameters for the reset operation.
* @param options.commit - If `true`, the changes will be committed after resetting.
*/
resetOrderBy(options?: { commit: boolean }): void {
// Use a more lightweight approach than structuredClone
patchState(this.#state, { orderBy: [...this.defaultState.orderBy] });
if (options?.commit) {
this.commit();
@@ -384,11 +531,15 @@ export class FilterService {
case InputType.Text:
if (input.value) {
result[input.key] = input.value;
} else {
result[input.key] = '';
}
break;
case InputType.Checkbox:
if (input.selected) {
result[input.key] = input.selected.join(';');
} else {
result[input.key] = '';
}
break;
case InputType.DateRange:
@@ -398,6 +549,8 @@ export class FilterService {
result[input.key] = `"${input.start}"-`;
} else if (input.stop) {
result[input.key] = `-"${input.stop}"`;
} else {
result[input.key] = '';
}
break;
}
@@ -406,12 +559,21 @@ export class FilterService {
const orderBy = commited.orderBy.find((o) => o.selected);
if (orderBy) {
result['orderBy'] = `${orderBy.by}:${orderBy.dir}`;
result['order_by'] = `${orderBy.by}:${orderBy.dir}`;
} else {
// Ensure 'orderBy' is not included if no order is selected
result['order_by'] = '';
}
return result;
});
/**
* Checks if the current query parameters match the provided parameters.
*
* @param params - The parameters to compare with the current query parameters
* @returns True if the parameters match, false otherwise
*/
isQueryParamsEqual(params: Record<string, string>): boolean {
const currentParams = this.queryParams();
return this.queryParamKeys().every(
@@ -424,7 +586,7 @@ export class FilterService {
const orderBy = this.orderBy().find((o) => o.dir);
if (orderBy) {
keys.push('orderBy');
keys.push('order_by');
}
return keys;
@@ -483,29 +645,46 @@ export class FilterService {
});
});
parseQueryParams(
/**
* Parses query parameters into filter state.
*
* @param params - Record of query parameters to parse
* @param options - Optional parameters
* @param options.commit - If true, commits the changes immediately after parsing
*/ parseQueryParams(
params: Record<string, string>,
options?: { commit: boolean },
): void {
this.reset();
for (const key in params) {
if (key === 'orderBy') {
if (key === 'order_by') {
const [by, dir] = params[key].split(':');
const orderBy = this.orderBy().some(
(o) => o.by === by && o.dir === dir,
);
if (orderBy) {
console.warn(`OrderBy already exists: ${by}:${dir}`);
this.#logger.warn(`OrderBy already exists`, () => ({
by,
dir,
method: 'parseQueryParams',
}));
this.setOrderBy(by, OrderByDirectionSchema.parse(dir));
}
continue;
}
const inputType = this.inputs().find((i) => i.key === key)?.type;
const input = this.inputs().find((i) => i.key === key);
if (!input) {
this.#logger.warn(`Input not found for key`, () => ({
key,
method: 'parseQueryParams',
}));
continue;
}
switch (inputType) {
switch (input.type) {
case InputType.Text:
this.setInputTextValue(key, params[key]);
break;
@@ -521,7 +700,7 @@ export class FilterService {
break;
}
default:
console.warn(`Input type not supported: ${inputType}`);
this.logUnsupportedInputType(input, 'parseQueryParams');
break;
}
}

View File

@@ -1,4 +1,9 @@
import { DestroyRef, EnvironmentProviders, inject, Provider } from '@angular/core';
import {
DestroyRef,
EnvironmentProviders,
inject,
Provider,
} from '@angular/core';
import { QuerySettings } from '../types';
import { FilterService, QUERY_SETTINGS } from './filter.service';
import { NavigationEnd, NavigationExtras, Router } from '@angular/router';
@@ -35,7 +40,9 @@ export function withQuerySettings(querySettings: QuerySettings): FilterFeature {
]);
}
export function withQuerySettingsFactory(querySettingsFactory: () => QuerySettings): FilterFeature {
export function withQuerySettingsFactory(
querySettingsFactory: () => QuerySettings,
): FilterFeature {
return filterFeature(ProvideFilterKind.QuerySettings, [
{ provide: QUERY_SETTINGS, useFactory: querySettingsFactory },
]);
@@ -44,7 +51,10 @@ export function withQuerySettingsFactory(querySettingsFactory: () => QuerySettin
export function withQueryParamsSync({
replaceUrl = true,
queryParamsHandling = 'merge',
}: Pick<NavigationExtras, 'replaceUrl' | 'queryParamsHandling'> = {}): FilterFeature {
}: Pick<
NavigationExtras,
'replaceUrl' | 'queryParamsHandling'
> = {}): FilterFeature {
function onCommitFactory() {
const router = inject(Router);
return (filterService: FilterService) => () => {

View File

@@ -89,7 +89,9 @@ export class FilterMenuButtonComponent {
*/
this.closed.subscribe(() => {
if (this.rollbackOnClose()) {
this.#filter.rollback();
setTimeout(() => {
this.#filter.rollback();
});
}
});

View File

@@ -4,7 +4,7 @@
(click)="filter.clear()"
type="button"
>
<span class="isa-text-body-2-bold" [class.active]="!filter.isEmptyFilter()">
<span class="isa-text-body-2-bold" [class.active]="!filter.isEmpty()">
Alle abwählen
</span>
</button>
@@ -25,7 +25,11 @@
}
</div>
<ng-icon class="text-isa-neutral-900" name="isaActionChevronRight" size="1.5rem"></ng-icon>
<ng-icon
class="text-isa-neutral-900"
name="isaActionChevronRight"
size="1.5rem"
></ng-icon>
</button>
}
} @else {
@@ -39,7 +43,10 @@
<span> {{ input!.label }} </span>
</button>
<filter-input-renderer class="overflow-scroll" [filterInput]="input!"></filter-input-renderer>
<filter-input-renderer
class="overflow-scroll"
[filterInput]="input!"
></filter-input-renderer>
}
<filter-actions