src/app/core/filter/filter.service.ts
Utility service to help handling and aligning filters with entities.
| providedIn | root |
Methods |
| alignEntityWithFilter | ||||||||||||
alignEntityWithFilter<T>(entity: T, filter: DataFilter<T>)
|
||||||||||||
|
Defined in src/app/core/filter/filter.service.ts:64
|
||||||||||||
Type parameters :
|
||||||||||||
|
Patches an entity with values required to pass the filter query. This patch happens in-place.
Parameters :
Returns :
void
|
| combineFilters | ||||||
combineFilters<T>(entityFilters: EntityFilter
|
||||||
|
Defined in src/app/core/filter/filter.service.ts:35
|
||||||
Type parameters :
|
||||||
|
Parameters :
Returns :
DataFilter<T>
|
| getFilterPredicate | ||||||||
getFilterPredicate<T>(filter: DataFilter<T>)
|
||||||||
|
Defined in src/app/core/filter/filter.service.ts:54
|
||||||||
Type parameters :
|
||||||||
|
Builds a predicate for a given filter object. This predicate can be used to filter arrays of objects. Example :
Parameters :
Returns :
any
|
import { inject, Injectable } from "@angular/core";
import { EntitySchemaField } from "../entity/schema/entity-schema-field";
import { Entity } from "../entity/model/entity";
import {
allInterpreters,
allParsingInstructions,
createFactory,
Filter,
} from "@ucast/mongo2js";
import moment from "moment";
import { ConfigurableEnumService } from "../basic-datatypes/configurable-enum/configurable-enum.service";
import {
combineFilterConditions,
DataFilter,
Filter as EntityFilter,
} from "./filters/filters";
import { MongoQuery } from "@casl/ability";
import { extendedCompare } from "../../utils/filter-compare-utils";
/**
* Utility service to help handling and aligning filters with entities.
*/
@Injectable({
providedIn: "root",
})
export class FilterService {
private enumService = inject(ConfigurableEnumService);
private filterFactory = createFactory(
allParsingInstructions,
allInterpreters,
{ compare: extendedCompare },
) as Filter;
combineFilters<T extends Entity>(
entityFilters: EntityFilter<T>[],
): DataFilter<T> {
return combineFilterConditions<T>(
...entityFilters.map((value: EntityFilter<T>): DataFilter<T> =>
value.getFilter(),
),
);
}
/**
* Builds a predicate for a given filter object.
* This predicate can be used to filter arrays of objects.
* ```javascript
* const predicate = this.filterService.getFilterPredicate(filterObj);
* const filtered = this.data.filter(predicate)
* ```
* @param filter a valid filter object, e.g. as provided by the `FilterComponent`
*/
getFilterPredicate<T extends Entity>(filter: DataFilter<T>) {
return this.filterFactory<T>((filter ?? {}) as MongoQuery<T>);
}
/**
* Patches an entity with values required to pass the filter query.
* This patch happens in-place.
* @param entity the entity to be patched
* @param filter the filter which the entity should pass afterward
*/
alignEntityWithFilter<T extends Entity>(entity: T, filter: DataFilter<T>) {
const schema = entity.getSchema();
Object.entries(filter ?? {}).forEach(([key, value]) => {
if (key === "$or" && Array.isArray(value)) {
// for $or filters, try each branch
for (const branch of value) {
this.alignEntityWithFilter(entity, branch);
}
} else if (typeof value !== "object") {
// only simple equality filters are automatically applied to new entities, complex conditions (e.g. $lt / $gt) are ignored)
this.assignValueToEntity(key, value, schema, entity);
} else if (value["$elemMatch"]?.["$eq"]) {
// e.g. { children: { $elemMatch: { $eq: "Child:some-id" } } }
this.assignValueToEntity(
key,
value["$elemMatch"]["$eq"],
schema,
entity,
true,
);
}
});
}
/**
* @param isElemMatch whether the value comes from an `$elemMatch` filter condition.
*/
private assignValueToEntity<T extends Entity>(
key: string,
value,
schema: Map<string, EntitySchemaField>,
newEntity: T,
isElemMatch = false,
) {
if (key.includes(".")) {
// TODO only one level deep nesting is supported (also by ucast https://github.com/stalniy/ucast/issues/32)
[key, value] = this.transformNestedKey(key, value);
}
const property = schema.get(key);
if (!property) {
// not a schema property
return;
}
if (isElemMatch && !property.isArray) {
// Filter options (e.g. of ConfigurableEnumFilter, EntityFilter) offer a plain equality and an
// `$elemMatch` variant side by side to also match data saved while the field had the other
// (single / multi select) setting. Only the variant fitting the current field applies here.
return;
}
if (property?.dataType === "configurable-enum") {
value = this.parseConfigurableEnumValue(property, value);
}
if (property?.dataType.includes("date")) {
value = moment(value).toDate();
}
newEntity[key] = property.isArray ? [value] : value;
}
private transformNestedKey(key: string, value): any[] {
const [first, second] = key.split(".");
return [first, { [second]: value }];
}
private parseConfigurableEnumValue(property: EntitySchemaField, value) {
const enumValues = this.enumService.getEnumValues(property.additional);
return enumValues.find(({ id }) => id === value["id"]);
}
}