src/app/core/filter/filter.service.ts

Description

Utility service to help handling and aligning filters with entities.

Metadata

Index

Methods

Methods

alignEntityWithFilter
alignEntityWithFilter<T>(entity: T, filter: DataFilter<T>)
Type parameters :
  • T

Patches an entity with values required to pass the filter query. This patch happens in-place.

Parameters :
Name Type Optional Description
entity T No

the entity to be patched

filter DataFilter<T> No

the filter which the entity should pass afterward

Returns : void
combineFilters
combineFilters<T>(entityFilters: EntityFilter[])
Type parameters :
  • T
Parameters :
Name Type Optional
entityFilters EntityFilter<T>[] No
Returns : DataFilter<T>
getFilterPredicate
getFilterPredicate<T>(filter: DataFilter<T>)
Type parameters :
  • T

Builds a predicate for a given filter object. This predicate can be used to filter arrays of objects.

Example :
const predicate = this.filterService.getFilterPredicate(filterObj);
const filtered = this.data.filter(predicate)
Parameters :
Name Type Optional Description
filter DataFilter<T> No

a valid filter object, e.g. as provided by the FilterComponent

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"]);
  }
}

results matching ""

    No results matching ""