src/app/core/basic-datatypes/schema-embed/schema-embed.datatype.ts
Datatype for the EntitySchemaService transforming values of complex objects recursively.
Can be used in two ways:
dataType: "schema-embed" and define the inner schema
in additional as a SchemaEmbedDatatypeAdditional map.embeddedType to point to a class
with @DatabaseField() annotations.The config in additional is merged with the embeddedType schema (if present),
allowing runtime config to extend or override a class's annotations.
DefaultDatatype<EntityType, DBType>
DefaultDatatype<EntityType, DBType>
Properties |
|
Methods |
| getEffectiveSchema | ||||||
getEffectiveSchema(schemaField?: EntitySchemaField)
|
||||||
|
Build the effective inner schema from the embedded type's annotations
and/or the
Parameters :
Returns :
EntitySchema
|
| getEmbeddedFieldDbPath | ||||||
getEmbeddedFieldDbPath(innerProp: string)
|
||||||
|
Path of an inner property within a stored embedded object, for database queries. The embedded object is stored as-is, so this is the property name itself. Can be removed once EventAttendanceMapDatatype is removed.
Parameters :
Returns :
string
|
| getReferenceSelector | ||||||||||||
getReferenceSelector(fieldId: string, schemaField: EntitySchemaField, referencedId: string)
|
||||||||||||
|
Inherited from
DefaultDatatype
|
||||||||||||
|
The embedded object is stored as-is, so an inner reference is matched through the outer field - either inside an array of embedded objects or, where a single object is stored, through the dotted path to the inner property. The inner keys come from getEffectiveSchema.
Parameters :
Returns :
[] | undefined
|
| holdsArray | ||||||
holdsArray(schemaField: EntitySchemaField)
|
||||||
|
Whether the given field holds an array of embedded objects (as opposed to a single embedded object). Can be removed once EventAttendanceMapDatatype is removed.
Parameters :
Returns :
boolean
|
| transformToDatabaseFormat | |||||||||
transformToDatabaseFormat(value: EntityType, schemaField?: EntitySchemaField)
|
|||||||||
|
Inherited from
DefaultDatatype
|
|||||||||
|
Parameters :
Returns :
DBType
|
| transformToObjectFormat | |||||||||
transformToObjectFormat(value: DBType, schemaField?: EntitySchemaField)
|
|||||||||
|
Inherited from
DefaultDatatype
|
|||||||||
|
Parameters :
Returns :
EntityType
|
| Async anonymize | ||||||||||||||||
anonymize(value: EntityType, schemaField: EntitySchemaField, parent: any)
|
||||||||||||||||
|
Inherited from
DefaultDatatype
|
||||||||||||||||
|
(Partially) anonymize to "retain-anonymized" for reporting purposes without personal identifiable information.
Parameters :
Returns :
Promise<any>
|
| Static detectAllFieldsInEntity | ||||||||||||
detectAllFieldsInEntity(entityOrType: Entity | EntityConstructor, dataTypes: string | string[])
|
||||||||||||
|
Inherited from
DefaultDatatype
|
||||||||||||
|
Detect all fields of the given datatype(s) in an entity's schema.
Parameters :
Returns :
literal type[]
Array of matching fields with their id and schema definition. |
| Static detectFieldInEntity | ||||||||||||
detectFieldInEntity(entityOrType: Entity | EntityConstructor, dataTypes: string | string[])
|
||||||||||||
|
Inherited from
DefaultDatatype
|
||||||||||||
|
Detect the first field of the given datatype(s) in an entity's schema. Scans the schema for a field whose Subclasses typically override this without the extra
Parameters :
Returns :
string | undefined
The field name of the first matching field, or |
| getExportColumns | ||||||
getExportColumns(schemaField: EntitySchemaField)
|
||||||
|
Inherited from
DefaultDatatype
|
||||||
|
Export columns for a field using this datatype. Each returned column contributes a CSV header and its own value resolver.
The returned The default implementation returns a single column with the raw field value. Readable formatting can be applied by callers during CSV transformation. Override this to provide custom or additional columns (e.g. entity references can add a human-readable name column alongside the ID column).
Parameters :
Returns :
ExportColumnMapping[]
|
| Async importMapFunction | ||||||||||||||||||||
importMapFunction(val: any, schemaField: EntitySchemaField, additional?: any, importProcessingContext?: any)
|
||||||||||||||||||||
|
Inherited from
DefaultDatatype
|
||||||||||||||||||||
|
The function used to map values from the import data to values in the entities to be created. to share information across processing of multiple columns and rows.
Parameters :
Returns :
Promise<EntityType>
|
| Async importMatchField | ||||||||||||||||
importMatchField(schemaField: EntitySchemaField, columns: ColumnImportInput[], importProcessingContext?: ImportProcessingContext)
|
||||||||||||||||
|
Inherited from
DefaultDatatype
|
||||||||||||||||
|
Map all columns mapped to one target field to the value(s) stored there. ImportService calls this once per target field per row with every column mapped to that field. The default handles trimming and array splitting and delegates each individual value to importMapFunction. Datatypes that need to consider several columns together (e.g. matching an entity by more than one property) override this. (isArray=true), or undefined if nothing was mapped
Parameters :
Returns :
Promise<unknown>
the value to store: a single value (isArray=false) or array (isArray=true), or undefined if nothing was mapped |
| normalizeSchemaField | ||||||||
normalizeSchemaField(schemaField: EntitySchemaField)
|
||||||||
|
Inherited from
DefaultDatatype
|
||||||||
|
Return the (potentially adjusted) schema field for this datatype. Called when schema fields are set up (e.g. from config), allowing the datatype to normalize or fill in required settings. Override this in a subclass to enforce constraints
(e.g. always setting
Parameters :
Returns :
EntitySchemaField
The schema field to use (default: unchanged) |
| sortValue | ||||||
sortValue(_fieldValue: EntityType)
|
||||||
|
Inherited from
DefaultDatatype
|
||||||
|
Return a comparable primitive for sorting this field's value in a list column.
Return
Parameters :
Returns :
number | string | undefined
|
| Static Readonly dataType |
Type : string
|
Default value : "schema-embed"
|
|
Inherited from
DefaultDatatype
|
| Optional embeddedType |
Type : SchemaEmbeddedType<EntityType>
|
| Protected Readonly schemaService |
Type : unknown
|
Default value : inject(EntitySchemaService)
|
| editComponent |
Type : string
|
Default value : "EditText"
|
|
Inherited from
DefaultDatatype
|
| Readonly importAllowsMultiMapping |
Type : boolean
|
Default value : false
|
|
Inherited from
DefaultDatatype
|
|
Whether this datatype allows multiple values to be mapped to the same entity field during import. |
| Optional importConfigComponent |
Type : string
|
|
Inherited from
DefaultDatatype
|
|
A component to be rendered inline to configure the import transformation (e.g. defining a format or value mapping). The component receives inputs:
|
| Optional importConfigDialog |
Type : string
|
|
Inherited from
DefaultDatatype
|
|
A dialog component in which the import transformation is configured (see MappingDialogData for the data it receives). Datatypes that require such a dialog get it opened automatically as soon as the user maps a column to one of their fields, so that the suggested settings are reviewed and confirmed rather than silently skipped. |
| Readonly internalValidators |
Type : ValidatorFn[]
|
Default value : []
|
|
Inherited from
DefaultDatatype
|
|
Validators that every value of this datatype has to pass in entity forms,
in addition to the |
| Static label |
Type : string
|
Default value : $localize`:datatype-label:any`
|
|
Inherited from
DefaultDatatype
|
|
The human-readable name for this dataType, used in config UIs. |
| viewComponent |
Type : string
|
Default value : "DisplayText"
|
|
Inherited from
DefaultDatatype
|
|
The default component how this datatype should be displayed in lists and forms. The edit component has to be a registered component. Components that are registered contain the |
import {
DefaultDatatype,
referenceIdConditions,
} from "../../entity/default-datatype/default.datatype";
import { EntitySchemaService } from "../../entity/schema/entity-schema.service";
import { EntitySchemaField } from "../../entity/schema/entity-schema-field";
import { getInnerEntityReferenceFields } from "../../entity/schema/entity-reference-fields";
import type { DataFilter } from "../../filter/filters/filters";
import {
EntitySchema,
SchemaEmbeddedType,
} from "../../entity/schema/entity-schema";
import { inject, Injectable } from "@angular/core";
/**
* Configuration for the "schema-embed" datatype's `additional` field.
*
* Defines the inner schema of the embedded object, using the same format as
* entity `attributes` config: a map of field names to their schema definitions.
*
* @example
* ```
* @DatabaseField({
* dataType: "schema-embed",
* additional: {
* "phoneNumber": { dataType: "string" },
* "type": { dataType: "string" }
* } as SchemaEmbedDatatypeAdditional
* })
* phoneNumber: { phoneNumber: string; type: string };
* ```
*/
export interface SchemaEmbedDatatypeAdditional {
[fieldId: string]: EntitySchemaField;
}
/**
* Build the inner schema of an embedded field from the embedded type's own schema
* and the field's `additional` config (a {@link SchemaEmbedDatatypeAdditional} map).
*
* An entry in `additional` only overrides what it states explicitly (e.g. restricting the
* allowed entity types of a reference), everything else (`dataType`, `label`, ...)
* is inherited from the embedded type's field of the same id.
*
* @param baseSchema schema of the embedded type (e.g. `embeddedType.schema`), not mutated
* @param additional the `additional` config of the field holding the embedded object(s)
*/
export function mergeEmbeddedSchema(
baseSchema: EntitySchema | undefined,
additional: unknown,
): EntitySchema {
const schema: EntitySchema = new Map(baseSchema ?? []);
if (
!additional ||
typeof additional !== "object" ||
Array.isArray(additional)
) {
return schema;
}
for (const [key, value] of Object.entries(
additional as SchemaEmbedDatatypeAdditional,
)) {
if (value && typeof value === "object") {
schema.set(key, { ...schema.get(key), ...value, id: key });
}
}
return schema;
}
/**
* Datatype for the EntitySchemaService transforming values of complex objects recursively.
*
* Can be used in two ways:
* 1. **Config-based** (directly via annotation): Set `dataType: "schema-embed"` and define the inner schema
* in `additional` as a {@link SchemaEmbedDatatypeAdditional} map.
* 2. **Subclass-based** (extending this class): Override `embeddedType` to point to a class
* with `@DatabaseField()` annotations.
*
* The config in `additional` is merged with the `embeddedType` schema (if present),
* allowing runtime config to extend or override a class's annotations.
*/
@Injectable()
export class SchemaEmbedDatatype<
EntityType = any,
DBType = any,
> extends DefaultDatatype<EntityType, DBType> {
static override readonly dataType: string = "schema-embed";
embeddedType?: SchemaEmbeddedType<EntityType>;
protected readonly schemaService = inject(EntitySchemaService);
/**
* Build the effective inner schema from the embedded type's annotations
* and/or the `additional` config.
*/
getEffectiveSchema(schemaField?: EntitySchemaField): EntitySchema {
return mergeEmbeddedSchema(
this.embeddedType?.schema,
schemaField?.additional,
);
}
/**
* Whether the given field holds an array of embedded objects
* (as opposed to a single embedded object).
*
* Can be removed once {@link EventAttendanceMapDatatype} is removed.
*/
holdsArray(schemaField: EntitySchemaField): boolean {
return schemaField.isArray === true;
}
/**
* Path of an inner property within a stored embedded object, for database queries.
* The embedded object is stored as-is, so this is the property name itself.
*
* Can be removed once {@link EventAttendanceMapDatatype} is removed.
*/
getEmbeddedFieldDbPath(innerProp: string): string {
return innerProp;
}
/**
* The embedded object is stored as-is, so an inner reference is matched through the
* outer field - either inside an array of embedded objects or, where a single object
* is stored, through the dotted path to the inner property.
*
* The inner keys come from {@link getEffectiveSchema}.
*/
override getReferenceSelector(
fieldId: string,
schemaField: EntitySchemaField,
referencedId: string,
): DataFilter<any>[] | undefined {
const innerKeys = getInnerEntityReferenceFields(
this.getEffectiveSchema(schemaField),
).map(([key]) => key);
if (innerKeys.length === 0) {
// nothing we could match on - let the caller load the whole type instead
return undefined;
}
return innerKeys.flatMap((key) => [
{ [fieldId]: { $elemMatch: { [key]: referencedId } } },
{
[fieldId]: {
$elemMatch: { [key]: { $elemMatch: { $eq: referencedId } } },
},
},
...referenceIdConditions(`${fieldId}.${key}`, referencedId),
]);
}
override transformToDatabaseFormat(
value: EntityType,
schemaField?: EntitySchemaField,
): DBType {
const schema = this.getEffectiveSchema(schemaField);
return this.schemaService.transformEntityToDatabaseFormat(
value as any,
schema,
) as DBType;
}
override transformToObjectFormat(
value: DBType,
schemaField?: EntitySchemaField,
): EntityType {
const schema = this.getEffectiveSchema(schemaField);
const transformedValue =
this.schemaService.transformDatabaseToEntityFormat<EntityType>(
value,
schema,
);
if (this.embeddedType) {
const instance = new this.embeddedType();
Object.assign(instance, transformedValue);
return instance;
}
return transformedValue;
}
}